add import proportions button (#811)

This commit is contained in:
Uriel
2023-09-02 16:57:00 -03:00
committed by GitHub
parent ac5a68d33d
commit 6dfc8a8101
4 changed files with 125 additions and 12 deletions

View File

@@ -16,10 +16,10 @@
"@tauri-apps/plugin-shell": "github:tauri-apps/tauri-plugin-shell#v2",
"@tauri-apps/plugin-window": "github:tauri-apps/tauri-plugin-window#v2",
"@vitejs/plugin-react": "^3.0.0",
"browser-fs-access": "^0.34.1",
"browserslist": "^4.18.1",
"classnames": "^2.3.1",
"eslint-config-react-app": "^7.0.0",
"file-saver": "^2.0.5",
"flatbuffers": "^22.10.26",
"identity-obj-proxy": "^3.0.0",
"intl-pluralrules": "^1.3.1",

View File

@@ -747,6 +747,9 @@ onboarding-choose_proportions-manual_proportions = Manual proportions
onboarding-choose_proportions-manual_proportions-subtitle = For small touches
onboarding-choose_proportions-manual_proportions-description = This will let you adjust your proportions manually by modifying them directly
onboarding-choose_proportions-export = Export proportions
onboarding-choose_proportions-import = Import proportions
onboarding-choose_proportions-import-success = Imported
onboarding-choose_proportions-import-failed = Failed
onboarding-choose_proportions-file_type = Body proportions file
## Tracker manual proportions setup

View File

@@ -5,21 +5,30 @@ import classNames from 'classnames';
import { Typography } from '../../../commons/Typography';
import { Button } from '../../../commons/Button';
import {
SkeletonConfigResponseT,
RpcMessage,
SkeletonConfigRequestT,
SkeletonBone,
ChangeSkeletonConfigRequestT,
} from 'solarxr-protocol';
import { useWebsocketAPI } from '../../../../hooks/websocket-api';
import saveAs from 'file-saver';
import { save } from '@tauri-apps/plugin-dialog';
import { writeTextFile } from '@tauri-apps/plugin-fs';
import { useIsTauri } from '../../../../hooks/breakpoint';
import { useAppContext } from '../../../../hooks/app';
import { error } from '../../../../utils/logging';
import { fileOpen, fileSave } from 'browser-fs-access';
import { useDebouncedEffect } from '../../../../hooks/timeout';
export const MIN_HEIGHT = 0.4;
export const MAX_HEIGHT = 4;
export const DEFAULT_HEIGHT = 1.5;
export const CURRENT_EXPORT_VERSION = 1;
enum ImportStatus {
FAILED,
SUCCESS,
OK,
}
export function ProportionsChoose() {
const isTauri = useIsTauri();
@@ -27,8 +36,15 @@ export function ProportionsChoose() {
const { applyProgress, state } = useOnboarding();
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
const [animated, setAnimated] = useState(false);
const [importState, setImportState] = useState(ImportStatus.OK);
const { computedTrackers } = useAppContext();
useDebouncedEffect(
() => setImportState(ImportStatus.OK),
[importState],
2000
);
const hmdTracker = useMemo(
() =>
computedTrackers.find(
@@ -48,9 +64,27 @@ export function ProportionsChoose() {
[hmdTracker?.tracker.position?.y]
);
const importStatusKey = useMemo(() => {
switch (importState) {
case ImportStatus.FAILED:
return 'onboarding-choose_proportions-import-failed';
case ImportStatus.SUCCESS:
return 'onboarding-choose_proportions-import-success';
case ImportStatus.OK:
return 'onboarding-choose_proportions-import';
}
}, [importState]);
useRPCPacket(
RpcMessage.SkeletonConfigResponse,
(data: SkeletonConfigResponseT) => {
(data: SkeletonConfigExport) => {
// Convert the skeleton part enums into a string
data.skeletonParts.forEach((x) => {
if (typeof x.bone === 'number')
x.bone = SkeletonBone[x.bone] as SkeletonBoneKey;
});
data.version = CURRENT_EXPORT_VERSION;
const blob = new Blob([JSON.stringify(data)], {
type: 'application/json',
});
@@ -71,13 +105,52 @@ export function ProportionsChoose() {
error(err);
});
} else {
saveAs(blob, 'body-proportions.json');
fileSave(blob, {
fileName: 'body-proportions.json',
extensions: ['.json'],
});
}
}
);
applyProgress(0.85);
const onImport = async () => {
const file = await fileOpen({
mimeTypes: ['application/json'],
});
const text = await file.text();
const config = JSON.parse(text) as SkeletonConfigExport;
if (
!config?.skeletonParts?.length ||
!Array.isArray(config.skeletonParts)
) {
error(
'failed to import body proportions because skeletonParts is not an array/empty'
);
return setImportState(ImportStatus.FAILED);
}
for (const bone of [...config.skeletonParts]) {
if (
(typeof bone.bone === 'string' && !(bone.bone in SkeletonBone)) ||
(typeof bone.bone === 'number' &&
typeof SkeletonBone[bone.bone] !== 'string')
) {
error(
`failed to import body proportions because ${bone.bone} is not a valid bone`
);
return setImportState(ImportStatus.FAILED);
}
}
parseConfigImport(config).forEach((req) =>
sendRPCPacket(RpcMessage.ChangeSkeletonConfigRequest, req)
);
setImportState(ImportStatus.SUCCESS);
};
return (
<>
<div className="flex flex-col gap-5 h-full items-center w-full xs:justify-center mobile:overflow-y-auto relative px-4 pb-4">
@@ -196,7 +269,7 @@ export function ProportionsChoose() {
</div>
</div>
</div>
<div className="flex flex-row">
<div className="flex flex-row gap-3">
{!state.alonePage && (
<Button variant="secondary" to="/onboarding/reset-tutorial">
{l10n.getString('onboarding-previous_step')}
@@ -214,9 +287,46 @@ export function ProportionsChoose() {
>
{l10n.getString('onboarding-choose_proportions-export')}
</Button>
<Button
variant={!state.alonePage ? 'secondary' : 'tertiary'}
className={classNames(
'transition-colors',
importState === ImportStatus.FAILED && 'bg-status-critical',
importState === ImportStatus.SUCCESS && 'bg-status-success'
)}
onClick={onImport}
>
{l10n.getString(importStatusKey)}
</Button>
</div>
</div>
</div>
</>
);
}
function parseConfigImport(
config: SkeletonConfigExport
): ChangeSkeletonConfigRequestT[] {
if (!config.version) config.version = 1;
if (config.version < 1) {
// Add config migration stuff here, this one is just an example.
}
return config.skeletonParts.map((part) => {
const bone =
typeof part.bone === 'string' ? SkeletonBone[part.bone] : part.bone;
return new ChangeSkeletonConfigRequestT(bone, part.value);
});
}
type SkeletonBoneKey = keyof typeof SkeletonBone;
interface SkeletonConfigExport {
version?: number;
skeletonParts: {
bone: SkeletonBoneKey | SkeletonBone;
value: number;
}[];
}

12
package-lock.json generated
View File

@@ -33,10 +33,10 @@
"@tauri-apps/plugin-shell": "github:tauri-apps/tauri-plugin-shell#v2",
"@tauri-apps/plugin-window": "github:tauri-apps/tauri-plugin-window#v2",
"@vitejs/plugin-react": "^3.0.0",
"browser-fs-access": "^0.34.1",
"browserslist": "^4.18.1",
"classnames": "^2.3.1",
"eslint-config-react-app": "^7.0.0",
"file-saver": "^2.0.5",
"flatbuffers": "^22.10.26",
"identity-obj-proxy": "^3.0.0",
"intl-pluralrules": "^1.3.1",
@@ -4246,6 +4246,11 @@
"node": ">=8"
}
},
"node_modules/browser-fs-access": {
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.34.1.tgz",
"integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ=="
},
"node_modules/browserslist": {
"version": "4.21.10",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz",
@@ -5794,11 +5799,6 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"node_modules/filesize": {
"version": "8.0.7",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz",