Skip to content

Commit 0f976bc

Browse files
committed
feat(GoogleSheets): enhance user experience with improved access messages and integrate Google Drive file picker functionality
1 parent 304ac75 commit 0f976bc

File tree

3 files changed

+108
-101
lines changed

3 files changed

+108
-101
lines changed

src/components/newtab/workflow/edit/EditGoogleSheets.vue

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,15 @@
4848
rel="noopener"
4949
class="ml-1 inline-block text-sm leading-tight"
5050
>
51-
Automa doesn't have access to the spreadsheet
52-
<v-remixicon name="riInformationLine" size="18" class="inline" />
51+
Automa doesn't have access to the spreadsheet.
52+
<a
53+
href="https://docs.extension.automa.site/blocks/google-sheets.html#access-to-spreadsheet"
54+
target="_blank"
55+
rel="noopener"
56+
>
57+
Click here to read more.
58+
<v-remixicon name="riInformationLine" size="18" class="inline" />
59+
</a>
5360
</a>
5461
<edit-autocomplete v-if="!['create', 'add-sheet'].includes(data.type)">
5562
<ui-input

src/components/newtab/workflow/edit/EditGoogleSheetsDrive.vue

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@
6868
</edit-google-sheets>
6969
</template>
7070
<script setup>
71+
import { useStore } from '@/stores/main';
72+
import { openGDrivePickerPopup } from '@/utils/openGDriveFilePicker';
7173
import { useI18n } from 'vue-i18n';
7274
import { useToast } from 'vue-toastification';
73-
import { useStore } from '@/stores/main';
74-
import openGDriveFilePicker from '@/utils/openGDriveFilePicker';
75+
import browser from 'webextension-polyfill';
7576
import EditGoogleSheets from './EditGoogleSheets.vue';
7677
7778
const props = defineProps({
@@ -90,21 +91,53 @@ store.getConnectedSheets();
9091
function updateData(value) {
9192
emit('update:data', { ...props.data, ...value });
9293
}
93-
function connectSheet() {
94-
openGDriveFilePicker().then((result) => {
95-
if (!result) return;
96-
97-
const { name, id, mimeType } = result;
9894
99-
if (mimeType !== 'application/vnd.google-apps.spreadsheet') {
95+
async function connectSheet() {
96+
// 1. 获取当前 access_token
97+
const { sessionToken } = await browser.storage.local.get('sessionToken');
98+
if (!sessionToken?.access) {
99+
toast.error('未获取到 Google 授权');
100+
return;
101+
}
102+
try {
103+
// 2. 弹出 Picker 让用户选择文件
104+
const file = await openGDrivePickerPopup(sessionToken.access);
105+
if (!file) return;
106+
if (file.mimeType !== 'application/vnd.google-apps.spreadsheet') {
100107
toast.error('File is not a google spreadsheet');
101108
return;
102109
}
103-
104-
const sheetExists = store.connectedSheets.some((sheet) => sheet.id === id);
110+
const sheetExists = store.connectedSheets.some(
111+
(sheet) => sheet.id === file.id
112+
);
105113
if (sheetExists) return;
106-
107-
store.connectedSheets.push({ name, id });
108-
});
114+
// 3. 加入已连接列表
115+
store.connectedSheets.push({ name: file.name, id: file.id });
116+
} catch (e) {
117+
toast.error('未选择文件或授权失败');
118+
}
109119
}
120+
121+
// function connectSheet() {
122+
// openGDriveFilePicker().then((sheets) => {
123+
// if (!Array.isArray(sheets) || sheets.length === 0) {
124+
// toast.error('未获取到 Google Sheets 文件');
125+
// return;
126+
// }
127+
// // 弹窗/下拉选择,用户选择后加入 store.connectedSheets
128+
// // 这里用 window.prompt 简化,实际可用自定义弹窗组件
129+
// const options = sheets.map((s, i) => `${i + 1}. ${s.name}`).join('\n');
130+
// const idx = window.prompt(`请选择要连接的 Google Sheet:\n${options}`);
131+
// const index = Number(idx) - 1;
132+
// if (Number.isNaN(index) || !sheets[index]) return;
133+
// const { name, id, mimeType } = sheets[index];
134+
// if (mimeType !== 'application/vnd.google-apps.spreadsheet') {
135+
// toast.error('File is not a google spreadsheet');
136+
// return;
137+
// }
138+
// const sheetExists = store.connectedSheets.some((sheet) => sheet.id === id);
139+
// if (sheetExists) return;
140+
// store.connectedSheets.push({ name, id });
141+
// });
142+
// }
110143
</script>

src/utils/openGDriveFilePicker.js

Lines changed: 53 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,64 @@
1-
import secrets from 'secrets';
21
import browser from 'webextension-polyfill';
32

4-
function injectFilePicker() {
5-
return new Promise((resolve, reject) => {
6-
const scriptExist = document.querySelector('#google-api');
7-
if (scriptExist) {
8-
resolve();
9-
return;
10-
}
11-
12-
let gisLoaded = false;
13-
let pickerLoaded = false;
14-
15-
// Create a blob URL for the Google API script
16-
const scriptApi = document.createElement('script');
17-
scriptApi.onload = () => {
18-
window.gapi.load('picker', () => {
19-
pickerLoaded = true;
20-
});
21-
};
22-
scriptApi.id = 'google-api';
23-
scriptApi.src = chrome.runtime.getURL('lib/google-apis.js');
24-
25-
// Create a blob URL for the GSI client script
26-
const scriptGis = document.createElement('script');
27-
scriptGis.onload = () => {
28-
gisLoaded = true;
29-
};
30-
scriptGis.src = chrome.runtime.getURL('lib/google-accounts.gsi.client.js');
31-
32-
document.body.appendChild(scriptApi);
33-
document.body.appendChild(scriptGis);
34-
35-
let count = 0;
36-
const checkIfLoaded = () => {
37-
if (count > 10) {
38-
reject(new Error('Timeout'));
39-
return;
40-
}
41-
42-
if (gisLoaded && pickerLoaded) {
43-
resolve();
44-
return;
45-
}
46-
47-
count += 1;
48-
setTimeout(checkIfLoaded, 1500);
49-
};
50-
51-
checkIfLoaded();
52-
});
53-
}
54-
55-
function selectFile(accessToken) {
56-
return new Promise((resolve) => {
57-
const callback = (event) => {
58-
if (!event || event?.action !== 'picked') return;
59-
60-
const [doc] = event.docs;
61-
resolve(doc);
62-
};
63-
64-
const picker = new window.google.picker.PickerBuilder()
65-
.addView(
66-
new window.google.picker.DocsView(
67-
window.google.picker.ViewId.SPREADSHEETS
68-
).setMode(window.google.picker.DocsViewMode.LIST)
69-
)
70-
.setAppId(secrets.googleProjectId)
71-
.setOAuthToken(accessToken)
72-
.setDeveloperKey(secrets.googleApiKey)
73-
.setCallback(callback)
74-
.build();
75-
picker.setVisible(true);
76-
77-
window.gDrivePicker = picker;
78-
});
79-
}
80-
81-
export default async function () {
82-
await injectFilePicker();
83-
3+
/**
4+
*
5+
* get all google sheets files in current user's google drive
6+
* @returns {Promise<Array>} file list [{ id, name, mimeType }]
7+
*/
8+
export default async function fetchGDriveSheets() {
849
const { sessionToken, session } = await browser.storage.local.get([
8510
'sessionToken',
8611
'session',
8712
]);
88-
if (!sessionToken || !sessionToken.access) return null;
13+
if (!sessionToken || !sessionToken.access) return [];
8914

9015
const isGoogleProvider =
9116
session?.user?.user_metadata?.iss.includes('google.com');
92-
if (!isGoogleProvider) return null;
93-
94-
const result = await selectFile(sessionToken.access);
17+
if (!isGoogleProvider) return [];
18+
19+
const accessToken = sessionToken.access;
20+
const endpoint =
21+
'https://www.googleapis.com/drive/v3/files?fields=files(id%2Cname%2CmimeType)&q=mimeType%3D%27application%2Fvnd.google-apps.spreadsheet%27&spaces=drive&pageSize=1000';
22+
23+
try {
24+
const res = await fetch(endpoint, {
25+
headers: {
26+
Authorization: `Bearer ${accessToken}`,
27+
},
28+
});
29+
if (!res.ok) throw new Error('Failed to fetch Google Sheets list');
30+
const data = await res.json();
31+
return data.files || [];
32+
} catch (e) {
33+
// handle token expired or other exceptions
34+
return [];
35+
}
36+
}
9537

96-
return result;
38+
/**
39+
* open google picker popup to get user authorized file
40+
* @param {string} accessToken
41+
* @returns {Promise<{id, name, mimeType}>}
42+
*/
43+
export function openGDrivePickerPopup(accessToken) {
44+
return new Promise((resolve, reject) => {
45+
const pickerUrl = `https://extension.automa.site/picker?access_token=${accessToken}`;
46+
const popup = window.open(pickerUrl, '_blank', 'width=600,height=600');
47+
function handleMessage(event) {
48+
if (!event.origin.startsWith('https://extension.automa.site')) return;
49+
if (event.data && event.data.type === 'GDRIVE_PICKER_RESULT') {
50+
window.removeEventListener('message', handleMessage);
51+
popup.close();
52+
resolve(event.data.file);
53+
}
54+
}
55+
window.addEventListener('message', handleMessage);
56+
const timer = setInterval(() => {
57+
if (popup.closed) {
58+
clearInterval(timer);
59+
window.removeEventListener('message', handleMessage);
60+
reject(new Error('Picker window closed'));
61+
}
62+
}, 500);
63+
});
9764
}

0 commit comments

Comments
 (0)