From 491b9603af4bc9a47fe35a28a357e2485ee0f05b Mon Sep 17 00:00:00 2001 From: ZXY101 Date: Wed, 13 Mar 2024 09:35:44 +0200 Subject: [PATCH 1/4] Add google drive support --- package-lock.json | 123 ++++++++++++++++++++++-- package.json | 3 + src/app.html | 16 +++- src/lib/components/NavBar.svelte | 5 +- src/lib/upload/index.ts | 1 - src/routes/cloud/+page.svelte | 156 +++++++++++++++++++++++++++++++ 6 files changed, 291 insertions(+), 13 deletions(-) create mode 100644 src/routes/cloud/+page.svelte diff --git a/package-lock.json b/package-lock.json index ec892b3..0b38b69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "z-reader", "version": "0.9.0", "dependencies": { + "@types/gapi": "^0.0.47", + "@types/google.accounts": "^0.0.14", "@vercel/analytics": "^1.1.0", "@zip.js/zip.js": "^2.7.20", "dexie": "^4.0.1-alpha.25", @@ -17,6 +19,7 @@ "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/kit": "^1.20.4", + "@types/gapi.client.drive-v3": "^0.0.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "autoprefixer": "^10.4.14", @@ -601,6 +604,26 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@maxim_mazurok/gapi.client.discovery-v1": { + "version": "0.1.20200806", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.1.20200806.tgz", + "integrity": "sha512-Wl6UfmZVDdWbY3PUu8E2ULk9RPLjnMqp/iOA4tcK8Ne+U/GmlnWP/e34IaZNGArfl7iXJNOG+/3Rj9L9jQyF9Q==", + "dev": true, + "dependencies": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, + "node_modules/@maxim_mazurok/gapi.client.drive-v3": { + "version": "0.0.20240304", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.drive-v3/-/gapi.client.drive-v3-0.0.20240304.tgz", + "integrity": "sha512-PuGvDvRE70XsyUqnQCq5M2VJ7/E7u5pDvHZfBXunVlf5mYmkyAdbZg+ygJDJ2243wWpUXjmEcuxLDY2yamIq4g==", + "dev": true, + "dependencies": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -747,6 +770,40 @@ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, + "node_modules/@types/gapi": { + "version": "0.0.47", + "resolved": "https://registry.npmjs.org/@types/gapi/-/gapi-0.0.47.tgz", + "integrity": "sha512-/ZsLuq6BffMgbKMtZyDZ8vwQvTyKhKQ1G2K6VyWCgtHHhfSSXbk4+4JwImZiTjWNXfI2q1ZStAwFFHSkNoTkHA==" + }, + "node_modules/@types/gapi.client": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/gapi.client/-/gapi.client-1.0.8.tgz", + "integrity": "sha512-qJQUmmumbYym3Amax0S8CVzuSngcXsC1fJdwRS2zeW5lM63zXkw4wJFP+bG0jzgi0R6EsJKoHnGNVTDbOyG1ng==", + "dev": true + }, + "node_modules/@types/gapi.client.discovery-v1": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.0.4.tgz", + "integrity": "sha512-uevhRumNE65F5mf2gABLaReOmbFSXONuzFZjNR3dYv6BmkHg+wciubHrfBAsp3554zNo3Dcg6dUAlwMqQfpwjQ==", + "dev": true, + "dependencies": { + "@maxim_mazurok/gapi.client.discovery-v1": "latest" + } + }, + "node_modules/@types/gapi.client.drive-v3": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/gapi.client.drive-v3/-/gapi.client.drive-v3-0.0.4.tgz", + "integrity": "sha512-jE37dJ0EzAdY0aJPFOp20xmec/aO0P4HtUIA9k07RMPyedFDOcuMlSac1r0PklwQdgXF7BHaMoObNHNAnwSQUQ==", + "dev": true, + "dependencies": { + "@maxim_mazurok/gapi.client.drive-v3": "latest" + } + }, + "node_modules/@types/google.accounts": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@types/google.accounts/-/google.accounts-0.0.14.tgz", + "integrity": "sha512-HqIVkVzpiLWhlajhQQd4rIV7czanFvXblJI2J1fSrL+VKQuQwwZ63m35D/mI0flsqKE6p/hNrAG0Yn4FD6JvNA==" + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", @@ -1257,9 +1314,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001525", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz", - "integrity": "sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q==", + "version": "1.0.30001597", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz", + "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==", "dev": true, "funding": [ { @@ -4349,6 +4406,26 @@ } } }, + "@maxim_mazurok/gapi.client.discovery-v1": { + "version": "0.1.20200806", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.1.20200806.tgz", + "integrity": "sha512-Wl6UfmZVDdWbY3PUu8E2ULk9RPLjnMqp/iOA4tcK8Ne+U/GmlnWP/e34IaZNGArfl7iXJNOG+/3Rj9L9jQyF9Q==", + "dev": true, + "requires": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, + "@maxim_mazurok/gapi.client.drive-v3": { + "version": "0.0.20240304", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.drive-v3/-/gapi.client.drive-v3-0.0.20240304.tgz", + "integrity": "sha512-PuGvDvRE70XsyUqnQCq5M2VJ7/E7u5pDvHZfBXunVlf5mYmkyAdbZg+ygJDJ2243wWpUXjmEcuxLDY2yamIq4g==", + "dev": true, + "requires": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4453,6 +4530,40 @@ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, + "@types/gapi": { + "version": "0.0.47", + "resolved": "https://registry.npmjs.org/@types/gapi/-/gapi-0.0.47.tgz", + "integrity": "sha512-/ZsLuq6BffMgbKMtZyDZ8vwQvTyKhKQ1G2K6VyWCgtHHhfSSXbk4+4JwImZiTjWNXfI2q1ZStAwFFHSkNoTkHA==" + }, + "@types/gapi.client": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/gapi.client/-/gapi.client-1.0.8.tgz", + "integrity": "sha512-qJQUmmumbYym3Amax0S8CVzuSngcXsC1fJdwRS2zeW5lM63zXkw4wJFP+bG0jzgi0R6EsJKoHnGNVTDbOyG1ng==", + "dev": true + }, + "@types/gapi.client.discovery-v1": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.0.4.tgz", + "integrity": "sha512-uevhRumNE65F5mf2gABLaReOmbFSXONuzFZjNR3dYv6BmkHg+wciubHrfBAsp3554zNo3Dcg6dUAlwMqQfpwjQ==", + "dev": true, + "requires": { + "@maxim_mazurok/gapi.client.discovery-v1": "latest" + } + }, + "@types/gapi.client.drive-v3": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/gapi.client.drive-v3/-/gapi.client.drive-v3-0.0.4.tgz", + "integrity": "sha512-jE37dJ0EzAdY0aJPFOp20xmec/aO0P4HtUIA9k07RMPyedFDOcuMlSac1r0PklwQdgXF7BHaMoObNHNAnwSQUQ==", + "dev": true, + "requires": { + "@maxim_mazurok/gapi.client.drive-v3": "latest" + } + }, + "@types/google.accounts": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@types/google.accounts/-/google.accounts-0.0.14.tgz", + "integrity": "sha512-HqIVkVzpiLWhlajhQQd4rIV7czanFvXblJI2J1fSrL+VKQuQwwZ63m35D/mI0flsqKE6p/hNrAG0Yn4FD6JvNA==" + }, "@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", @@ -4785,9 +4896,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001525", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz", - "integrity": "sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q==", + "version": "1.0.30001597", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz", + "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==", "dev": true }, "chalk": { diff --git a/package.json b/package.json index f54737a..81fbc9a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/kit": "^1.20.4", + "@types/gapi.client.drive-v3": "^0.0.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "autoprefixer": "^10.4.14", @@ -36,6 +37,8 @@ }, "type": "module", "dependencies": { + "@types/gapi": "^0.0.47", + "@types/google.accounts": "^0.0.14", "@vercel/analytics": "^1.1.0", "@zip.js/zip.js": "^2.7.20", "dexie": "^4.0.1-alpha.25", diff --git a/src/app.html b/src/app.html index 38dd61c..72867e8 100644 --- a/src/app.html +++ b/src/app.html @@ -3,17 +3,25 @@ - + + + %sveltekit.head%
%sveltekit.body%
+ id="popupAbout" + class="pageContainer" + style=" + display: contents; + background-image: url('https://reader.mokuro.app/_app/immutable/assets/icon.06fcfdd6.webp'); + " + > + %sveltekit.body% + diff --git a/src/lib/components/NavBar.svelte b/src/lib/components/NavBar.svelte index 6e71f74..c6a7262 100644 --- a/src/lib/components/NavBar.svelte +++ b/src/lib/components/NavBar.svelte @@ -1,7 +1,7 @@ + +
+ {#if loadingMessage} + + {loadingMessage} + + {:else if zips} +
+

Google drive:

+
+ {#each zips as zip} + onClick(zip)}> +

{zip.name}

+

{formatBytes(parseInt(zip.size || '0'))}

+
+ {/each} +
+
+ {:else} + + {/if} +
From cc3448f6bad58e668063cbeaa200ccc37d9016f4 Mon Sep 17 00:00:00 2001 From: ZXY101 Date: Thu, 14 Mar 2024 07:31:13 +0200 Subject: [PATCH 2/4] Add gdrive volume data uploading/downloading --- src/routes/cloud/+page.svelte | 149 ++++++++++++++++++++++++++++++---- 1 file changed, 131 insertions(+), 18 deletions(-) diff --git a/src/routes/cloud/+page.svelte b/src/routes/cloud/+page.svelte index e043b3a..c8e4d0b 100644 --- a/src/routes/cloud/+page.svelte +++ b/src/routes/cloud/+page.svelte @@ -2,21 +2,28 @@ import { processFiles } from '$lib/upload'; import Loader from '$lib/components/Loader.svelte'; import { formatBytes, showSnackbar } from '$lib/util'; - import { Card } from 'flowbite-svelte'; + import { Button, Frame, Listgroup, ListgroupItem } from 'flowbite-svelte'; import { onMount } from 'svelte'; import { promptConfirmation } from '$lib/util'; import { GoogleSolid } from 'flowbite-svelte-icons'; + import { volumes } from '$lib/settings'; const CLIENT_ID = import.meta.env.VITE_GDRIVE_CLIENT_ID; const API_KEY = import.meta.env.VITE_GDRIVE_API_KEY; const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'; const SCOPES = 'https://www.googleapis.com/auth/drive'; + const FILES_API_URL = 'https://www.googleapis.com/upload/drive/v3/files'; + const FOLDER_MIME_TYPE = 'application/vnd.google-apps.folder'; + const READER_FOLDER = 'mokuro-reader'; + const VOLUME_DATA_FILE = 'volume-data.json'; let tokenClient: any; let zips: gapi.client.drive.File[]; let loadingMessage = ''; + let readerFolderId = ''; + let volumeDataId = ''; async function fetchZips(folderId: string) { const { result } = await gapi.client.drive.files.list({ @@ -78,19 +85,34 @@ loadingMessage = 'Connecting to drive'; - const { result } = await gapi.client.drive.files.list({ - q: `mimeType='application/vnd.google-apps.folder' and name='MokuroReader'`, + const { result: readerFolderRes } = await gapi.client.drive.files.list({ + q: `mimeType='application/vnd.google-apps.folder' and name='${READER_FOLDER}'`, fields: 'files(id)' }); - if (result.files?.length === 0) { - await gapi.client.drive.files.create({ - resource: { mimeType: FOLDER_MIME_TYPE, name: 'MokuroReader' }, + if (readerFolderRes.files?.length === 0) { + const { result: createReaderFolderRes } = await gapi.client.drive.files.create({ + resource: { mimeType: FOLDER_MIME_TYPE, name: READER_FOLDER }, fields: 'id' }); - } else { - zips = [...((await fetchZips(result.files?.[0]?.id || '')) || [])]; + + readerFolderId = createReaderFolderRes.id || ''; loadingMessage = ''; + } else { + const id = readerFolderRes.files?.[0]?.id || ''; + zips = [...((await fetchZips(id)) || [])]; + + readerFolderId = id || ''; + loadingMessage = ''; + } + + const { result: volumeDataRes } = await gapi.client.drive.files.list({ + q: `'${readerFolderId}' in parents and name='${VOLUME_DATA_FILE}'`, + fields: 'files(id, name)' + }); + + if (volumeDataRes.files?.length !== 0) { + volumeDataId = volumeDataRes.files?.[0].id || ''; } showSnackbar('Connected to Google Drive'); @@ -123,6 +145,62 @@ callback: connectDrive }); }); + + async function onUpload() { + const type = 'application/json'; + const json = localStorage.getItem('volumes') || ''; + const blob = new Blob([json], { type }); + + const metadata = { + mimeType: type, + name: VOLUME_DATA_FILE, + parents: [volumeDataId ? null : readerFolderId] + }; + + const { access_token } = gapi.auth.getToken(); + + const form = new FormData(); + + form.append('resource', new Blob([JSON.stringify(metadata)], { type })); + form.append('file', blob); + + loadingMessage = 'Uploading volume data'; + + const res = await fetch( + `${FILES_API_URL}${volumeDataId ? `/${volumeDataId}` : ''}?uploadType=multipart`, + { + method: volumeDataId ? 'PATCH' : 'POST', + headers: new Headers({ Authorization: 'Bearer ' + access_token }), + body: form + } + ); + volumeDataId = (await res.json()).id; + + loadingMessage = ''; + + showSnackbar('Volume data uploaded'); + } + + async function onDownload() { + loadingMessage = 'Downloading volume data'; + + const { body } = await gapi.client.drive.files.get({ + fileId: volumeDataId, + alt: 'media' + }); + + const downloaded = JSON.parse(body); + + volumes.update((prev) => { + return { + ...prev, + ...downloaded + }; + }); + + loadingMessage = ''; + showSnackbar('Volume data downloaded'); + }
@@ -131,15 +209,50 @@ {loadingMessage} {:else if zips} -
-

Google drive:

-
- {#each zips as zip} - onClick(zip)}> -

{zip.name}

-

{formatBytes(parseInt(zip.size || '0'))}

-
- {/each} +
+
+

Google Drive:

+
+ {#if zips.length > 0} + + {#each zips as zip} + onClick(zip)} + rounded + border + class="divide-y divide-gray-200 dark:divide-gray-600" + > + +
+

{zip.name}

+

{formatBytes(parseInt(zip.size || '0'))}

+
+
+ + {/each} +
+ {:else} +

+ Add your zip files to the {READER_FOLDER} folder + in your Google Drive. +

+ {/if} +
+
+
+ + {#if volumeDataId} + + {/if}
{:else} @@ -147,7 +260,7 @@ class="w-full border rounded-lg border-slate-600 p-10 border-opacity-50 hover:bg-slate-800" on:click={signIn} > -
+

Connect to Google Drive

From 8ecc96e92e7682b0db1001171420a48aa2414ce2 Mon Sep 17 00:00:00 2001 From: ZXY101 Date: Thu, 14 Mar 2024 07:53:42 +0200 Subject: [PATCH 3/4] Update drive styling --- src/routes/cloud/+page.svelte | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/routes/cloud/+page.svelte b/src/routes/cloud/+page.svelte index c8e4d0b..2999d78 100644 --- a/src/routes/cloud/+page.svelte +++ b/src/routes/cloud/+page.svelte @@ -203,17 +203,17 @@ } -
+
{#if loadingMessage} {loadingMessage} {:else if zips} -
-
+
+

Google Drive:

{#if zips.length > 0} @@ -241,7 +241,7 @@ {/if}
-
+
From 6daeb966c5bbd03aab22eb479da4444b2b0d291c Mon Sep 17 00:00:00 2001 From: ZXY101 Date: Tue, 26 Mar 2024 01:21:20 +0200 Subject: [PATCH 4/4] Add profile uploading + cleanup --- src/lib/components/NavBar.svelte | 2 +- src/lib/util/cloud.ts | 32 +++++ src/lib/util/index.ts | 3 +- src/routes/cloud/+page.svelte | 194 ++++++++++++++++++++----------- 4 files changed, 163 insertions(+), 68 deletions(-) create mode 100644 src/lib/util/cloud.ts diff --git a/src/lib/components/NavBar.svelte b/src/lib/components/NavBar.svelte index c6a7262..e38f636 100644 --- a/src/lib/components/NavBar.svelte +++ b/src/lib/components/NavBar.svelte @@ -37,7 +37,7 @@
(uploadModalOpen = true)} /> - goto('/cloud')} /> +
diff --git a/src/lib/util/cloud.ts b/src/lib/util/cloud.ts new file mode 100644 index 0000000..4e95108 --- /dev/null +++ b/src/lib/util/cloud.ts @@ -0,0 +1,32 @@ +type FileInfo = { + accessToken: string; + metadata: any; + fileId?: string; + localStorageId: string; + type: string; +} + +const FILES_API_URL = 'https://www.googleapis.com/upload/drive/v3/files'; + +export async function uploadFile({ accessToken, fileId, localStorageId, metadata, type }: FileInfo) { + const json = localStorage.getItem(localStorageId) || ''; + const blob = new Blob([json], { type }); + + const form = new FormData(); + + form.append('resource', new Blob([JSON.stringify(metadata)], { type })); + form.append('file', blob); + + + const res = await fetch( + `${FILES_API_URL}${fileId ? `/${fileId}` : ''}?uploadType=multipart`, + { + method: fileId ? 'PATCH' : 'POST', + headers: new Headers({ Authorization: 'Bearer ' + accessToken }), + body: form + } + ); + + return await res.json() +} + diff --git a/src/lib/util/index.ts b/src/lib/util/index.ts index 768a78e..629bdc9 100644 --- a/src/lib/util/index.ts +++ b/src/lib/util/index.ts @@ -2,4 +2,5 @@ export * from './snackbar'; export * from './upload'; export * from './misc'; export * from './modals'; -export * from './zip' \ No newline at end of file +export * from './zip' +export * from './cloud' \ No newline at end of file diff --git a/src/routes/cloud/+page.svelte b/src/routes/cloud/+page.svelte index 2999d78..c6227c4 100644 --- a/src/routes/cloud/+page.svelte +++ b/src/routes/cloud/+page.svelte @@ -1,29 +1,32 @@
@@ -209,49 +254,66 @@ {loadingMessage} {:else if zips} -
-
-

Google Drive:

-
- {#if zips.length > 0} - - {#each zips as zip} - onClick(zip)} - rounded - border - class="divide-y divide-gray-200 dark:divide-gray-600" - > - -
-

{zip.name}

-

{formatBytes(parseInt(zip.size || '0'))}

-
-
- - {/each} -
- {:else} -

- Add your zip files to the {READER_FOLDER} folder - in your Google Drive. -

+
+
+

Google Drive:

+
+ + {#if volumeDataId} + + {/if} + + {#if profilesId} + {/if}
-
- - {#if volumeDataId} - +
+ {#if zips.length > 0} + + {#each zips as zip} + onClick(zip)} + rounded + border + class="divide-y divide-gray-200 dark:divide-gray-600" + > + +
+

{zip.name}

+

{formatBytes(parseInt(zip.size || '0'))}

+
+
+ + {/each} +
+ {:else} +

+ Add your zip files to the {READER_FOLDER} folder in + your Google Drive. +

{/if}