Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 12 additions & 33 deletions src/keyboard/Keyboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ import { BehaviorBindingPicker } from "../behaviors/BehaviorBindingPicker";
import { produce } from "immer";
import { LockStateContext } from "../rpc/LockStateContext";
import { LockState } from "@zmkfirmware/zmk-studio-ts-client/core";
import { deserializeLayoutZoom, LayoutZoom } from "./PhysicalLayout";
import { useLocalStorageState } from "../misc/useLocalStorageState";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useLocalStorageState was created for future local storage uses. I don't think we should remove it #31 (comment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restored useLocalStorageState, i slightly modified the hook to have default serialize+deserializer, imho, these options should only be required for specific serialize+deserializer functions. lmk what you think ^^

import { KeyboardViewport } from "./KeyboardViewport";

type BehaviorMap = Record<number, GetBehaviorDetailsResponse>;

Expand Down Expand Up @@ -174,10 +173,6 @@ export default function Keyboard() {
true
);

const [keymapScale, setKeymapScale] = useLocalStorageState<LayoutZoom>("keymapScale", "auto", {
deserialize: deserializeLayoutZoom,
});

const [selectedLayerIndex, setSelectedLayerIndex] = useState<number>(0);
const [selectedKeyPosition, setSelectedKeyPosition] = useState<
number | undefined
Expand Down Expand Up @@ -520,33 +515,17 @@ export default function Keyboard() {
)}
</div>
{layouts && keymap && behaviors && (
<div className="p-2 col-start-2 row-start-1 grid items-center justify-center relative min-w-0">
<KeymapComp
keymap={keymap}
layout={layouts[selectedPhysicalLayoutIndex]}
behaviors={behaviors}
scale={keymapScale}
selectedLayerIndex={selectedLayerIndex}
selectedKeyPosition={selectedKeyPosition}
onKeyPositionClicked={setSelectedKeyPosition}
/>
<select
className="absolute top-2 right-2 h-8 rounded px-2"
value={keymapScale}
onChange={(e) => {
const value = deserializeLayoutZoom(e.target.value);
setKeymapScale(value);
}}
>
<option value="auto">Auto</option>
<option value={0.25}>25%</option>
<option value={0.5}>50%</option>
<option value={0.75}>75%</option>
<option value={1}>100%</option>
<option value={1.25}>125%</option>
<option value={1.5}>150%</option>
<option value={2}>200%</option>
</select>
<div className="col-start-2 row-start-1 flex items-center justify-center relative min-w-0">
<KeyboardViewport>
<KeymapComp
keymap={keymap}
layout={layouts[selectedPhysicalLayoutIndex]}
behaviors={behaviors}
selectedLayerIndex={selectedLayerIndex}
selectedKeyPosition={selectedKeyPosition}
onKeyPositionClicked={setSelectedKeyPosition}
/>
</KeyboardViewport>
</div>
)}
{keymap && selectedBinding && (
Expand Down
153 changes: 153 additions & 0 deletions src/keyboard/KeyboardViewport.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { FC, PropsWithChildren, useEffect, useRef } from "react";

type KeyboardViewportType = PropsWithChildren<{
className?: string;
}>;

const KEYMAP_SCALE = "keymap:scale";
const DEFAULT_SCALE = window.localStorage.getItem(KEYMAP_SCALE) ?? "1";

export const KeyboardViewport: FC<KeyboardViewportType> = ({
children,
className,
}) => {
const targetRef = useRef<HTMLDivElement>(null);
const scaleRef = useRef<HTMLInputElement>(null);

const setScale = (param: "increase" | "decrease") => {
if (!targetRef.current || !scaleRef.current) return;

const current = scaleRef.current.value;

if (param === "increase" && Number(current) < 2) {
scaleRef.current.value = String(Number(scaleRef.current.value) + 0.2);
}

if (param === "decrease" && Number(current) > 0.2) {
scaleRef.current.value = String(Number(scaleRef.current.value) - 0.2);
}

localStorage.setItem(KEYMAP_SCALE, scaleRef.current.value);
targetRef.current.style.setProperty(
"transform",
`scale(${scaleRef.current.value})`,
);
};

const resetScale = () => {
if (!targetRef.current || !scaleRef.current) return;
targetRef.current.style.translate = "unset";
targetRef.current.style.setProperty("transform", "scale(1)");
scaleRef.current.value = "1";
localStorage.setItem(KEYMAP_SCALE, "1");
};

useEffect(() => {
if (!targetRef.current) return;

const target = targetRef.current;
const offset = { x: 0, y: 0 };
let isPanningActive = false;

function panStart(e: KeyboardEvent) {
if (e.key !== " ") return;
e.preventDefault();

target.style.cursor = "grab";
isPanningActive = true;
}

function panEnd(e: KeyboardEvent) {
if (e.key !== " ") return;
isPanningActive = false;
target.style.cursor = "unset";
}

function panMove(e: PointerEvent) {
if (!isPanningActive) return;
offset.x += e.movementX;
offset.y += e.movementY;
target.style.translate = `${offset.x}px ${offset.y}px`;
}

document.addEventListener("keydown", panStart);
document.addEventListener("keyup", panEnd);
target.addEventListener("pointermove", panMove);

return () => {
document.removeEventListener("keydown", panStart);
document.removeEventListener("keyup", panEnd);
target.removeEventListener("pointermove", panMove);
};
}, []);

useEffect(() => {
if (!scaleRef.current || !targetRef.current) return;

const input = scaleRef.current;
const target = targetRef.current;

input.value = DEFAULT_SCALE;
target.style.setProperty("transform", `scale(${DEFAULT_SCALE})`);

function onInputChange(e: Event) {
const value = (e.currentTarget as HTMLInputElement).value;
target.style.setProperty("transform", `scale(${value})`);
localStorage.setItem(KEYMAP_SCALE, value);
}

input.addEventListener("change", onInputChange);
return () => {
input.removeEventListener("change", onInputChange);
};
}, []);

return (
<div
className={[
"relative size-full overflow-hidden p-0 touch-none",
className,
].join(" ")}
>
<div
ref={targetRef}
className="flex size-full origin-center items-center justify-center transition-transform"
>
{children}
</div>

<div className="absolute bottom-[10px] left-1/2 ml-[-170px] flex justify-center items-center w-[298px] gap-1 rounded-xl bg-muted py-1 select-none bg-base-300">
<button
className="block px-4 py-1.5 bg-base-100 rounded-l-lg"
onClick={() => setScale("decrease")}
>
-
</button>
<div className="flex h-9 px-2 justify-center items-center bg-base-100">
<input
type="range"
name="scale"
min={0.25}
max={2}
step={0.01}
ref={scaleRef}
defaultValue={DEFAULT_SCALE}
className="mx-auto h-1 w-28 cursor-pointer appearance-none rounded-lg"
/>
</div>
<button
className="block px-4 py-1.5 bg-base-100"
onClick={() => setScale("increase")}
>
+
</button>
<button
className="block px-4 py-1.5 bg-base-100 rounded-r-lg"
onClick={resetScale}
>
Auto
</button>
</div>
</div>
);
};
2 changes: 1 addition & 1 deletion src/keyboard/Keymap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface KeymapProps {
layout: PhysicalLayout;
keymap: KeymapMsg;
behaviors: BehaviorMap;
scale: LayoutZoom;
scale?: LayoutZoom;
selectedLayerIndex: number;
selectedKeyPosition: number | undefined;
onKeyPositionClicked: (keyPosition: number) => void;
Expand Down
38 changes: 0 additions & 38 deletions src/keyboard/PhysicalLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
CSSProperties,
PropsWithChildren,
useLayoutEffect,
useRef,
useState,
} from "react";
import { Key } from "./Key";

Expand Down Expand Up @@ -78,41 +76,6 @@ export const PhysicalLayout = ({
...props
}: PhysicalLayoutProps) => {
const ref = useRef<HTMLDivElement>(null);
const [scale, setScale] = useState(1);

useLayoutEffect(() => {
const element = ref.current;
if (!element) return;

const parent = element.parentElement;
if (!parent) return;

const calculateScale = () => {
if (props.zoom === "auto") {
const padding = Math.min(window.innerWidth, window.innerHeight) * 0.05; // Padding when in auto mode
const newScale = Math.min(
parent.clientWidth / (element.clientWidth + 2 * padding),
parent.clientHeight / (element.clientHeight + 2 * padding),
);
setScale(newScale);
} else {
setScale(props.zoom || 1);
}
};

calculateScale(); // Initial calculation

const resizeObserver = new ResizeObserver(() => {
calculateScale();
});

resizeObserver.observe(element);
resizeObserver.observe(parent);

return () => {
resizeObserver.disconnect();
};
}, [props.zoom]);

// TODO: Add a bit of padding for rotation when supported
let rightMost = positions
Expand Down Expand Up @@ -145,7 +108,6 @@ export const PhysicalLayout = ({
style={{
height: bottomMost * oneU + "px",
width: rightMost * oneU + "px",
transform: `scale(${scale})`,
}}
ref={ref}
{...props}
Expand Down