soiz1 commited on
Commit
6c27beb
·
verified ·
1 Parent(s): 6611d2c

Update src/components/menu-bar/google-drive-save.jsx

Browse files
src/components/menu-bar/google-drive-save.jsx CHANGED
@@ -1,4 +1,3 @@
1
-
2
  import React from 'react';
3
  import PropTypes from 'prop-types';
4
  import classNames from 'classnames';
@@ -15,7 +14,9 @@ class GoogleDriveSave extends React.Component {
15
  currentAccountName: localStorage.getItem('googleDriveAccountName') || null,
16
  files: [],
17
  isModalOpen: false,
18
- isLoading: false
 
 
19
  };
20
  this.modalContentRef = React.createRef();
21
  }
@@ -29,47 +30,47 @@ class GoogleDriveSave extends React.Component {
29
  };
30
 
31
  handleCloseModal = () => {
32
- this.setState({isModalOpen: false});
33
  };
 
34
  handleOverlayClick = (e) => {
35
- // モーダルコンテンツ内をクリックした場合は閉じない
36
  if (this.modalContentRef.current && !this.modalContentRef.current.contains(e.target)) {
37
  this.handleCloseModal();
38
  }
39
  };
40
- startGoogleLogin = () => {
41
- // 既存のトークン情報をクリア
42
- localStorage.removeItem('googleDriveAccessToken');
43
- localStorage.removeItem('googleDriveAccountEmail');
44
- localStorage.removeItem('googleDriveAccountName');
45
-
46
- const CLIENT_ID = "169451419993-v1b3s315s8dkui950j2nm15hetr5i0qk.apps.googleusercontent.com";
47
- const REDIRECT_URI = "https://soiz1-s4s-upload.hf.space/close2";
48
- const SCOPES = "https://www.googleapis.com/auth/drive.file";
49
-
50
- const messageListener = (event) => {
51
- if (event.data.token) {
52
- window.removeEventListener("message", messageListener);
53
- this.setState({
54
- accessToken: event.data.token,
55
- currentAccountEmail: event.data.email || null,
56
- currentAccountName: event.data.name || null,
57
- isModalOpen: true
58
- });
59
-
60
- this.fetchDriveFiles(event.data.token);
61
- }
 
 
 
 
 
 
 
 
 
 
62
  };
63
- window.addEventListener("message", messageListener);
64
-
65
- const authUrl = `https://accounts.google.com/o/oauth2/auth?` +
66
- `client_id=${CLIENT_ID}` +
67
- `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
68
- `&response_type=token` +
69
- `&scope=${encodeURIComponent(SCOPES)}`;
70
-
71
- window.open(authUrl, "_blank", "width=500,height=600");
72
- };
73
 
74
  fetchDriveFiles = async (accessToken) => {
75
  this.setState({isLoading: true});
@@ -98,7 +99,7 @@ startGoogleLogin = () => {
98
 
99
  return (
100
  <div className={styles.modalOverlay} onClick={this.handleOverlayClick}>
101
- <div className={styles.modalContent}>
102
  <div className={styles.modalHeader}>
103
  <h2>Googleドライブに保存</h2>
104
  <button onClick={this.handleCloseModal} className={styles.closeButton}>×</button>
@@ -106,6 +107,7 @@ startGoogleLogin = () => {
106
 
107
  <div className={styles.modalBody}>
108
  {this.renderAuthSection()}
 
109
  {this.state.accessToken && this.renderFileList()}
110
  </div>
111
  </div>
@@ -143,12 +145,53 @@ startGoogleLogin = () => {
143
  );
144
  }
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  renderFileList() {
147
  if (this.state.isLoading) {
148
  return <div className={styles.loading}>読み込み中...</div>;
149
  }
150
 
151
- // プロジェクトファイルとサムネイルを関連付ける
152
  const projectFiles = this.state.files.filter(file => file.mimeType === 'application/x-scratch');
153
  const thumbnailFiles = this.state.files.filter(file => file.mimeType === 'image/png');
154
 
@@ -159,13 +202,7 @@ startGoogleLogin = () => {
159
  return (
160
  <div className={styles.fileListContainer}>
161
  <div className={styles.fileListHeader}>
162
- <h3>プロジェクト: {this.renderProjectTitle()}</h3>
163
- <button
164
- onClick={this.handleNewFile}
165
- className={styles.newFileButton}
166
- >
167
- 新規保存
168
- </button>
169
  </div>
170
 
171
  <div className={styles.fileList}>
@@ -175,19 +212,6 @@ startGoogleLogin = () => {
175
  );
176
  }
177
 
178
- renderProjectTitle() {
179
- const projectName = window.vm.runtime.projectName || "無題";
180
-
181
- return (
182
- <span
183
- className={styles.projectTitle}
184
- onDoubleClick={this.handleEditProjectTitle}
185
- >
186
- {projectName}
187
- </span>
188
- );
189
- }
190
-
191
  renderFileItem(project, thumbnailFiles) {
192
  const thumbnail = thumbnailFiles.find(
193
  thumb => thumb.name === `Scratch-Thumbnail-${project.id}.png`
@@ -245,37 +269,37 @@ startGoogleLogin = () => {
245
  );
246
  }
247
 
248
- renderShareLink(fileId) {
249
- const SHORT_URL = "https://s4.rf.gd/";
250
-
251
- return (
252
- <div className={styles.linkContainer}>
253
- <div className={styles.linkHeader}>
254
- <span>共有リンク:</span>
255
- <button
256
- onClick={() => this.copyToClipboard(`${SHORT_URL}${fileId}`)}
257
- className={styles.copyButton}
258
- >
259
- リンクをコピー
260
- </button>
261
- <button
262
- onClick={() => this.copyToClipboard(fileId)}
263
- className={styles.copyButton}
 
 
 
 
 
 
 
 
 
264
  >
265
- IDのみコピー
266
- </button>
267
  </div>
268
- <a
269
- href={`${SHORT_URL}${fileId}`}
270
- target="_blank"
271
- rel="noopener noreferrer"
272
- className={styles.linkUrl}
273
- >
274
- {`${SHORT_URL}${fileId}`}
275
- </a>
276
- </div>
277
- );
278
- }
279
 
280
  render() {
281
  return (
@@ -299,7 +323,6 @@ renderShareLink(fileId) {
299
  );
300
  }
301
 
302
- // イベントハンドラメソッド
303
  handleChangeAccount = () => {
304
  this.setState({
305
  accessToken: null,
@@ -311,27 +334,18 @@ renderShareLink(fileId) {
311
  localStorage.removeItem('googleDriveAccountName');
312
  };
313
 
314
- handleEditProjectTitle = () => {
315
- const currentName = window.vm.runtime.projectName || "無題";
316
- const newName = prompt("プロジェクト名を入力してください", currentName);
317
-
318
- if (newName !== null) {
319
- window.vm.runtime.projectName = newName.trim() || "無題";
320
- this.forceUpdate();
 
 
321
  }
322
  };
323
 
324
- handleNewFile = async () => {
325
- try {
326
- await this.saveToGoogleDrive(null, null);
327
- alert("success", "新規保存しました");
328
- this.fetchDriveFiles(this.state.accessToken);
329
- } catch (error) {
330
- console.error("新規保存エラー:", error);
331
- alert("error", "新規保存に失敗しました");
332
- }
333
- };
334
-
335
  handleLoadFile = (project) => {
336
  const PROXY_URL = "https://soiz1-drive-proxy.hf.space/?file_id=";
337
 
@@ -362,10 +376,8 @@ handleNewFile = async () => {
362
  handleDeleteFile = async (project, thumbnailFiles) => {
363
  if (confirm(`"${project.name}"とそのサムネイルを完全に削除しますか?この操作は元に戻せません。`)) {
364
  try {
365
- // プロジェクトファイルを削除
366
  await this.deleteFile(project.id);
367
 
368
- // 対応するサムネイルを探して削除
369
  const thumbnailToDelete = thumbnailFiles.find(
370
  thumb => thumb.name === `Scratch-Thumbnail-${project.id}.png`
371
  );
@@ -389,7 +401,6 @@ handleNewFile = async () => {
389
  .catch(() => alert("error", "リンクのコピーに失敗しました"));
390
  };
391
 
392
- // API操作メソッド
393
  async deleteFile(fileId) {
394
  const response = await fetch(`https://www.googleapis.com/drive/v3/files/${fileId}`, {
395
  method: "DELETE",
@@ -404,13 +415,10 @@ handleNewFile = async () => {
404
  }
405
 
406
  async saveToGoogleDrive(fileId, fileName) {
407
- // プロジェクトを保存
408
  const blob = await window.vm.saveProjectSb3();
409
- const projectName = window.vm.runtime.projectName || "無題";
410
- const nameToUse = fileName || `${projectName}.s4s.txt`;
411
 
412
  const metadata = {
413
- name: nameToUse,
414
  mimeType: "application/x-scratch",
415
  };
416
 
@@ -438,7 +446,6 @@ handleNewFile = async () => {
438
 
439
  const fileData = await uploadResponse.json();
440
 
441
- // サムネイルを保存
442
  try {
443
  const thumbnailDataUrl = await this.getProjectThumbnail();
444
  const thumbnailBlob = await (await fetch(thumbnailDataUrl)).blob();
 
 
1
  import React from 'react';
2
  import PropTypes from 'prop-types';
3
  import classNames from 'classnames';
 
14
  currentAccountName: localStorage.getItem('googleDriveAccountName') || null,
15
  files: [],
16
  isModalOpen: false,
17
+ isLoading: false,
18
+ newFileName: window.vm.runtime.projectName || '無題',
19
+ showNewFileInput: false
20
  };
21
  this.modalContentRef = React.createRef();
22
  }
 
30
  };
31
 
32
  handleCloseModal = () => {
33
+ this.setState({isModalOpen: false, showNewFileInput: false});
34
  };
35
+
36
  handleOverlayClick = (e) => {
 
37
  if (this.modalContentRef.current && !this.modalContentRef.current.contains(e.target)) {
38
  this.handleCloseModal();
39
  }
40
  };
41
+
42
+ startGoogleLogin = () => {
43
+ localStorage.removeItem('googleDriveAccessToken');
44
+ localStorage.removeItem('googleDriveAccountEmail');
45
+ localStorage.removeItem('googleDriveAccountName');
46
+
47
+ const CLIENT_ID = "169451419993-v1b3s315s8dkui950j2nm15hetr5i0qk.apps.googleusercontent.com";
48
+ const REDIRECT_URI = "https://sc4sc-redirect-auth.hf.space/close2";
49
+ const SCOPES = "https://www.googleapis.com/auth/drive.file";
50
+
51
+ const messageListener = (event) => {
52
+ if (event.data.token) {
53
+ window.removeEventListener("message", messageListener);
54
+ this.setState({
55
+ accessToken: event.data.token,
56
+ currentAccountEmail: event.data.email || null,
57
+ currentAccountName: event.data.name || null,
58
+ isModalOpen: true
59
+ });
60
+
61
+ this.fetchDriveFiles(event.data.token);
62
+ }
63
+ };
64
+ window.addEventListener("message", messageListener);
65
+
66
+ const authUrl = `https://accounts.google.com/o/oauth2/auth?` +
67
+ `client_id=${CLIENT_ID}` +
68
+ `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
69
+ `&response_type=token` +
70
+ `&scope=${encodeURIComponent(SCOPES)}`;
71
+
72
+ window.open(authUrl, "_blank", "width=500,height=600");
73
  };
 
 
 
 
 
 
 
 
 
 
74
 
75
  fetchDriveFiles = async (accessToken) => {
76
  this.setState({isLoading: true});
 
99
 
100
  return (
101
  <div className={styles.modalOverlay} onClick={this.handleOverlayClick}>
102
+ <div className={styles.modalContent} ref={this.modalContentRef}>
103
  <div className={styles.modalHeader}>
104
  <h2>Googleドライブに保存</h2>
105
  <button onClick={this.handleCloseModal} className={styles.closeButton}>×</button>
 
107
 
108
  <div className={styles.modalBody}>
109
  {this.renderAuthSection()}
110
+ {this.state.accessToken && this.renderNewFileSection()}
111
  {this.state.accessToken && this.renderFileList()}
112
  </div>
113
  </div>
 
145
  );
146
  }
147
 
148
+ renderNewFileSection() {
149
+ if (this.state.showNewFileInput) {
150
+ return (
151
+ <div className={styles.newFileSection}>
152
+ <div className={styles.newFileInputGroup}>
153
+ <input
154
+ type="text"
155
+ value={this.state.newFileName}
156
+ onChange={(e) => this.setState({newFileName: e.target.value})}
157
+ className={styles.newFileNameInput}
158
+ placeholder="ファイル名を入力"
159
+ />
160
+ <button
161
+ onClick={this.handleNewFileSave}
162
+ className={styles.newFileSaveButton}
163
+ disabled={!this.state.newFileName.trim()}
164
+ >
165
+ 保存
166
+ </button>
167
+ <button
168
+ onClick={() => this.setState({showNewFileInput: false})}
169
+ className={styles.newFileCancelButton}
170
+ >
171
+ キャンセル
172
+ </button>
173
+ </div>
174
+ </div>
175
+ );
176
+ }
177
+
178
+ return (
179
+ <div className={styles.newFileSection}>
180
+ <button
181
+ onClick={() => this.setState({showNewFileInput: true, newFileName: window.vm.runtime.projectName || '無題'})}
182
+ className={styles.newFileButton}
183
+ >
184
+ 新規保存
185
+ </button>
186
+ </div>
187
+ );
188
+ }
189
+
190
  renderFileList() {
191
  if (this.state.isLoading) {
192
  return <div className={styles.loading}>読み込み中...</div>;
193
  }
194
 
 
195
  const projectFiles = this.state.files.filter(file => file.mimeType === 'application/x-scratch');
196
  const thumbnailFiles = this.state.files.filter(file => file.mimeType === 'image/png');
197
 
 
202
  return (
203
  <div className={styles.fileListContainer}>
204
  <div className={styles.fileListHeader}>
205
+ <h3>保存済みプロジェクト</h3>
 
 
 
 
 
 
206
  </div>
207
 
208
  <div className={styles.fileList}>
 
212
  );
213
  }
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  renderFileItem(project, thumbnailFiles) {
216
  const thumbnail = thumbnailFiles.find(
217
  thumb => thumb.name === `Scratch-Thumbnail-${project.id}.png`
 
269
  );
270
  }
271
 
272
+ renderShareLink(fileId) {
273
+ const SHORT_URL = "https://s4.rf.gd/";
274
+
275
+ return (
276
+ <div className={styles.linkContainer}>
277
+ <div className={styles.linkHeader}>
278
+ <span>共有リンク:</span>
279
+ <button
280
+ onClick={() => this.copyToClipboard(`${SHORT_URL}${fileId}`)}
281
+ className={styles.copyButton}
282
+ >
283
+ リンクをコピー
284
+ </button>
285
+ <button
286
+ onClick={() => this.copyToClipboard(fileId)}
287
+ className={styles.copyButton}
288
+ >
289
+ IDのみコピー
290
+ </button>
291
+ </div>
292
+ <a
293
+ href={`${SHORT_URL}${fileId}`}
294
+ target="_blank"
295
+ rel="noopener noreferrer"
296
+ className={styles.linkUrl}
297
  >
298
+ {`${SHORT_URL}${fileId}`}
299
+ </a>
300
  </div>
301
+ );
302
+ }
 
 
 
 
 
 
 
 
 
303
 
304
  render() {
305
  return (
 
323
  );
324
  }
325
 
 
326
  handleChangeAccount = () => {
327
  this.setState({
328
  accessToken: null,
 
334
  localStorage.removeItem('googleDriveAccountName');
335
  };
336
 
337
+ handleNewFileSave = async () => {
338
+ try {
339
+ await this.saveToGoogleDrive(null, `${this.state.newFileName}.s4s.txt`);
340
+ alert("success", "新規保存しました");
341
+ this.setState({showNewFileInput: false});
342
+ this.fetchDriveFiles(this.state.accessToken);
343
+ } catch (error) {
344
+ console.error("新規保存エラー:", error);
345
+ alert("error", "新規保存に失敗しました");
346
  }
347
  };
348
 
 
 
 
 
 
 
 
 
 
 
 
349
  handleLoadFile = (project) => {
350
  const PROXY_URL = "https://soiz1-drive-proxy.hf.space/?file_id=";
351
 
 
376
  handleDeleteFile = async (project, thumbnailFiles) => {
377
  if (confirm(`"${project.name}"とそのサムネイルを完全に削除しますか?この操作は元に戻せません。`)) {
378
  try {
 
379
  await this.deleteFile(project.id);
380
 
 
381
  const thumbnailToDelete = thumbnailFiles.find(
382
  thumb => thumb.name === `Scratch-Thumbnail-${project.id}.png`
383
  );
 
401
  .catch(() => alert("error", "リンクのコピーに失敗しました"));
402
  };
403
 
 
404
  async deleteFile(fileId) {
405
  const response = await fetch(`https://www.googleapis.com/drive/v3/files/${fileId}`, {
406
  method: "DELETE",
 
415
  }
416
 
417
  async saveToGoogleDrive(fileId, fileName) {
 
418
  const blob = await window.vm.saveProjectSb3();
 
 
419
 
420
  const metadata = {
421
+ name: fileName,
422
  mimeType: "application/x-scratch",
423
  };
424
 
 
446
 
447
  const fileData = await uploadResponse.json();
448
 
 
449
  try {
450
  const thumbnailDataUrl = await this.getProjectThumbnail();
451
  const thumbnailBlob = await (await fetch(thumbnailDataUrl)).blob();