diff --git a/src/googledrive.ts b/src/googledrive.ts index 2128ae2f1..3846add3e 100644 --- a/src/googledrive.ts +++ b/src/googledrive.ts @@ -25,9 +25,9 @@ export const useGoogleLogIn = () => { const login = useGoogleLogin({ onSuccess (tokenResponse) { localStorage.hasEverLoggedIn = true - googleProviderData.accessToken = tokenResponse.access_token - googleProviderData.expiresIn = ref(new Date(Date.now() + tokenResponse.expires_in * 1000)) - googleProviderData.hasEverLoggedIn = true + googleProviderState.accessToken = tokenResponse.access_token + googleProviderState.expiresIn = ref(new Date(Date.now() + tokenResponse.expires_in * 1000)) + googleProviderState.hasEverLoggedIn = true }, // prompt: hasEverLoggedIn ? 'none' : 'consent', scope: SCOPES, @@ -35,12 +35,12 @@ export const useGoogleLogIn = () => { onError (error) { const accessDenied = error.error === 'access_denied' || error.error === 'invalid_scope' || (error as any).error_subtype === 'access_denied' if (accessDenied) { - googleProviderData.hasEverLoggedIn = false + googleProviderState.hasEverLoggedIn = false } } }) return () => login({ - prompt: googleProviderData.hasEverLoggedIn ? 'none' : 'consent' + prompt: googleProviderState.hasEverLoggedIn ? 'none' : 'consent' }) } @@ -61,11 +61,11 @@ export const possiblyHandleStateVariable = async () => { async callback (response) { if (response.error) { setLoadingScreenStatus('Error: ' + response.error, true) - googleProviderData.hasEverLoggedIn = false + googleProviderState.hasEverLoggedIn = false return } setLoadingScreenStatus('Opening world in read only mode...') - googleProviderData.accessToken = response.access_token + googleProviderState.accessToken = response.access_token await mountGoogleDriveFolder(true, parsed.ids[0]) await loadInMemorySave('/google') } @@ -73,14 +73,14 @@ export const possiblyHandleStateVariable = async () => { const choice = await showOptionsModal('Select an action...', ['Login']) if (choice === 'Login') { tokenClient.requestAccessToken({ - prompt: googleProviderData.hasEverLoggedIn ? '' : 'consent', + prompt: googleProviderState.hasEverLoggedIn ? '' : 'consent', }) } else { window.close() } } -export const googleProviderData = proxy({ +export const googleProviderState = proxy({ accessToken: (localStorage.saveAccessToken ? localStorage.accessToken : null) as string | null, hasEverLoggedIn: !!(localStorage.hasEverLoggedIn), isReady: false, @@ -92,18 +92,18 @@ export const googleProviderData = proxy({ } | null }) -subscribe(googleProviderData, () => { - localStorage.googleReadonlyMode = googleProviderData.readonlyMode - localStorage.lastSelectedFolder = googleProviderData.lastSelectedFolder ? JSON.stringify(googleProviderData.lastSelectedFolder) : null - if (googleProviderData.hasEverLoggedIn) { +subscribe(googleProviderState, () => { + localStorage.googleReadonlyMode = googleProviderState.readonlyMode + localStorage.lastSelectedFolder = googleProviderState.lastSelectedFolder ? JSON.stringify(googleProviderState.lastSelectedFolder) : null + if (googleProviderState.hasEverLoggedIn) { localStorage.hasEverLoggedIn = true } else { delete localStorage.hasEverLoggedIn } - if (localStorage.saveAccessToken && googleProviderData) { + if (localStorage.saveAccessToken && googleProviderState) { // For testing only - localStorage.accessToken = googleProviderData.accessToken || null + localStorage.accessToken = googleProviderState.accessToken || null } else { delete localStorage.accessToken } diff --git a/src/react/SingleplayerProvider.tsx b/src/react/SingleplayerProvider.tsx index 32d175f43..8eb3af0f6 100644 --- a/src/react/SingleplayerProvider.tsx +++ b/src/react/SingleplayerProvider.tsx @@ -7,7 +7,7 @@ import { googleDriveGetFileIdFromPath, mountExportFolder, mountGoogleDriveFolder import { hideCurrentModal, showModal } from '../globalState' import { haveDirectoryPicker, setLoadingScreenStatus } from '../utils' import { exportWorld } from '../builtinCommands' -import { googleProviderData, useGoogleLogIn, GoogleDriveProvider, isGoogleDriveAvailable, APP_ID } from '../googledrive' +import { googleProviderState, useGoogleLogIn, GoogleDriveProvider, isGoogleDriveAvailable, APP_ID } from '../googledrive' import Singleplayer, { WorldProps } from './Singleplayer' import { useIsModalActive } from './utils' import { showOptionsModal } from './SelectOption' @@ -18,6 +18,7 @@ const worldsProxy = proxy({ value: null as null | WorldProps[], brokenWorlds: [] as string[], selectedProvider: 'local' as 'local' | 'google', + selectedGoogleId: '', error: '', }) @@ -48,7 +49,7 @@ export const readWorlds = (abortController: AbortController) => { (async () => { const brokenWorlds = [] as string[] try { - const loggedIn = !!googleProviderData.accessToken + const loggedIn = !!googleProviderState.accessToken worldsProxy.value = null if (worldsProxy.selectedProvider === 'google' && !loggedIn) { worldsProxy.value = [] @@ -135,7 +136,7 @@ export const loadGoogleDriveApi = async () => { gapi.load('client', () => { gapi.load('client:picker', () => { void gapi.client.load('https://www.googleapis.com/discovery/v1/apis/drive/v3/rest').then(() => { - googleProviderData.isReady = true + googleProviderState.isReady = true resolve() }) }) @@ -145,24 +146,17 @@ export const loadGoogleDriveApi = async () => { const Inner = () => { const worlds = useSnapshot(worldsProxy).value as WorldProps[] | null - const { selectedProvider, error, brokenWorlds } = useSnapshot(worldsProxy) + const { selectedProvider, error, brokenWorlds, selectedGoogleId } = useSnapshot(worldsProxy) const readWorldsAbortController = useRef(new AbortController()) - useEffect(() => { - return () => { - worldsProxy.selectedProvider = 'local' - } - }, []) - // 3rd party providers useEffect(() => { if (selectedProvider !== 'google') return void loadGoogleDriveApi() }, [selectedProvider]) - const [selectedGoogleId, setSelectedGoogleId] = useState('') - const loggedIn = !!useSnapshot(googleProviderData).accessToken - const googleDriveReadonly = useSnapshot(googleProviderData).readonlyMode + const loggedIn = !!useSnapshot(googleProviderState).accessToken + const googleDriveReadonly = useSnapshot(googleProviderState).readonlyMode useEffect(() => { (async () => { @@ -171,7 +165,7 @@ const Inner = () => { worldsProxy.value = [] return } - await mountGoogleDriveFolder(googleProviderData.readonlyMode, selectedGoogleId) + await mountGoogleDriveFolder(googleProviderState.readonlyMode, selectedGoogleId) const exists = async (path) => { try { await fs.promises.stat(path) @@ -199,21 +193,74 @@ const Inner = () => { const googleLogIn = useGoogleLogIn() - const isGoogleProviderReady = useSnapshot(googleProviderData).isReady - const providerActions = selectedProvider === 'google' ? isGoogleProviderReady ? loggedIn ? { + const googlePicker = useRef/* */(null as any) + + useEffect(() => { + return () => { + googlePicker.current?.dispose() + } + }) + + const selectGoogleFolder = async () => { + if (googleProviderState.lastSelectedFolder) { + // ask to use saved previous fodler + const choice = await showOptionsModal(`Use previously selected folder "${googleProviderState.lastSelectedFolder.name}"?`, ['Yes', 'No']) + if (!choice) return + if (choice === 'Yes') { + worldsProxy.selectedGoogleId = googleProviderState.lastSelectedFolder.id + return + } + } + + const { google } = window + + const view = new google.picker.DocsView(google.picker.ViewId.FOLDERS) + .setIncludeFolders(true) + .setMimeTypes('application/vnd.google-apps.folder') + .setSelectFolderEnabled(true) + .setParent('root') + + + googlePicker.current = new google.picker.PickerBuilder() + .enableFeature(google.picker.Feature.NAV_HIDDEN) + .enableFeature(google.picker.Feature.MULTISELECT_ENABLED) + .setDeveloperKey('AIzaSyBTiHpEqaLL7mEcrsnSS4M-z8cpRH5UwY0') + .setAppId(APP_ID) + .setOAuthToken(googleProviderState.accessToken) + .addView(view) + .addView(new google.picker.DocsUploadView()) + .setTitle('Select a folder with your worlds') + .setCallback((data) => { + if (data.action === google.picker.Action.PICKED) { + googleProviderState.lastSelectedFolder = { + id: data.docs[0].id, + name: data.docs[0].name, + } + worldsProxy.selectedGoogleId = data.docs[0].id + } + }) + .build() + googlePicker.current.setVisible(true) + } + + const isGoogleProviderReady = useSnapshot(googleProviderState).isReady + const providerActions = loggedIn && selectedProvider === 'google' && isGoogleProviderReady && !selectedGoogleId ? { + 'Select Folder': selectGoogleFolder + } : selectedProvider === 'google' ? isGoogleProviderReady ? loggedIn ? { 'Log Out' () { - googleProviderData.hasEverLoggedIn = false - googleProviderData.accessToken = null - googleProviderData.lastSelectedFolder = null - window.google.accounts.oauth2.revoke(googleProviderData.accessToken) + googleProviderState.hasEverLoggedIn = false + googleProviderState.accessToken = null + googleProviderState.lastSelectedFolder = null + window.google.accounts.oauth2.revoke(googleProviderState.accessToken) }, async [`Read Only: ${googleDriveReadonly ? 'ON' : 'OFF'}`] () { - if (googleProviderData.readonlyMode) { + if (googleProviderState.readonlyMode) { const choice = await showOptionsModal('[Unstable Feature] Enabling world save might corrupt your worlds, eg remove entities (note: you can always restore previous version of files in Drive)', ['Continue']) if (choice !== 'Continue') return } - googleProviderData.readonlyMode = !googleProviderData.readonlyMode + googleProviderState.readonlyMode = !googleProviderState.readonlyMode }, + 'Select Folder': selectGoogleFolder, // 'Worlds Path': { // googleProviderData.worldsPath = e.target.value // }} /> @@ -224,48 +271,9 @@ const Inner = () => { } : undefined // end - useEffect(() => { - let picker - if (loggedIn && selectedProvider === 'google' && isGoogleProviderReady) { - const { google } = window - - const view = new google.picker.DocsView(google.picker.ViewId.FOLDERS) - .setIncludeFolders(true) - .setMimeTypes('application/vnd.google-apps.folder') - .setSelectFolderEnabled(true) - .setParent('root') - - - picker = new google.picker.PickerBuilder() - .enableFeature(google.picker.Feature.NAV_HIDDEN) - .enableFeature(google.picker.Feature.MULTISELECT_ENABLED) - .setDeveloperKey('AIzaSyBTiHpEqaLL7mEcrsnSS4M-z8cpRH5UwY0') - .setAppId(APP_ID) - .setOAuthToken(googleProviderData.accessToken) - .addView(view) - .addView(new google.picker.DocsUploadView()) - .setTitle('Select a folder with your worlds') - .setCallback((data) => { - if (data.action === google.picker.Action.PICKED) { - googleProviderData.lastSelectedFolder = { - id: data.docs[0].id, - name: data.docs[0].name, - } - setSelectedGoogleId(data.docs[0].id) - } - }) - .build() - picker.setVisible(true) - } - - return () => { - if (picker) picker.dispose() - } - }, [selectedProvider, loggedIn]) - return