soiz1 commited on
Commit
fcfbb0b
·
verified ·
1 Parent(s): e78df4b

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.setState({isModalOpen: false, showNewFileInput: false});
 
 
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 onClick={this.handleCloseModal} className={styles.closeButton}>×</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({showNewFileInput: true, newFileName: window.vm.runtime.projectName || '無題'})}
 
 
 
 
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: "reader",
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);