diff --git a/packages/client-core/i18n/en/editor.json b/packages/client-core/i18n/en/editor.json index 3fbb540085..8c923e4306 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/btyesToSize.ts b/packages/common/src/utils/btyesToSize.ts new file mode 100644 index 0000000000..edffca23f8 --- /dev/null +++ b/packages/common/src/utils/btyesToSize.ts @@ -0,0 +1,43 @@ +/* +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. +*/ + +/** + * Converts bytes to a human-readable size + * @param bytes The number of bytes + * @param decimals The number of decimal places to include + * @returns The human-readable size + */ + +export function bytesToSize(bytes: number, decimals = 2) { + if (bytes === 0) return '0 Bytes' + + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] +} 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/common/src/utils/mapToObject.ts b/packages/common/src/utils/mapToObject.ts index 9db2ca7dfe..6370aa323f 100644 --- a/packages/common/src/utils/mapToObject.ts +++ b/packages/common/src/utils/mapToObject.ts @@ -52,3 +52,7 @@ export const iterativeMapToObject = (root: Record) => { } return cloneDeep(iterate(root)) } + +export function objectToMap(object: object) { + return new Map(Object.entries(object)) +} diff --git a/packages/common/src/utils/miscUtils.ts b/packages/common/src/utils/miscUtils.ts index 24b6f9ff93..3cdbb44c5c 100644 --- a/packages/common/src/utils/miscUtils.ts +++ b/packages/common/src/utils/miscUtils.ts @@ -38,6 +38,11 @@ export function isNumber(value: string | number): boolean { return value != null && value !== '' && !isNaN(Number(value.toString())) } +export function toPrecision(value, precision) { + const p = 1 / precision + return Math.round(value * p) / p +} + export function combine(first, second, third) { const res: any[] = [] @@ -47,6 +52,23 @@ export function combine(first, second, third) { return res } + +export const unique = (arr: T[], keyFinder: (item: T) => S): T[] => { + const set = new Set() + const newArr = [] as T[] + if (!keyFinder) keyFinder = (item: T) => item as any as S + + for (const item of arr) { + const key = keyFinder(item) + if (set.has(key)) continue + + newArr.push(item) + set.add(key) + } + + return newArr +} + export function combineArrays(arrays: [[]]) { const res = [] @@ -59,6 +81,23 @@ export function combineArrays(arrays: [[]]) { return res } +export function insertArraySeparator(children, separatorFn) { + if (!Array.isArray(children)) { + return children + } + const length = children.length + if (length === 1) { + return children[0] + } + return children.reduce((acc, item, index) => { + acc.push(item) + if (index !== length - 1) { + acc.push(separatorFn(index)) + } + return acc + }, []) +} + export function arraysAreEqual(arr1: any[], arr2: any[]): boolean { if (arr1.length !== arr2.length) return false @@ -154,3 +193,14 @@ export const toCapitalCase = (source: string) => { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() }) } + +export function toCamelPad(source: string) { + return source + .replace(/([A-Z]+)([A-Z][a-z])/g, ' $1 $2') + .replace(/([a-z\d])([A-Z])/g, '$1 $2') + .replace(/([a-zA-Z])(\d)/g, '$1 $2') + .replace(/^./, (str) => { + return str.toUpperCase() + }) + .trim() +} diff --git a/packages/editor/src/functions/utils.ts b/packages/editor/src/functions/utils.ts index 38e654987e..f1d2b197f5 100755 --- a/packages/editor/src/functions/utils.ts +++ b/packages/editor/src/functions/utils.ts @@ -22,60 +22,7 @@ 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. */ - -export function insertSeparator(children, separatorFn) { - if (!Array.isArray(children)) { - return children - } - const length = children.length - if (length === 1) { - return children[0] - } - return children.reduce((acc, item, index) => { - acc.push(item) - if (index !== length - 1) { - acc.push(separatorFn(index)) - } - return acc - }, []) -} -export function objectToMap(object: object) { - return new Map(Object.entries(object)) -} - -export const unique = (arr: T[], keyFinder: (item: T) => S): T[] => { - const set = new Set() - const newArr = [] as T[] - if (!keyFinder) keyFinder = (item: T) => item as any as S - - for (const item of arr) { - const key = keyFinder(item) - if (set.has(key)) continue - - newArr.push(item) - set.add(key) - } - - return newArr -} - -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() -} - -export const cmdOrCtrlString = isApple() ? 'meta' : 'ctrl' +import { isApple } from '@etherealengine/common/src/utils/getDeviceStats' export function getStepSize(event, smallStep, mediumStep, largeStep) { if (event.altKey) { @@ -86,29 +33,4 @@ export function getStepSize(event, smallStep, mediumStep, largeStep) { return mediumStep } -export function toPrecision(value, precision) { - const p = 1 / precision - return Math.round(value * p) / p -} -// https://stackoverflow.com/a/26188910 -export function camelPad(str) { - return str - .replace(/([A-Z]+)([A-Z][a-z])/g, ' $1 $2') - .replace(/([a-z\d])([A-Z])/g, '$1 $2') - .replace(/([a-zA-Z])(\d)/g, '$1 $2') - .replace(/^./, (str) => { - return str.toUpperCase() - }) - .trim() -} -export function bytesToSize(bytes: number, decimals = 2) { - if (bytes === 0) return '0 Bytes' - - const k = 1024 - const dm = decimals < 0 ? 0 : decimals - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - - const i = Math.floor(Math.log(bytes) / Math.log(k)) - - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] -} +export const cmdOrCtrlString = isApple() ? 'meta' : 'ctrl' diff --git a/packages/ui/src/components/editor/input/Numeric/index.tsx b/packages/ui/src/components/editor/input/Numeric/index.tsx index ba948c519a..d6feb6c37e 100644 --- a/packages/ui/src/components/editor/input/Numeric/index.tsx +++ b/packages/ui/src/components/editor/input/Numeric/index.tsx @@ -27,7 +27,8 @@ import React from 'react' import { clamp } from '@etherealengine/spatial/src/common/functions/MathLerpFunctions' -import { getStepSize, toPrecision } from '@etherealengine/editor/src/functions/utils' +import { toPrecision } from '@etherealengine/common/src/utils/miscUtils' +import { getStepSize } from '@etherealengine/editor/src/functions/utils' import { useHookstate } from '@etherealengine/hyperflux' import { twMerge } from 'tailwind-merge' import Text from '../../../../primitives/tailwind/Text' diff --git a/packages/ui/src/components/editor/layout/Scrubber.tsx b/packages/ui/src/components/editor/layout/Scrubber.tsx index 2eccf3f469..08198f3f4b 100644 --- a/packages/ui/src/components/editor/layout/Scrubber.tsx +++ b/packages/ui/src/components/editor/layout/Scrubber.tsx @@ -23,7 +23,8 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { getStepSize, toPrecision } from '@etherealengine/editor/src/functions/utils' +import { toPrecision } from '@etherealengine/common/src/utils/miscUtils' +import { getStepSize } from '@etherealengine/editor/src/functions/utils' import { useHookstate } from '@etherealengine/hyperflux' import React, { useRef } from 'react' import { twMerge } from 'tailwind-merge' 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 feaca8373a..5793b4bc76 100644 --- a/packages/ui/src/components/editor/panels/Files/container/index.tsx +++ b/packages/ui/src/components/editor/panels/Files/container/index.tsx @@ -25,18 +25,17 @@ Ethereal Engine. All Rights Reserved. import { FileThumbnailJobState } from '@etherealengine/client-core/src/common/services/FileThumbnailJobState' import { NotificationService } from '@etherealengine/client-core/src/common/services/NotificationService' import { PopoverState } from '@etherealengine/client-core/src/common/services/PopoverState' -import config from '@etherealengine/common/src/config' import { FileBrowserContentType, StaticResourceType, UserID, - archiverPath, fileBrowserPath, projectPath, staticResourcePath } from '@etherealengine/common/src/schema.type.module' import { CommonKnownContentTypes } from '@etherealengine/common/src/utils/CommonKnownContentTypes' -import { Engine } from '@etherealengine/ecs' +import { bytesToSize } from '@etherealengine/common/src/utils/btyesToSize' +import { unique } from '@etherealengine/common/src/utils/miscUtils' import { AssetSelectionChangePropsType } from '@etherealengine/editor/src/components/assets/AssetsPreviewPanel' import { FilesViewModeSettings, @@ -48,12 +47,7 @@ import ImageCompressionPanel from '@etherealengine/editor/src/components/assets/ import ModelCompressionPanel from '@etherealengine/editor/src/components/assets/ModelCompressionPanel' import { DndWrapper } from '@etherealengine/editor/src/components/dnd/DndWrapper' import { SupportedFileTypes } from '@etherealengine/editor/src/constants/AssetTypes' -import { - downloadBlobAsZip, - handleUploadFiles, - inputFileWithAddToScene -} from '@etherealengine/editor/src/functions/assetFunctions' -import { bytesToSize, unique } from '@etherealengine/editor/src/functions/utils' +import { handleUploadFiles, inputFileWithAddToScene } from '@etherealengine/editor/src/functions/assetFunctions' 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' @@ -86,6 +80,7 @@ import InputGroup from '../../../input/Group' import { FileBrowserItem, FileTableWrapper, canDropItemOverFolder } from '../browserGrid' import DeleteFileModal from '../browserGrid/DeleteFileModal' import FilePropertiesModal from '../browserGrid/FilePropertiesModal' +import { ProjectDownloadProgress, handleDownloadProject } from '../download/projectDownload' import { FileUploadProgress } from '../upload/FileUploadProgress' type FileBrowserContentPanelProps = { @@ -230,6 +225,66 @@ function GeneratingThumbnailsProgress() { ) } +export const ViewModeSettings = () => { + const { t } = useTranslation() + + const filesViewMode = useMutableState(FilesViewModeState).viewMode + + const viewModeSettings = useHookstate(getMutableState(FilesViewModeSettings)) + return ( + + + {isLoading && ( )} @@ -888,64 +929,3 @@ export default function FilesPanelContainer() { /> ) } - -export const ViewModeSettings = () => { - const { t } = useTranslation() - - const filesViewMode = useMutableState(FilesViewModeState).viewMode - - const viewModeSettings = useHookstate(getMutableState(FilesViewModeSettings)) - return ( - -