mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 01:38:44 +00:00
artcraft: move OnboardingHelper + Help icon kit into ui-pagescene
OnboardingHelper had been left in apps because it depended on the
Help.tsx icon kit (Key, KeyGroup, Mouse, Plus etc.) under
ControlsTopButtons/Help. Help.tsx itself has no host coupling --
it imports only @storyteller/ui-label -- so both move into the lib
together:
- src/lib/comps/ControlsTopButtons/Help/{Help,index}.tsx — keyboard
+ mouse icon visual primitives (Key, KeyGroup, Mouse, Plus,
ShortcutsGroup, Shortcut, Help itself).
- src/lib/comps/OnboardingHelper.tsx — the WASD/click hint that
fades out on first key/mouse interaction.
Apps copies become re-export shims. Public API exposes the icon
primitives so future host-side help overlays can reuse them.
Verified: artcraft typecheck at 70 errors (one fewer than the 71
prior; one of OnboardingHelper's pre-existing unused-import errors
disappeared with the migration).
This commit is contained in:
1 parent
e2f1b218bc
commit
e01b02e463
7 files changed
+267
-248
No files matched your search
+8
-144
@@ -1,144 +1,8 @@
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
|
||||
export const ShortcutsGroup = (props: {
|
||||
label: string;
|
||||
children?: React.ReactNode;
|
||||
}) => (
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label className="text-md font-semibold" {...props}>
|
||||
{props.label}
|
||||
</Label>
|
||||
<div className="relative flex flex-col gap-2.5">
|
||||
{props.children}
|
||||
<div className="absolute top-0 h-full w-0.5 bg-white/15" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Shortcut = (props: {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<div className="flex items-center gap-2 ps-4">
|
||||
<span className="text-md w-[280px] font-medium opacity-80">
|
||||
{props.label}
|
||||
</span>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Key = (props: { button: string }) => (
|
||||
<div className="flex h-[30px] w-auto min-w-[30px] items-center justify-center rounded-md border-b-2 border-[#9E9EA6] bg-white px-2 text-center text-sm font-bold text-ui-background">
|
||||
{props.button}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const KeyGroup = (props: { children: React.ReactNode }) => (
|
||||
<div className="flex gap-1">{props.children}</div>
|
||||
);
|
||||
|
||||
export const Mouse = (props: {
|
||||
button: "left" | "middle" | "right";
|
||||
label?: string;
|
||||
}) => (
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
alt={`${props.button} mouse`}
|
||||
src={`/resources/icons/mouse_${props.button === "left" ? "lmb" : props.button === "middle" ? "mmb" : "rmb"}.png`}
|
||||
className="h-7 w-auto object-contain"
|
||||
/>
|
||||
<span className="text-sm font-normal opacity-60">{props.label}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Plus = () => <div className="text-xl font-medium">+</div>;
|
||||
|
||||
const ShortcutsView = () => (
|
||||
<div className="grid select-none grid-cols-2 gap-12 ps-4 mt-6">
|
||||
<div className="flex flex-col gap-8">
|
||||
<ShortcutsGroup label="Navigation">
|
||||
<Shortcut label="Orbit View">
|
||||
<KeyGroup>
|
||||
<Mouse button="left" label="(Hold)" />
|
||||
</KeyGroup>
|
||||
</Shortcut>
|
||||
<Shortcut label="Forward Backward">
|
||||
<KeyGroup>
|
||||
<Key button="W" />
|
||||
<Key button="S" />
|
||||
</KeyGroup>
|
||||
</Shortcut>
|
||||
<Shortcut label="Left Right">
|
||||
<KeyGroup>
|
||||
<Key button="A" />
|
||||
<Key button="D" />
|
||||
</KeyGroup>
|
||||
</Shortcut>
|
||||
<Shortcut label="Up Down">
|
||||
<KeyGroup>
|
||||
<Key button="E" />
|
||||
<Key button="Q" />
|
||||
</KeyGroup>
|
||||
</Shortcut>
|
||||
<Shortcut label="Speed Boost">
|
||||
<KeyGroup>
|
||||
<Key button="Shift" />
|
||||
</KeyGroup>
|
||||
</Shortcut>
|
||||
</ShortcutsGroup>
|
||||
|
||||
<ShortcutsGroup label="Interaction">
|
||||
<Shortcut label="Select Object">
|
||||
<Mouse button="left" label="(Click)" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Delete Selection">
|
||||
<Key button="Del" />
|
||||
</Shortcut>
|
||||
</ShortcutsGroup>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-8">
|
||||
<ShortcutsGroup label="Shortcuts">
|
||||
<Shortcut label="Transform">
|
||||
<Key button="T" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Rotate">
|
||||
<Key button="R" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Scale">
|
||||
<Key button="G" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Focus">
|
||||
<Key button="F" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Copy">
|
||||
<Key button="Ctrl" />
|
||||
<Plus></Plus>
|
||||
<Key button="C" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Paste">
|
||||
<Key button="Ctrl" />
|
||||
<Plus></Plus>
|
||||
<Key button="Shift" />
|
||||
<Plus></Plus>
|
||||
<Key button="V" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Open 3D Asset Modal">
|
||||
<Key button="B" />
|
||||
</Shortcut>
|
||||
</ShortcutsGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
|
||||
export const Help = () => {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1">
|
||||
<ShortcutsView />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export {
|
||||
Help,
|
||||
Key,
|
||||
KeyGroup,
|
||||
Mouse,
|
||||
Plus,
|
||||
ShortcutsGroup,
|
||||
} from "@storyteller/ui-pagescene";
|
||||
@@ -1,103 +1 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { Key, KeyGroup, Mouse, Plus } from "./ControlsTopButtons/Help/Help";
|
||||
import { CloseButton } from "@storyteller/ui-close-button";
|
||||
|
||||
const STORAGE_KEY = "onboarding_helper_dismissed";
|
||||
const EXPIRATION_DAYS = 3;
|
||||
|
||||
export const OnboardingHelper = () => {
|
||||
const [actionPerformed, setActionPerformed] = useState(false);
|
||||
const [keyPressed, setKeyPressed] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if the helper was dismissed
|
||||
/*
|
||||
const dismissedData = localStorage.getItem(STORAGE_KEY);
|
||||
if (dismissedData) {
|
||||
const { timestamp } = JSON.parse(dismissedData);
|
||||
const expirationTime = timestamp + EXPIRATION_DAYS * 24 * 60 * 60 * 1000; // 3 days in milliseconds
|
||||
if (Date.now() < expirationTime) {
|
||||
setIsVisible(false);
|
||||
} else {
|
||||
// Clear expired data
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}, []);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsVisible(false);
|
||||
// Store dismissal timestamp
|
||||
localStorage.setItem(
|
||||
STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (["W", "A", "S", "D"].includes(event.key.toUpperCase())) {
|
||||
setTimeout(() => setKeyPressed(true), 200);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAction = () => {
|
||||
setTimeout(() => setActionPerformed(true), 200);
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
window.addEventListener("mousedown", handleAction);
|
||||
window.addEventListener("dragstart", handleAction);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
window.removeEventListener("mousedown", handleAction);
|
||||
window.removeEventListener("dragstart", handleAction);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={isVisible && !actionPerformed && !keyPressed}
|
||||
enter="transition-opacity duration-500"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-500"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="glass pointer-events-none absolute left-1/2 bottom-56 z-10 flex -translate-x-1/2 flex-col items-center justify-center gap-1 rounded-xl border border-brand-primary !bg-brand-primary/10 px-3 pr-4 py-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="scale-75 origin-center -mx-3">
|
||||
<KeyGroup>
|
||||
<Key button="W" />
|
||||
<Key button="A" />
|
||||
<Key button="S" />
|
||||
<Key button="D" />
|
||||
</KeyGroup>
|
||||
</div>
|
||||
<p className="text-sm font-semibold">Move</p>
|
||||
</div>
|
||||
<div className="h-4 w-[1px] bg-white/20" />
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="scale-75 origin-center -mx-1">
|
||||
<Mouse button="left" />
|
||||
</div>
|
||||
<p className="text-sm font-semibold">Look</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-auto absolute -top-2 -right-2">
|
||||
<CloseButton onClick={handleClose} className="h-5 w-5 bg-black/50 hover:bg-black/80 rounded-full border border-white/10" />
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
export default OnboardingHelper;
|
||||
export { OnboardingHelper, OnboardingHelper as default } from "@storyteller/ui-pagescene";
|
||||
@@ -1 +1 @@
|
||||
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.promise.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/@remix-run/node/dist/globals.d.ts","../../node_modules/@types/cookie/index.d.ts","../../node_modules/@remix-run/server-runtime/dist/crypto.d.ts","../../node_modules/@remix-run/server-runtime/dist/cookies.d.ts","../../node_modules/@remix-run/server-runtime/dLine truncated
|
||||
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.promise.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/@remix-run/node/dist/globals.d.ts","../../node_modules/@types/cookie/index.d.ts","../../node_modules/@remix-run/server-runtime/dist/crypto.d.ts","../../node_modules/@remix-run/server-runtime/dist/cookies.d.ts","../../node_modules/@remix-run/server-runtime/dLine truncated
|
||||
@@ -144,6 +144,15 @@ export { ControlPanelSceneObject } from "./lib/comps/ControlPanelSceneObject";
|
||||
export { SceneContainer } from "./lib/comps/SceneContainer";
|
||||
export { PreviewEngineCamera } from "./lib/comps/PreviewEngineCamera";
|
||||
export { PreviewImages } from "./lib/comps/PreviewImages";
|
||||
export { OnboardingHelper } from "./lib/comps/OnboardingHelper";
|
||||
export {
|
||||
Help,
|
||||
Key,
|
||||
KeyGroup,
|
||||
Mouse,
|
||||
Plus,
|
||||
ShortcutsGroup,
|
||||
} from "./lib/comps/ControlsTopButtons/Help/Help";
|
||||
|
||||
// Viewport-size hook (also useful to host code that wants the same
|
||||
// reactivity contract the lib comps use).
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
|
||||
export const ShortcutsGroup = (props: {
|
||||
label: string;
|
||||
children?: React.ReactNode;
|
||||
}) => (
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label className="text-md font-semibold" {...props}>
|
||||
{props.label}
|
||||
</Label>
|
||||
<div className="relative flex flex-col gap-2.5">
|
||||
{props.children}
|
||||
<div className="absolute top-0 h-full w-0.5 bg-white/15" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Shortcut = (props: {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<div className="flex items-center gap-2 ps-4">
|
||||
<span className="text-md w-[280px] font-medium opacity-80">
|
||||
{props.label}
|
||||
</span>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Key = (props: { button: string }) => (
|
||||
<div className="flex h-[30px] w-auto min-w-[30px] items-center justify-center rounded-md border-b-2 border-[#9E9EA6] bg-white px-2 text-center text-sm font-bold text-ui-background">
|
||||
{props.button}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const KeyGroup = (props: { children: React.ReactNode }) => (
|
||||
<div className="flex gap-1">{props.children}</div>
|
||||
);
|
||||
|
||||
export const Mouse = (props: {
|
||||
button: "left" | "middle" | "right";
|
||||
label?: string;
|
||||
}) => (
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
alt={`${props.button} mouse`}
|
||||
src={`/resources/icons/mouse_${props.button === "left" ? "lmb" : props.button === "middle" ? "mmb" : "rmb"}.png`}
|
||||
className="h-7 w-auto object-contain"
|
||||
/>
|
||||
<span className="text-sm font-normal opacity-60">{props.label}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Plus = () => <div className="text-xl font-medium">+</div>;
|
||||
|
||||
const ShortcutsView = () => (
|
||||
<div className="grid select-none grid-cols-2 gap-12 ps-4 mt-6">
|
||||
<div className="flex flex-col gap-8">
|
||||
<ShortcutsGroup label="Navigation">
|
||||
<Shortcut label="Orbit View">
|
||||
<KeyGroup>
|
||||
<Mouse button="left" label="(Hold)" />
|
||||
</KeyGroup>
|
||||
</Shortcut>
|
||||
<Shortcut label="Forward Backward">
|
||||
<KeyGroup>
|
||||
<Key button="W" />
|
||||
<Key button="S" />
|
||||
</KeyGroup>
|
||||
</Shortcut>
|
||||
<Shortcut label="Left Right">
|
||||
<KeyGroup>
|
||||
<Key button="A" />
|
||||
<Key button="D" />
|
||||
</KeyGroup>
|
||||
</Shortcut>
|
||||
<Shortcut label="Up Down">
|
||||
<KeyGroup>
|
||||
<Key button="E" />
|
||||
<Key button="Q" />
|
||||
</KeyGroup>
|
||||
</Shortcut>
|
||||
<Shortcut label="Speed Boost">
|
||||
<KeyGroup>
|
||||
<Key button="Shift" />
|
||||
</KeyGroup>
|
||||
</Shortcut>
|
||||
</ShortcutsGroup>
|
||||
|
||||
<ShortcutsGroup label="Interaction">
|
||||
<Shortcut label="Select Object">
|
||||
<Mouse button="left" label="(Click)" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Delete Selection">
|
||||
<Key button="Del" />
|
||||
</Shortcut>
|
||||
</ShortcutsGroup>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-8">
|
||||
<ShortcutsGroup label="Shortcuts">
|
||||
<Shortcut label="Transform">
|
||||
<Key button="T" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Rotate">
|
||||
<Key button="R" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Scale">
|
||||
<Key button="G" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Focus">
|
||||
<Key button="F" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Copy">
|
||||
<Key button="Ctrl" />
|
||||
<Plus></Plus>
|
||||
<Key button="C" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Paste">
|
||||
<Key button="Ctrl" />
|
||||
<Plus></Plus>
|
||||
<Key button="Shift" />
|
||||
<Plus></Plus>
|
||||
<Key button="V" />
|
||||
</Shortcut>
|
||||
<Shortcut label="Open 3D Asset Modal">
|
||||
<Key button="B" />
|
||||
</Shortcut>
|
||||
</ShortcutsGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
|
||||
export const Help = () => {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1">
|
||||
<ShortcutsView />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Help } from "./Help";
|
||||
@@ -0,0 +1,103 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { Key, KeyGroup, Mouse, Plus } from "./ControlsTopButtons/Help/Help";
|
||||
import { CloseButton } from "@storyteller/ui-close-button";
|
||||
|
||||
const STORAGE_KEY = "onboarding_helper_dismissed";
|
||||
const EXPIRATION_DAYS = 3;
|
||||
|
||||
export const OnboardingHelper = () => {
|
||||
const [actionPerformed, setActionPerformed] = useState(false);
|
||||
const [keyPressed, setKeyPressed] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if the helper was dismissed
|
||||
/*
|
||||
const dismissedData = localStorage.getItem(STORAGE_KEY);
|
||||
if (dismissedData) {
|
||||
const { timestamp } = JSON.parse(dismissedData);
|
||||
const expirationTime = timestamp + EXPIRATION_DAYS * 24 * 60 * 60 * 1000; // 3 days in milliseconds
|
||||
if (Date.now() < expirationTime) {
|
||||
setIsVisible(false);
|
||||
} else {
|
||||
// Clear expired data
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}, []);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsVisible(false);
|
||||
// Store dismissal timestamp
|
||||
localStorage.setItem(
|
||||
STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (["W", "A", "S", "D"].includes(event.key.toUpperCase())) {
|
||||
setTimeout(() => setKeyPressed(true), 200);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAction = () => {
|
||||
setTimeout(() => setActionPerformed(true), 200);
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
window.addEventListener("mousedown", handleAction);
|
||||
window.addEventListener("dragstart", handleAction);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
window.removeEventListener("mousedown", handleAction);
|
||||
window.removeEventListener("dragstart", handleAction);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={isVisible && !actionPerformed && !keyPressed}
|
||||
enter="transition-opacity duration-500"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-500"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="glass pointer-events-none absolute left-1/2 bottom-56 z-10 flex -translate-x-1/2 flex-col items-center justify-center gap-1 rounded-xl border border-brand-primary !bg-brand-primary/10 px-3 pr-4 py-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="scale-75 origin-center -mx-3">
|
||||
<KeyGroup>
|
||||
<Key button="W" />
|
||||
<Key button="A" />
|
||||
<Key button="S" />
|
||||
<Key button="D" />
|
||||
</KeyGroup>
|
||||
</div>
|
||||
<p className="text-sm font-semibold">Move</p>
|
||||
</div>
|
||||
<div className="h-4 w-[1px] bg-white/20" />
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="scale-75 origin-center -mx-1">
|
||||
<Mouse button="left" />
|
||||
</div>
|
||||
<p className="text-sm font-semibold">Look</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-auto absolute -top-2 -right-2">
|
||||
<CloseButton onClick={handleClose} className="h-5 w-5 bg-black/50 hover:bg-black/80 rounded-full border border-white/10" />
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
export default OnboardingHelper;
|
||||
Reference in new issue
Block a user