Spaces:
Build error
Build error
Update src/components/menu-bar/google-drive-save.jsx
Browse files
src/components/menu-bar/google-drive-save.jsx
CHANGED
@@ -16,8 +16,11 @@ class GoogleDriveSave extends React.Component {
|
|
16 |
files: [],
|
17 |
isModalOpen: false,
|
18 |
isLoading: false,
|
|
|
19 |
newFileName: this.props.projectTitle || '無題',
|
20 |
-
showNewFileInput: false
|
|
|
|
|
21 |
};
|
22 |
this.modalContentRef = React.createRef();
|
23 |
}
|
@@ -31,16 +34,20 @@ class GoogleDriveSave extends React.Component {
|
|
31 |
};
|
32 |
|
33 |
handleCloseModal = () => {
|
34 |
-
this.
|
|
|
|
|
35 |
};
|
36 |
|
37 |
handleOverlayClick = (e) => {
|
38 |
-
if (this.modalContentRef.current && !this.modalContentRef.current.contains(e.target)) {
|
39 |
this.handleCloseModal();
|
40 |
}
|
41 |
};
|
42 |
|
43 |
startGoogleLogin = () => {
|
|
|
|
|
44 |
localStorage.removeItem('googleDriveAccessToken');
|
45 |
localStorage.removeItem('googleDriveAccountEmail');
|
46 |
localStorage.removeItem('googleDriveAccountName');
|
@@ -103,7 +110,13 @@ class GoogleDriveSave extends React.Component {
|
|
103 |
<div className={styles.modalContent} ref={this.modalContentRef}>
|
104 |
<div className={styles.modalHeader}>
|
105 |
<h2>Googleドライブに保存</h2>
|
106 |
-
<button
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
</div>
|
108 |
|
109 |
<div className={styles.modalBody}>
|
@@ -111,6 +124,13 @@ class GoogleDriveSave extends React.Component {
|
|
111 |
{this.state.accessToken && this.renderNewFileSection()}
|
112 |
{this.state.accessToken && this.renderFileList()}
|
113 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
</div>
|
115 |
</div>
|
116 |
);
|
@@ -126,6 +146,7 @@ class GoogleDriveSave extends React.Component {
|
|
126 |
<button
|
127 |
onClick={this.handleChangeAccount}
|
128 |
className={styles.changeAccountButton}
|
|
|
129 |
>
|
130 |
アカウントを変更
|
131 |
</button>
|
@@ -139,6 +160,7 @@ class GoogleDriveSave extends React.Component {
|
|
139 |
<button
|
140 |
onClick={this.startGoogleLogin}
|
141 |
className={styles.loginButton}
|
|
|
142 |
>
|
143 |
Googleでログイン
|
144 |
</button>
|
@@ -157,17 +179,31 @@ class GoogleDriveSave extends React.Component {
|
|
157 |
onChange={(e) => this.setState({newFileName: e.target.value})}
|
158 |
className={styles.newFileNameInput}
|
159 |
placeholder="ファイル名を入力"
|
|
|
160 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
<button
|
162 |
onClick={this.handleNewFileSave}
|
163 |
className={styles.newFileSaveButton}
|
164 |
-
disabled={!this.state.newFileName.trim()}
|
165 |
>
|
166 |
保存
|
167 |
</button>
|
168 |
<button
|
169 |
onClick={() => this.setState({showNewFileInput: false})}
|
170 |
className={styles.newFileCancelButton}
|
|
|
171 |
>
|
172 |
キャンセル
|
173 |
</button>
|
@@ -179,8 +215,13 @@ class GoogleDriveSave extends React.Component {
|
|
179 |
return (
|
180 |
<div className={styles.newFileSection}>
|
181 |
<button
|
182 |
-
onClick={() => this.setState({
|
|
|
|
|
|
|
|
|
183 |
className={styles.newFileButton}
|
|
|
184 |
>
|
185 |
新規保存
|
186 |
</button>
|
@@ -244,24 +285,28 @@ class GoogleDriveSave extends React.Component {
|
|
244 |
<button
|
245 |
onClick={() => this.handleLoadFile(project)}
|
246 |
className={styles.actionButton}
|
|
|
247 |
>
|
248 |
読み込む
|
249 |
</button>
|
250 |
<button
|
251 |
onClick={() => this.handleReplaceFile(project)}
|
252 |
className={styles.actionButton}
|
|
|
253 |
>
|
254 |
上書き
|
255 |
</button>
|
256 |
<button
|
257 |
onClick={() => this.handleShareFile(project.id)}
|
258 |
className={classNames(styles.actionButton, styles.shareButton)}
|
|
|
259 |
>
|
260 |
共有
|
261 |
</button>
|
262 |
<button
|
263 |
onClick={() => this.handleDeleteFile(project, thumbnailFiles)}
|
264 |
className={classNames(styles.actionButton, styles.deleteButton)}
|
|
|
265 |
>
|
266 |
削除
|
267 |
</button>
|
@@ -280,12 +325,21 @@ class GoogleDriveSave extends React.Component {
|
|
280 |
<button
|
281 |
onClick={() => this.copyToClipboard(`${SHORT_URL}${fileId}`)}
|
282 |
className={styles.copyButton}
|
|
|
283 |
>
|
284 |
リンクをコピー
|
285 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
286 |
<button
|
287 |
onClick={() => this.copyToClipboard(fileId)}
|
288 |
className={styles.copyButton}
|
|
|
289 |
>
|
290 |
IDのみコピー
|
291 |
</button>
|
@@ -325,6 +379,8 @@ class GoogleDriveSave extends React.Component {
|
|
325 |
}
|
326 |
|
327 |
handleChangeAccount = () => {
|
|
|
|
|
328 |
this.setState({
|
329 |
accessToken: null,
|
330 |
currentAccountEmail: null,
|
@@ -336,18 +392,23 @@ class GoogleDriveSave extends React.Component {
|
|
336 |
};
|
337 |
|
338 |
handleNewFileSave = async () => {
|
|
|
339 |
try {
|
340 |
-
await this.saveToGoogleDrive(null, `${this.state.newFileName}.s4s.txt
|
341 |
alert("success", "新規保存しました");
|
342 |
this.setState({showNewFileInput: false});
|
343 |
this.fetchDriveFiles(this.state.accessToken);
|
344 |
} catch (error) {
|
345 |
console.error("新規保存エラー:", error);
|
346 |
alert("error", "新規保存に失敗しました");
|
|
|
|
|
347 |
}
|
348 |
};
|
349 |
|
350 |
handleLoadFile = (project) => {
|
|
|
|
|
351 |
const PROXY_URL = "https://soiz1-drive-proxy.hf.space/?file_id=";
|
352 |
|
353 |
if (confirm(`"${project.name}"を読み込みますか?現在のプロジェクトは失われます。`)) {
|
@@ -357,7 +418,10 @@ class GoogleDriveSave extends React.Component {
|
|
357 |
};
|
358 |
|
359 |
handleReplaceFile = async (project) => {
|
|
|
|
|
360 |
if (confirm(`"${project.name}"を現在のプロジェクトで上書きしますか?`)) {
|
|
|
361 |
try {
|
362 |
await this.saveToGoogleDrive(project.id, project.name);
|
363 |
alert("success", "上書き保存しました");
|
@@ -365,17 +429,24 @@ class GoogleDriveSave extends React.Component {
|
|
365 |
} catch (error) {
|
366 |
console.error("ファイル上書きエラー:", error);
|
367 |
alert("error", "ファイルの上書きに失敗しました");
|
|
|
|
|
368 |
}
|
369 |
}
|
370 |
};
|
371 |
|
372 |
handleShareFile = (fileId) => {
|
|
|
|
|
373 |
const SHARE_URL = "https://scratch-school.ct.ws/upload?id=";
|
374 |
window.open(`${SHARE_URL}${fileId}`, "_blank");
|
375 |
};
|
376 |
|
377 |
handleDeleteFile = async (project, thumbnailFiles) => {
|
|
|
|
|
378 |
if (confirm(`"${project.name}"とそのサムネイルを完全に削除しますか?この操作は元に戻せません。`)) {
|
|
|
379 |
try {
|
380 |
await this.deleteFile(project.id);
|
381 |
|
@@ -392,11 +463,15 @@ class GoogleDriveSave extends React.Component {
|
|
392 |
} catch (error) {
|
393 |
console.error("削除エラー:", error);
|
394 |
alert("error", "ファイルの削除に失敗しました");
|
|
|
|
|
395 |
}
|
396 |
}
|
397 |
};
|
398 |
|
399 |
copyToClipboard = (text) => {
|
|
|
|
|
400 |
navigator.clipboard.writeText(text)
|
401 |
.then(() => alert("success", "リンクをクリップボードにコピーしました"))
|
402 |
.catch(() => alert("error", "リンクのコピーに失敗しました"));
|
@@ -415,7 +490,7 @@ class GoogleDriveSave extends React.Component {
|
|
415 |
}
|
416 |
}
|
417 |
|
418 |
-
async saveToGoogleDrive(fileId, fileName) {
|
419 |
const blob = await window.vm.saveProjectSb3();
|
420 |
|
421 |
const metadata = {
|
@@ -496,7 +571,7 @@ class GoogleDriveSave extends React.Component {
|
|
496 |
"Content-Type": "application/json",
|
497 |
},
|
498 |
body: JSON.stringify({
|
499 |
-
role:
|
500 |
type: "anyone",
|
501 |
}),
|
502 |
});
|
@@ -521,4 +596,4 @@ const mapStateToProps = state => ({
|
|
521 |
projectTitle: state.scratchGui.projectTitle
|
522 |
});
|
523 |
|
524 |
-
export default connect(mapStateToProps)(GoogleDriveSave);
|
|
|
16 |
files: [],
|
17 |
isModalOpen: false,
|
18 |
isLoading: false,
|
19 |
+
isProcessing: false,
|
20 |
newFileName: this.props.projectTitle || '無題',
|
21 |
+
showNewFileInput: false,
|
22 |
+
sharePermission: 'reader', // 'reader', 'writer', or 'owner'
|
23 |
+
selectedFileId: null
|
24 |
};
|
25 |
this.modalContentRef = React.createRef();
|
26 |
}
|
|
|
34 |
};
|
35 |
|
36 |
handleCloseModal = () => {
|
37 |
+
if (!this.state.isProcessing) {
|
38 |
+
this.setState({isModalOpen: false, showNewFileInput: false});
|
39 |
+
}
|
40 |
};
|
41 |
|
42 |
handleOverlayClick = (e) => {
|
43 |
+
if (!this.state.isProcessing && this.modalContentRef.current && !this.modalContentRef.current.contains(e.target)) {
|
44 |
this.handleCloseModal();
|
45 |
}
|
46 |
};
|
47 |
|
48 |
startGoogleLogin = () => {
|
49 |
+
if (this.state.isProcessing) return;
|
50 |
+
|
51 |
localStorage.removeItem('googleDriveAccessToken');
|
52 |
localStorage.removeItem('googleDriveAccountEmail');
|
53 |
localStorage.removeItem('googleDriveAccountName');
|
|
|
110 |
<div className={styles.modalContent} ref={this.modalContentRef}>
|
111 |
<div className={styles.modalHeader}>
|
112 |
<h2>Googleドライブに保存</h2>
|
113 |
+
<button
|
114 |
+
onClick={this.handleCloseModal}
|
115 |
+
className={styles.closeButton}
|
116 |
+
disabled={this.state.isProcessing}
|
117 |
+
>
|
118 |
+
×
|
119 |
+
</button>
|
120 |
</div>
|
121 |
|
122 |
<div className={styles.modalBody}>
|
|
|
124 |
{this.state.accessToken && this.renderNewFileSection()}
|
125 |
{this.state.accessToken && this.renderFileList()}
|
126 |
</div>
|
127 |
+
|
128 |
+
{this.state.isProcessing && (
|
129 |
+
<div className={styles.processingOverlay}>
|
130 |
+
<div className={styles.spinner}></div>
|
131 |
+
<div>処理中...</div>
|
132 |
+
</div>
|
133 |
+
)}
|
134 |
</div>
|
135 |
</div>
|
136 |
);
|
|
|
146 |
<button
|
147 |
onClick={this.handleChangeAccount}
|
148 |
className={styles.changeAccountButton}
|
149 |
+
disabled={this.state.isProcessing}
|
150 |
>
|
151 |
アカウントを変更
|
152 |
</button>
|
|
|
160 |
<button
|
161 |
onClick={this.startGoogleLogin}
|
162 |
className={styles.loginButton}
|
163 |
+
disabled={this.state.isProcessing}
|
164 |
>
|
165 |
Googleでログイン
|
166 |
</button>
|
|
|
179 |
onChange={(e) => this.setState({newFileName: e.target.value})}
|
180 |
className={styles.newFileNameInput}
|
181 |
placeholder="ファイル名を入力"
|
182 |
+
disabled={this.state.isProcessing}
|
183 |
/>
|
184 |
+
<div className={styles.permissionDropdown}>
|
185 |
+
<label>公開設定: </label>
|
186 |
+
<select
|
187 |
+
value={this.state.sharePermission}
|
188 |
+
onChange={(e) => this.setState({sharePermission: e.target.value})}
|
189 |
+
disabled={this.state.isProcessing}
|
190 |
+
>
|
191 |
+
<option value="reader">閲覧のみ</option>
|
192 |
+
<option value="writer">編集可能</option>
|
193 |
+
<option value="owner">所有者</option>
|
194 |
+
</select>
|
195 |
+
</div>
|
196 |
<button
|
197 |
onClick={this.handleNewFileSave}
|
198 |
className={styles.newFileSaveButton}
|
199 |
+
disabled={!this.state.newFileName.trim() || this.state.isProcessing}
|
200 |
>
|
201 |
保存
|
202 |
</button>
|
203 |
<button
|
204 |
onClick={() => this.setState({showNewFileInput: false})}
|
205 |
className={styles.newFileCancelButton}
|
206 |
+
disabled={this.state.isProcessing}
|
207 |
>
|
208 |
キャンセル
|
209 |
</button>
|
|
|
215 |
return (
|
216 |
<div className={styles.newFileSection}>
|
217 |
<button
|
218 |
+
onClick={() => this.setState({
|
219 |
+
showNewFileInput: true,
|
220 |
+
newFileName: window.vm.runtime.projectName || '無題',
|
221 |
+
sharePermission: 'reader'
|
222 |
+
})}
|
223 |
className={styles.newFileButton}
|
224 |
+
disabled={this.state.isProcessing}
|
225 |
>
|
226 |
新規保存
|
227 |
</button>
|
|
|
285 |
<button
|
286 |
onClick={() => this.handleLoadFile(project)}
|
287 |
className={styles.actionButton}
|
288 |
+
disabled={this.state.isProcessing}
|
289 |
>
|
290 |
読み込む
|
291 |
</button>
|
292 |
<button
|
293 |
onClick={() => this.handleReplaceFile(project)}
|
294 |
className={styles.actionButton}
|
295 |
+
disabled={this.state.isProcessing}
|
296 |
>
|
297 |
上書き
|
298 |
</button>
|
299 |
<button
|
300 |
onClick={() => this.handleShareFile(project.id)}
|
301 |
className={classNames(styles.actionButton, styles.shareButton)}
|
302 |
+
disabled={this.state.isProcessing}
|
303 |
>
|
304 |
共有
|
305 |
</button>
|
306 |
<button
|
307 |
onClick={() => this.handleDeleteFile(project, thumbnailFiles)}
|
308 |
className={classNames(styles.actionButton, styles.deleteButton)}
|
309 |
+
disabled={this.state.isProcessing}
|
310 |
>
|
311 |
削除
|
312 |
</button>
|
|
|
325 |
<button
|
326 |
onClick={() => this.copyToClipboard(`${SHORT_URL}${fileId}`)}
|
327 |
className={styles.copyButton}
|
328 |
+
disabled={this.state.isProcessing}
|
329 |
>
|
330 |
リンクをコピー
|
331 |
</button>
|
332 |
+
<button
|
333 |
+
onClick={() => window.open(`https://scratch-school.ct.ws/bit.php?id=${fileId}`)}
|
334 |
+
className={styles.copyButton}
|
335 |
+
disabled={this.state.isProcessing}
|
336 |
+
>
|
337 |
+
リンクを短縮
|
338 |
+
</button>
|
339 |
<button
|
340 |
onClick={() => this.copyToClipboard(fileId)}
|
341 |
className={styles.copyButton}
|
342 |
+
disabled={this.state.isProcessing}
|
343 |
>
|
344 |
IDのみコピー
|
345 |
</button>
|
|
|
379 |
}
|
380 |
|
381 |
handleChangeAccount = () => {
|
382 |
+
if (this.state.isProcessing) return;
|
383 |
+
|
384 |
this.setState({
|
385 |
accessToken: null,
|
386 |
currentAccountEmail: null,
|
|
|
392 |
};
|
393 |
|
394 |
handleNewFileSave = async () => {
|
395 |
+
this.setState({isProcessing: true});
|
396 |
try {
|
397 |
+
await this.saveToGoogleDrive(null, `${this.state.newFileName}.s4s.txt`, this.state.sharePermission);
|
398 |
alert("success", "新規保存しました");
|
399 |
this.setState({showNewFileInput: false});
|
400 |
this.fetchDriveFiles(this.state.accessToken);
|
401 |
} catch (error) {
|
402 |
console.error("新規保存エラー:", error);
|
403 |
alert("error", "新規保存に失敗しました");
|
404 |
+
} finally {
|
405 |
+
this.setState({isProcessing: false});
|
406 |
}
|
407 |
};
|
408 |
|
409 |
handleLoadFile = (project) => {
|
410 |
+
if (this.state.isProcessing) return;
|
411 |
+
|
412 |
const PROXY_URL = "https://soiz1-drive-proxy.hf.space/?file_id=";
|
413 |
|
414 |
if (confirm(`"${project.name}"を読み込みますか?現在のプロジェクトは失われます。`)) {
|
|
|
418 |
};
|
419 |
|
420 |
handleReplaceFile = async (project) => {
|
421 |
+
if (this.state.isProcessing) return;
|
422 |
+
|
423 |
if (confirm(`"${project.name}"を現在のプロジェクトで上書きしますか?`)) {
|
424 |
+
this.setState({isProcessing: true});
|
425 |
try {
|
426 |
await this.saveToGoogleDrive(project.id, project.name);
|
427 |
alert("success", "上書き保存しました");
|
|
|
429 |
} catch (error) {
|
430 |
console.error("ファイル上書きエラー:", error);
|
431 |
alert("error", "ファイルの上書きに失敗しました");
|
432 |
+
} finally {
|
433 |
+
this.setState({isProcessing: false});
|
434 |
}
|
435 |
}
|
436 |
};
|
437 |
|
438 |
handleShareFile = (fileId) => {
|
439 |
+
if (this.state.isProcessing) return;
|
440 |
+
|
441 |
const SHARE_URL = "https://scratch-school.ct.ws/upload?id=";
|
442 |
window.open(`${SHARE_URL}${fileId}`, "_blank");
|
443 |
};
|
444 |
|
445 |
handleDeleteFile = async (project, thumbnailFiles) => {
|
446 |
+
if (this.state.isProcessing) return;
|
447 |
+
|
448 |
if (confirm(`"${project.name}"とそのサムネイルを完全に削除しますか?この操作は元に戻せません。`)) {
|
449 |
+
this.setState({isProcessing: true});
|
450 |
try {
|
451 |
await this.deleteFile(project.id);
|
452 |
|
|
|
463 |
} catch (error) {
|
464 |
console.error("削除エラー:", error);
|
465 |
alert("error", "ファイルの削除に失敗しました");
|
466 |
+
} finally {
|
467 |
+
this.setState({isProcessing: false});
|
468 |
}
|
469 |
}
|
470 |
};
|
471 |
|
472 |
copyToClipboard = (text) => {
|
473 |
+
if (this.state.isProcessing) return;
|
474 |
+
|
475 |
navigator.clipboard.writeText(text)
|
476 |
.then(() => alert("success", "リンクをクリップボードにコピーしました"))
|
477 |
.catch(() => alert("error", "リンクのコピーに失敗しました"));
|
|
|
490 |
}
|
491 |
}
|
492 |
|
493 |
+
async saveToGoogleDrive(fileId, fileName, permission = 'reader') {
|
494 |
const blob = await window.vm.saveProjectSb3();
|
495 |
|
496 |
const metadata = {
|
|
|
571 |
"Content-Type": "application/json",
|
572 |
},
|
573 |
body: JSON.stringify({
|
574 |
+
role: permission, // ここで公開設定を使用
|
575 |
type: "anyone",
|
576 |
}),
|
577 |
});
|
|
|
596 |
projectTitle: state.scratchGui.projectTitle
|
597 |
});
|
598 |
|
599 |
+
export default connect(mapStateToProps)(GoogleDriveSave);
|