From 1f51336ea37886df558393061a86b7d4710f0536 Mon Sep 17 00:00:00 2001 From: sybiote Date: Thu, 8 Aug 2024 01:50:51 +0530 Subject: [PATCH 1/3] download progress bar --- packages/client-core/i18n/en/editor.json | 1 + .../src/utils/{getOS.ts => getDeviceStats.ts} | 16 ++ .../editor/panels/Files/container/index.tsx | 160 +++++++++++------- .../download/projectDownloadProgress.tsx | 45 +++++ 4 files changed, 159 insertions(+), 63 deletions(-) rename packages/common/src/utils/{getOS.ts => getDeviceStats.ts} (75%) create mode 100644 packages/ui/src/components/editor/panels/Files/download/projectDownloadProgress.tsx diff --git a/packages/client-core/i18n/en/editor.json b/packages/client-core/i18n/en/editor.json index 4275bf81c4..d7c1f2dba4 100755 --- a/packages/client-core/i18n/en/editor.json +++ b/packages/client-core/i18n/en/editor.json @@ -1217,6 +1217,7 @@ "uploadFiles": "Upload Files", "uploadFolder": "Upload Folder", "uploadingFiles": "Uploading Files ({{completed}}/{{total}})", + "downloadingProject": "Downloading Project ({{completed}}/{{total}})", "search-placeholder": "Search", "generatingThumbnails": "Generating Thumbnails ({{count}} remaining)", "file": "File", diff --git a/packages/common/src/utils/getOS.ts b/packages/common/src/utils/getDeviceStats.ts similarity index 75% rename from packages/common/src/utils/getOS.ts rename to packages/common/src/utils/getDeviceStats.ts index 40e44c697e..5fa025b4d2 100644 --- a/packages/common/src/utils/getOS.ts +++ b/packages/common/src/utils/getDeviceStats.ts @@ -35,3 +35,19 @@ export function getOS() { } return 'other' } + +export const isApple = () => { + if ('navigator' in globalThis === false) return false + + const iOS_1to12 = /iPad|iPhone|iPod/.test(navigator.platform) + + const iOS13_iPad = navigator.platform === 'MacIntel' + + const iOS1to12quirk = () => { + const audio = new Audio() // temporary Audio object + audio.volume = 0.5 // has no effect on iOS <= 12 + return audio.volume === 1 + } + + return iOS_1to12 || iOS13_iPad || iOS1to12quirk() +} diff --git a/packages/ui/src/components/editor/panels/Files/container/index.tsx b/packages/ui/src/components/editor/panels/Files/container/index.tsx index 3008bb79bc..b39ffd2825 100644 --- a/packages/ui/src/components/editor/panels/Files/container/index.tsx +++ b/packages/ui/src/components/editor/panels/Files/container/index.tsx @@ -36,6 +36,8 @@ import { staticResourcePath } from '@etherealengine/common/src/schema.type.module' import { CommonKnownContentTypes } from '@etherealengine/common/src/utils/CommonKnownContentTypes' +import { bytesToSize } from '@etherealengine/common/src/utils/btyesToSize' +import { unique } from '@etherealengine/common/src/utils/miscUtils' import { Engine } from '@etherealengine/ecs' import { AssetSelectionChangePropsType } from '@etherealengine/editor/src/components/assets/AssetsPreviewPanel' import { @@ -53,7 +55,6 @@ import { handleUploadFiles, inputFileWithAddToScene } from '@etherealengine/editor/src/functions/assetFunctions' -import { bytesToSize, unique } from '@etherealengine/editor/src/functions/utils' import { EditorState } from '@etherealengine/editor/src/services/EditorServices' import { ClickPlacementState } from '@etherealengine/editor/src/systems/ClickPlacementSystem' import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' @@ -85,6 +86,7 @@ import InputGroup from '../../../input/Group' import { FileBrowserItem, FileTableWrapper, canDropItemOverFolder } from '../browserGrid' import DeleteFileModal from '../browserGrid/DeleteFileModal' import FilePropertiesModal from '../browserGrid/FilePropertiesModal' +import { ProjectDownloadProgress } from '../download/projectDownloadProgress' import { FileUploadProgress } from '../upload/FileUploadProgress' type FileBrowserContentPanelProps = { @@ -228,6 +230,66 @@ function GeneratingThumbnailsProgress() { ) } +export const ViewModeSettings = () => { + const { t } = useTranslation() + + const filesViewMode = useMutableState(FilesViewModeState).viewMode + + const viewModeSettings = useHookstate(getMutableState(FilesViewModeSettings)) + return ( + + + {isLoading && ( )} @@ -848,64 +943,3 @@ export default function FilesPanelContainer() { /> ) } - -export const ViewModeSettings = () => { - const { t } = useTranslation() - - const filesViewMode = useMutableState(FilesViewModeState).viewMode - - const viewModeSettings = useHookstate(getMutableState(FilesViewModeSettings)) - return ( - - - + {isLoading && ( )} diff --git a/packages/ui/src/components/editor/panels/Files/download/projectDownload.tsx b/packages/ui/src/components/editor/panels/Files/download/projectDownload.tsx new file mode 100644 index 0000000000..bb6b4406dc --- /dev/null +++ b/packages/ui/src/components/editor/panels/Files/download/projectDownload.tsx @@ -0,0 +1,111 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { NotificationService } from '@etherealengine/client-core/src/common/services/NotificationService' +import config from '@etherealengine/common/src/config' +import { archiverPath } from '@etherealengine/common/src/schema.type.module' +import { bytesToSize } from '@etherealengine/common/src/utils/btyesToSize' +import { Engine } from '@etherealengine/ecs' +import { downloadBlobAsZip } from '@etherealengine/editor/src/functions/assetFunctions' +import { defineState, getMutableState, useMutableState } from '@etherealengine/hyperflux' +import React from 'react' +import { useTranslation } from 'react-i18next' +import Progress from '../../../../../primitives/tailwind/Progress' + +const DownloadProjectState = defineState({ + name: 'DownloadProjectState', + initial: () => ({ + total: 0, + progress: 0, + isDownloading: false + }) +}) + +export const handleDownloadProject = async (projectName: string, selectedDirectory: string) => { + const data = await Engine.instance.api + .service(archiverPath) + .get(null, { query: { project: projectName } }) + .catch((err: Error) => { + NotificationService.dispatchNotify(err.message, { variant: 'warning' }) + return null + }) + if (!data) return + + const downloadState = getMutableState(DownloadProjectState) + + downloadState.isDownloading.set(true) // Start Download + + const response = await fetch(`${config.client.fileServer}/${data}`) + const totalBytes = parseInt(response.headers.get('Content-Length') || '0', 10) + downloadState.total.set(totalBytes) // Set the total bytes + + const reader = response.body?.getReader() + const chunks: Uint8Array[] = [] + let bytesReceived = 0 + + while (true) { + const { done, value } = await reader!.read() + if (done) break + chunks.push(value) + bytesReceived += value.length + downloadState.progress.set(bytesReceived) + } + + const blob = new Blob(chunks) + downloadState.isDownloading.set(false) // Mark as completed + downloadState.progress.set(0) + downloadState.total.set(0) + + let fileName: string + if (selectedDirectory.at(-1) === '/') { + fileName = selectedDirectory.split('/').at(-2) as string + } else { + fileName = selectedDirectory.split('/').at(-1) as string + } + + downloadBlobAsZip(blob, fileName) +} + +export const ProjectDownloadProgress = () => { + const { t } = useTranslation() + const downloadState = useMutableState(DownloadProjectState) + const isDownloading = downloadState.isDownloading.value + const completed = bytesToSize(downloadState.progress.value) + const total = bytesToSize(downloadState.total.value) + const progress = (downloadState.progress.value / downloadState.total.value) * 100 + + return isDownloading ? ( +
+
+ + {t('editor:layout.filebrowser.downloadingProject', { completed, total })} + +
+ +
+
+
+ ) : null +} diff --git a/packages/ui/src/components/editor/panels/Files/download/projectDownloadProgress.tsx b/packages/ui/src/components/editor/panels/Files/download/projectDownloadProgress.tsx deleted file mode 100644 index 510c42b14d..0000000000 --- a/packages/ui/src/components/editor/panels/Files/download/projectDownloadProgress.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import React from 'react' -import { useTranslation } from 'react-i18next' -import Progress from '../../../../../primitives/tailwind/Progress' - -export const ProjectDownloadProgress = ({ completed, total, progress, isDownloading }) => { - const { t } = useTranslation() - - return isDownloading ? ( -
-
- - {t('editor:layout.filebrowser.downloadingProject', { completed, total })} - -
- -
-
-
- ) : null -}