From c1721d257c95d242f96d5b4555661795a3d3fea0 Mon Sep 17 00:00:00 2001 From: Uriel Date: Fri, 21 Jul 2023 02:11:00 -0300 Subject: [PATCH 1/3] add support in GUI --- gui/package.json | 2 + gui/src/App.tsx | 45 +++++++++++++- .../body-proportions/ProportionsChoose.tsx | 36 ++++-------- gui/src/utils/a11y.ts | 58 +++++++++++++++++++ package-lock.json | 24 ++++++-- solarxr-protocol | 2 +- 6 files changed, 134 insertions(+), 33 deletions(-) diff --git a/gui/package.json b/gui/package.json index 6e61ff09d4..d09489c6cd 100644 --- a/gui/package.json +++ b/gui/package.json @@ -18,6 +18,7 @@ "identity-obj-proxy": "^3.0.0", "intl-pluralrules": "^1.3.1", "ip-num": "^1.4.1", + "mime-types": "^2.1.35", "postcss-flexbugs-fixes": "^5.0.2", "postcss-normalize": "^10.0.1", "postcss-preset-env": "^7.0.1", @@ -68,6 +69,7 @@ "@tailwindcss/forms": "^0.5.3", "@tauri-apps/cli": "^1.4.0", "@types/file-saver": "^2.0.5", + "@types/mime-types": "^2.1.1", "@types/react": "18.0.25", "@types/react-dom": "^18.0.5", "@types/react-modal": "3.13.1", diff --git a/gui/src/App.tsx b/gui/src/App.tsx index 213d70f51c..79b450f8a5 100644 --- a/gui/src/App.tsx +++ b/gui/src/App.tsx @@ -13,6 +13,7 @@ import { Serial } from './components/settings/pages/Serial'; import { SettingsLayoutRoute } from './components/settings/SettingsLayout'; import { useProvideWebsocketApi, + useWebsocketAPI, WebSocketApiContext, } from './hooks/websocket-api'; @@ -48,10 +49,17 @@ import { CalibrationTutorialPage } from './components/onboarding/pages/Calibrati import { AssignmentTutorialPage } from './components/onboarding/pages/assignment-preparation/AssignmentTutorial'; import { open } from '@tauri-apps/api/shell'; import semver from 'semver'; -import { useBreakpoint } from './hooks/breakpoint'; +import { useBreakpoint, useIsTauri } from './hooks/breakpoint'; import { VRModePage } from './components/vr-mode/VRModePage'; import { InterfaceSettings } from './components/settings/pages/InterfaceSettings'; import { error, log } from './utils/logging'; +import { + ComputerDirectory, + RpcMessage, + SaveFileNotificationT, +} from 'solarxr-protocol'; +import mime from 'mime-types'; +import { resolveDir, saveFile } from './utils/a11y'; export const GH_REPO = 'SlimeVR/SlimeVR-Server'; export const VersionContext = createContext(''); @@ -60,10 +68,43 @@ export const SLIMEVR_DISCORD = 'https://discord.gg/slimevr'; function Layout() { const { loading } = useConfig(); + const { isMobile } = useBreakpoint('mobile'); + const isTauri = useIsTauri(); + const { useRPCPacket } = useWebsocketAPI(); + + useRPCPacket( + RpcMessage.SaveFileNotification, + async (fileSave: SaveFileNotificationT) => { + if (!fileSave.data) return; + let filename = + typeof fileSave.expectedFilename === 'string' + ? fileSave.expectedFilename + : 'data'; + + let mimeType = 'application/octet-stream'; + if (typeof fileSave.fileExtension === 'string') { + filename += fileSave.fileExtension; + } else if (typeof fileSave.mimeType === 'string') { + mimeType = fileSave.mimeType; + filename += mime.extension(mimeType); + } + + const file = new File([Uint8Array.from(fileSave.data)], filename, { + type: mimeType, + }); + + saveFile({ + isTauri, + file, + defaultPath: await resolveDir( + fileSave.expectedDir ?? ComputerDirectory.Documents + ).catch(() => undefined), + }); + } + ); if (loading) return <>; - const { isMobile } = useBreakpoint('mobile'); return ( <> diff --git a/gui/src/components/onboarding/pages/body-proportions/ProportionsChoose.tsx b/gui/src/components/onboarding/pages/body-proportions/ProportionsChoose.tsx index 0b24616a97..6e77eb0e4e 100644 --- a/gui/src/components/onboarding/pages/body-proportions/ProportionsChoose.tsx +++ b/gui/src/components/onboarding/pages/body-proportions/ProportionsChoose.tsx @@ -10,12 +10,9 @@ import { SkeletonConfigRequestT, } from 'solarxr-protocol'; import { useWebsocketAPI } from '../../../../hooks/websocket-api'; -import saveAs from 'file-saver'; -import { save } from '@tauri-apps/api/dialog'; -import { writeTextFile } from '@tauri-apps/api/fs'; import { useIsTauri } from '../../../../hooks/breakpoint'; import { useAppContext } from '../../../../hooks/app'; -import { error } from '../../../../utils/logging'; +import { saveFile } from '../../../../utils/a11y'; export const MIN_HEIGHT = 0.4; export const MAX_HEIGHT = 4; @@ -51,28 +48,19 @@ export function ProportionsChoose() { useRPCPacket( RpcMessage.SkeletonConfigResponse, (data: SkeletonConfigResponseT) => { - const blob = new Blob([JSON.stringify(data)], { + const file = new File([JSON.stringify(data)], 'body-proportions.json', { type: 'application/json', }); - if (isTauri) { - save({ - filters: [ - { - name: l10n.getString('onboarding-choose_proportions-file_type'), - extensions: ['json'], - }, - ], - defaultPath: 'body-proportions.json', - }) - .then((path) => - path ? writeTextFile(path, JSON.stringify(data)) : undefined - ) - .catch((err) => { - error(err); - }); - } else { - saveAs(blob, 'body-proportions.json'); - } + saveFile({ + isTauri, + file, + filters: [ + { + name: l10n.getString('onboarding-choose_proportions-file_type'), + extensions: ['json'], + }, + ], + }); } ); diff --git a/gui/src/utils/a11y.ts b/gui/src/utils/a11y.ts index 351f63f3dd..f73130bb89 100644 --- a/gui/src/utils/a11y.ts +++ b/gui/src/utils/a11y.ts @@ -1,3 +1,10 @@ +import { DialogFilter, save } from '@tauri-apps/api/dialog'; +import { error } from './logging'; +import saveAs from 'file-saver'; +import { writeBinaryFile } from '@tauri-apps/api/fs'; +import { ComputerDirectory } from 'solarxr-protocol'; +import { documentDir } from '@tauri-apps/api/path'; + export function a11yClick(event: React.KeyboardEvent | React.MouseEvent) { if (event.type === 'click') { return true; @@ -6,3 +13,54 @@ export function a11yClick(event: React.KeyboardEvent | React.MouseEvent) { return keyboard.key === 'Enter' || keyboard.key === ' '; } } + +export async function saveFile({ + isTauri, + file, + filters, + defaultPath, +}: { + isTauri: boolean; + file: File; + filters?: DialogFilter[]; + defaultPath?: string; +}): Promise { + if (isTauri) { + await save({ + filters, + defaultPath: defaultPath ? `${defaultPath}/${file.name}` : file.name, + }) + .then(async (path) => + path ? writeBinaryFile(path, await file.arrayBuffer()) : undefined + ) + .catch((err) => { + error(err); + }); + } else { + if ('share' in navigator) { + let canShare: boolean | null = null; + if ('canShare' in navigator) { + canShare = navigator.canShare({ files: [file] }); + } + if (canShare || canShare === null) { + const shared = await navigator + .share({ files: [file] }) + .then(() => true) + .catch((err) => { + error(err); + return false; + }); + + if (shared) return; + } + } + saveAs(file, file.name); + } +} + +export function resolveDir(dir: ComputerDirectory): Promise { + switch(dir) { + case ComputerDirectory.Documents: + return documentDir(); + } +} diff --git a/package-lock.json b/package-lock.json index a87d0bd8c6..620e01c57d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "identity-obj-proxy": "^3.0.0", "intl-pluralrules": "^1.3.1", "ip-num": "^1.4.1", + "mime-types": "^2.1.35", "postcss-flexbugs-fixes": "^5.0.2", "postcss-normalize": "^10.0.1", "postcss-preset-env": "^7.0.1", @@ -55,6 +56,7 @@ "@tailwindcss/forms": "^0.5.3", "@tauri-apps/cli": "^1.4.0", "@types/file-saver": "^2.0.5", + "@types/mime-types": "^2.1.1", "@types/react": "18.0.25", "@types/react-dom": "^18.0.5", "@types/react-modal": "3.13.1", @@ -3325,6 +3327,12 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/mime-types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", + "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", + "dev": true + }, "node_modules/@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -7537,7 +7545,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "peer": true, "engines": { "node": ">= 0.6" } @@ -7546,7 +7553,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -12851,6 +12857,12 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "@types/mime-types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", + "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", + "dev": true + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -15824,14 +15836,12 @@ "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "peer": true + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "peer": true, "requires": { "mime-db": "1.52.0" } @@ -17283,6 +17293,7 @@ "@tauri-apps/api": "^1.4.0", "@tauri-apps/cli": "^1.4.0", "@types/file-saver": "^2.0.5", + "@types/mime-types": "^2.1.1", "@types/react": "18.0.25", "@types/react-dom": "^18.0.5", "@types/react-modal": "3.13.1", @@ -17307,6 +17318,7 @@ "identity-obj-proxy": "^3.0.0", "intl-pluralrules": "^1.3.1", "ip-num": "^1.4.1", + "mime-types": "^2.1.35", "postcss": "^8.4.24", "postcss-flexbugs-fixes": "^5.0.2", "postcss-normalize": "^10.0.1", @@ -17321,8 +17333,8 @@ "react-modal": "3.15.1", "react-responsive": "^9.0.2", "react-router-dom": "^6.2.2", - "semver": "^7.5.3", "rollup-plugin-visualizer": "^5.9.2", + "semver": "^7.5.3", "solarxr-protocol": "file:../solarxr-protocol", "tailwind-gradient-mask-image": "^1.0.0", "tailwindcss": "^3.3.2", diff --git a/solarxr-protocol b/solarxr-protocol index c22d4729ec..d7a51763db 160000 --- a/solarxr-protocol +++ b/solarxr-protocol @@ -1 +1 @@ -Subproject commit c22d4729ec45a9d4bd357458cfa4cc5cc65968ab +Subproject commit d7a51763db6ba2d4be9a6bcb9d3ba3c217dcfbf5 From abddd3a3f8e30946ab0ff7330b52c38ebf834214 Mon Sep 17 00:00:00 2001 From: ImUrX Date: Thu, 3 Aug 2023 02:27:10 -0300 Subject: [PATCH 2/3] change rpc --- solarxr-protocol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solarxr-protocol b/solarxr-protocol index d7a51763db..5dba5b3f2c 160000 --- a/solarxr-protocol +++ b/solarxr-protocol @@ -1 +1 @@ -Subproject commit d7a51763db6ba2d4be9a6bcb9d3ba3c217dcfbf5 +Subproject commit 5dba5b3f2c453111f8028473c3aff3a956b287fe From 69d56847768a18b364e6ac9063be098a4c755435 Mon Sep 17 00:00:00 2001 From: ImUrX Date: Thu, 3 Aug 2023 03:02:41 -0300 Subject: [PATCH 3/3] save latest solarxr --- solarxr-protocol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solarxr-protocol b/solarxr-protocol index 5dba5b3f2c..fa9a557085 160000 --- a/solarxr-protocol +++ b/solarxr-protocol @@ -1 +1 @@ -Subproject commit 5dba5b3f2c453111f8028473c3aff3a956b287fe +Subproject commit fa9a557085ceedf35ffa39d055ff24ea8a5db189