diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 200a1665..a12d8f7e 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -6,11 +6,24 @@ module.exports = { "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended", "plugin:storybook/recommended", + "plugin:tailwindcss/recommended", ], ignorePatterns: ["dist", ".eslintrc.cjs"], parser: "@typescript-eslint/parser", plugins: ["react-refresh"], rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], "react-refresh/only-export-components": [ "warn", { allowConstantExport: true }, diff --git a/.prettierrc b/.prettierrc index 0967ef42..32d463b8 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1 +1,4 @@ -{} +{ + "tabWidth": 2, + "semi": true +} diff --git a/package-lock.json b/package-lock.json index 314418dc..31d59f03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "eslint-plugin-storybook": "^0.8.0", + "eslint-plugin-tailwindcss": "^3.17.5", "postcss": "^8.4.38", "prettier": "3.3.2", "run-script-os": "^1.1.6", @@ -6460,6 +6461,23 @@ "node": ">=4.0" } }, + "node_modules/eslint-plugin-tailwindcss": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.17.5.tgz", + "integrity": "sha512-8Mi7p7dm+mO1dHgRHHFdPu4RDTBk69Cn4P0B40vRQR+MrguUpwmKwhZy1kqYe3Km8/4nb+cyrCF+5SodOEmaow==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.5", + "postcss": "^8.4.4" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "tailwindcss": "^3.4.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "dev": true, @@ -15294,6 +15312,16 @@ } } }, + "eslint-plugin-tailwindcss": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.17.5.tgz", + "integrity": "sha512-8Mi7p7dm+mO1dHgRHHFdPu4RDTBk69Cn4P0B40vRQR+MrguUpwmKwhZy1kqYe3Km8/4nb+cyrCF+5SodOEmaow==", + "dev": true, + "requires": { + "fast-glob": "^3.2.5", + "postcss": "^8.4.4" + } + }, "eslint-scope": { "version": "7.2.2", "dev": true, diff --git a/package.json b/package.json index 68d2f2ac..f4dd8d5b 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "eslint-plugin-storybook": "^0.8.0", + "eslint-plugin-tailwindcss": "^3.17.5", "postcss": "^8.4.38", "prettier": "3.3.2", "run-script-os": "^1.1.6", diff --git a/src/AboutModal.tsx b/src/AboutModal.tsx index 8ecf5940..73bf1397 100644 --- a/src/AboutModal.tsx +++ b/src/AboutModal.tsx @@ -179,8 +179,8 @@ export const AboutModal = ({ open, onClose }: AboutModalProps) => { const ref = useModalRef(open, true); return ( - -
+ +

The ZMK Project:{" "} website,{" "} @@ -193,20 +193,20 @@ export const AboutModal = ({ open, onClose }: AboutModalProps) => {

-

+

ZMK Studio is made possible thanks to the generous donation of time from our contributors, as well as the financial sponsorship from the following vendors:

-
+
{sponsors.map((s) => { const heightVariants = { [SponsorSize.Large]: "h-16", @@ -218,7 +218,7 @@ export const AboutModal = ({ open, onClose }: AboutModalProps) => {
diff --git a/src/App.tsx b/src/App.tsx index e09ed0cb..c5b96b38 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,7 +21,7 @@ import { } from "./tauri/serial"; import Keyboard from "./keyboard/Keyboard"; import { UndoRedoContext, useUndoRedo } from "./undoRedo"; -import { usePub, useSub } from "./usePubSub"; +import { emitter, useSub } from "./usePubSub"; import { LockState } from "@zmkfirmware/zmk-studio-ts-client/core"; import { LockStateContext } from "./rpc/LockStateContext"; import { UnlockModal } from "./UnlockModal"; @@ -68,19 +68,17 @@ const TRANSPORTS: TransportFactory[] = [ async function listen_for_notifications( notification_stream: ReadableStream, - signal: AbortSignal + signal: AbortSignal, ): Promise { - let reader = notification_stream.getReader(); + const reader = notification_stream.getReader(); const onAbort = () => { reader.cancel(); reader.releaseLock(); }; signal.addEventListener("abort", onAbort, { once: true }); do { - let pub = usePub(); - try { - let { done, value } = await reader.read(); + const { done, value } = await reader.read(); if (done) { break; } @@ -90,10 +88,10 @@ async function listen_for_notifications( } console.log("Notification", value); - pub("rpc_notification", value); + await emitter.emit("rpc_notification", value); const subsystem = Object.entries(value).find( - ([_k, v]) => v !== undefined + ([_k, v]) => v !== undefined, ); if (!subsystem) { continue; @@ -109,28 +107,29 @@ async function listen_for_notifications( const [eventName, eventData] = event; const topic = ["rpc_notification", subId, eventName].join("."); - pub(topic, eventData); + await emitter.emit(topic, eventData); } catch (e) { signal.removeEventListener("abort", onAbort); reader.releaseLock(); throw e; } + // eslint-disable-next-line no-constant-condition } while (true); signal.removeEventListener("abort", onAbort); reader.releaseLock(); - notification_stream.cancel(); + await notification_stream.cancel(); } async function connect( transport: RpcTransport, setConn: Dispatch, setConnectedDeviceName: Dispatch, - signal: AbortSignal + signal: AbortSignal, ) { - let conn = await create_rpc_connection(transport, { signal }); + const conn = create_rpc_connection(transport, { signal }); - let details = await Promise.race([ + const details = await Promise.race([ call_rpc(conn, { core: { getDeviceInfo: true } }) .then((r) => r?.core?.getDeviceInfo) .catch((e) => { @@ -171,12 +170,13 @@ function App() { const [connectionAbort, setConnectionAbort] = useState(new AbortController()); const [lockState, setLockState] = useState( - LockState.ZMK_STUDIO_CORE_LOCK_STATE_LOCKED + LockState.ZMK_STUDIO_CORE_LOCK_STATE_LOCKED, ); - useSub("rpc_notification.core.lockStateChanged", (ls) => { - setLockState(ls); - }); + useSub( + "rpc_notification.core.lockStateChanged", + (ls) => void setLockState(ls), + ); useEffect(() => { if (!conn) { @@ -189,18 +189,18 @@ function App() { return; } - let locked_resp = await call_rpc(conn.conn, { + const locked_resp = await call_rpc(conn.conn, { core: { getLockState: true }, }); setLockState( locked_resp.core?.getLockState || - LockState.ZMK_STUDIO_CORE_LOCK_STATE_LOCKED + LockState.ZMK_STUDIO_CORE_LOCK_STATE_LOCKED, ); } - updateLockState(); - }, [conn, setLockState]); + updateLockState().then(console.info).catch(console.error); + }, [conn, reset, setLockState]); const save = useCallback(() => { async function doSave() { @@ -208,13 +208,13 @@ function App() { return; } - let resp = await call_rpc(conn.conn, { keymap: { saveChanges: true } }); + const resp = await call_rpc(conn.conn, { keymap: { saveChanges: true } }); if (!resp.keymap?.saveChanges || resp.keymap?.saveChanges.err) { console.error("Failed to save changes", resp.keymap?.saveChanges); } } - doSave(); + doSave().then(console.info).catch(console.error); }, [conn]); const discard = useCallback(() => { @@ -223,7 +223,7 @@ function App() { return; } - let resp = await call_rpc(conn.conn, { + const resp = await call_rpc(conn.conn, { keymap: { discardChanges: true }, }); if (!resp.keymap?.discardChanges) { @@ -234,8 +234,8 @@ function App() { setConn({ conn: conn.conn }); } - doDiscard(); - }, [conn]); + doDiscard().then(console.info).catch(console.error); + }, [conn.conn, reset]); const resetSettings = useCallback(() => { async function doReset() { @@ -243,7 +243,7 @@ function App() { return; } - let resp = await call_rpc(conn.conn, { + const resp = await call_rpc(conn.conn, { core: { resetSettings: true }, }); if (!resp.core?.resetSettings) { @@ -254,8 +254,8 @@ function App() { setConn({ conn: conn.conn }); } - doReset(); - }, [conn]); + doReset().then(console.info).catch(console.error); + }, [conn.conn, reset]); const disconnect = useCallback(() => { async function doDisconnect() { @@ -268,16 +268,18 @@ function App() { setConnectionAbort(new AbortController()); } - doDisconnect(); - }, [conn]); + doDisconnect().then(console.info).catch(console.error); + }, [conn.conn, connectionAbort]); const onConnect = useCallback( (t: RpcTransport) => { const ac = new AbortController(); setConnectionAbort(ac); - connect(t, setConn, setConnectedDeviceName, ac.signal); + connect(t, setConn, setConnectedDeviceName, ac.signal) + .then(console.info) + .catch(console.error); }, - [setConn, setConnectedDeviceName, setConnectedDeviceName] + [setConn, setConnectedDeviceName], ); return ( @@ -295,7 +297,7 @@ function App() { open={showLicenseNotice} onClose={() => setShowLicenseNotice(false)} /> -
+
{ return ( -
+
© 2024 - The ZMK Contributors -{" "} - + About ZMK Studio {" "} -{" "} - + License NOTICE
diff --git a/src/AppHeader.tsx b/src/AppHeader.tsx index 52a6658d..8b34f906 100644 --- a/src/AppHeader.tsx +++ b/src/AppHeader.tsx @@ -52,21 +52,22 @@ export const AppHeader = ({ ) { setShowSettingsReset(false); } - }, [lockState, showSettingsReset]); + }, [connectionState.conn, lockState, showSettingsReset]); const showSettingsRef = useModalRef(showSettingsReset); const [unsaved, setUnsaved] = useConnectedDeviceData( { keymap: { checkUnsavedChanges: true } }, - (r) => r.keymap?.checkUnsavedChanges + (r) => r.keymap?.checkUnsavedChanges, ); - useSub("rpc_notification.keymap.unsavedChangesStatusChanged", (unsaved) => - setUnsaved(unsaved) + useSub( + "rpc_notification.keymap.unsavedChangesStatusChanged", + (unsaved) => void setUnsaved(unsaved), ); return ( -
-
+
+
ZMK Logo

Studio

@@ -78,15 +79,15 @@ export const AppHeader = ({ Studio and restore the stock keymap

Continue?

-
+
- + )} @@ -138,30 +139,30 @@ export const AppHeader = ({ {onRedo && ( )}
diff --git a/src/ConnectModal.tsx b/src/ConnectModal.tsx index e84d6ae9..96c6aa3f 100644 --- a/src/ConnectModal.tsx +++ b/src/ConnectModal.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; +import { FC, useCallback, useEffect, useMemo, useState } from "react"; import type { RpcTransport } from "@zmkfirmware/zmk-studio-ts-client/transport/index"; import { UserCancelledError } from "@zmkfirmware/zmk-studio-ts-client/transport/errors"; @@ -25,20 +25,20 @@ export interface ConnectModalProps { onTransportCreated: (t: RpcTransport) => void; } -function deviceList( - open: boolean, - transports: TransportFactory[], - onTransportCreated: (t: RpcTransport) => void -) { +const DeviceList: FC = ({ + open, + transports, + onTransportCreated, +}) => { const [devices, setDevices] = useState< Array<[TransportFactory, AvailableDevice]> >([]); const [selectedDev, setSelectedDev] = useState(new Set()); const [refreshing, setRefreshing] = useState(false); - async function LoadEm() { + const LoadEm = useCallback(async () => { setRefreshing(true); - let entries: Array<[TransportFactory, AvailableDevice]> = []; + const entries: Array<[TransportFactory, AvailableDevice]> = []; for (const t of transports.filter((t) => t.pick_and_connect)) { const devices = await t.pick_and_connect?.list(); if (!devices) { @@ -48,27 +48,27 @@ function deviceList( entries.push( ...devices.map<[TransportFactory, AvailableDevice]>((d) => { return [t, d]; - }) + }), ); } setDevices(entries); setRefreshing(false); - } + }, [transports]); useEffect(() => { setSelectedDev(new Set()); setDevices([]); - LoadEm(); - }, [transports, open, setDevices]); + LoadEm().then(console.info).catch(console.error); + }, [transports, open, setDevices, LoadEm]); const onRefresh = useCallback(() => { setSelectedDev(new Set()); setDevices([]); - LoadEm(); - }, [setDevices]); + LoadEm().then(console.info).catch(console.error); + }, [LoadEm]); const onSelect = useCallback( async (keys: Selection) => { @@ -83,7 +83,7 @@ function deviceList( .catch((e) => alert(e)); } }, - [devices, onTransportCreated] + [devices, onTransportCreated], ); return ( @@ -91,7 +91,7 @@ function deviceList(
); -} +}; -function simpleDevicePicker( - transports: TransportFactory[], - onTransportCreated: (t: RpcTransport) => void -) { +const SimpleDevicePicker: FC> = ({ + transports, + onTransportCreated, +}) => { const [availableDevices, setAvailableDevices] = useState< AvailableDevice[] | undefined >(undefined); @@ -146,50 +146,50 @@ function simpleDevicePicker( let ignore = false; - if (selectedTransport.connect) { - async function connectTransport() { - try { - const transport = await selectedTransport?.connect?.(); + async function connectTransport() { + try { + const transport = await selectedTransport?.connect?.(); - if (!ignore) { - if (transport) { - onTransportCreated(transport); - } - setSelectedTransport(undefined); + if (!ignore) { + if (transport) { + onTransportCreated(transport); } - } catch (e) { - if (!ignore) { - console.error(e); - if (e instanceof Error && !(e instanceof UserCancelledError)) { - alert(e.message); - } - setSelectedTransport(undefined); + setSelectedTransport(undefined); + } + } catch (e) { + if (!ignore) { + console.error(e); + if (e instanceof Error && !(e instanceof UserCancelledError)) { + alert(e.message); } + setSelectedTransport(undefined); } } + } - connectTransport(); - } else { - async function loadAvailableDevices() { - const devices = await selectedTransport?.pick_and_connect?.list(); + async function loadAvailableDevices() { + const devices = await selectedTransport?.pick_and_connect?.list(); - if (!ignore) { - setAvailableDevices(devices); - } + if (!ignore) { + setAvailableDevices(devices); } + } - loadAvailableDevices(); + if (selectedTransport.connect) { + connectTransport().then(console.info).catch(console.error); + return; } + loadAvailableDevices().then(console.info).catch(console.error); return () => { ignore = true; }; - }, [selectedTransport]); + }, [onTransportCreated, selectedTransport]); - let connections = transports.map((t) => ( + const connections = transports.map((t) => (
  • )); + return (

    Select a connection type.

    @@ -209,7 +210,7 @@ function simpleDevicePicker( className="m-1 p-1" onClick={async () => { onTransportCreated( - await selectedTransport!.pick_and_connect!.connect(d) + await selectedTransport!.pick_and_connect!.connect(d), ); setSelectedTransport(undefined); }} @@ -221,9 +222,9 @@ function simpleDevicePicker( )}
    ); -} +}; -function noTransportsOptionsPrompt() { +const NoTransportsOptionsPrompt: FC = () => { return (

    @@ -240,7 +241,7 @@ function noTransportsOptionsPrompt() {

    To use ZMK Studio, either:

    -
      +
      • Use a browser that supports the above web technologies, e.g. Chrome/Edge, or @@ -256,22 +257,24 @@ function noTransportsOptionsPrompt() {
    ); -} +}; -function connectOptions( - transports: TransportFactory[], - onTransportCreated: (t: RpcTransport) => void, - open?: boolean -) { - const useSimplePicker = useMemo( +const ConnectOptions: FC = ({ + open, + transports, + onTransportCreated, +}) => { + const simpleMode = useMemo( () => transports.every((t) => !t.pick_and_connect), - [transports] + [transports], ); - return useSimplePicker - ? simpleDevicePicker(transports, onTransportCreated) - : deviceList(open || false, transports, onTransportCreated); -} + if (simpleMode) { + return ; + } + + return ; +}; export const ConnectModal = ({ open, @@ -279,15 +282,16 @@ export const ConnectModal = ({ onTransportCreated, }: ConnectModalProps) => { const dialog = useModalRef(open || false, false, false); - const haveTransports = useMemo(() => transports.length > 0, [transports]); return (

    Welcome to ZMK Studio

    - {haveTransports - ? connectOptions(transports, onTransportCreated, open) - : noTransportsOptionsPrompt()} + {haveTransports ? ( + + ) : ( + + )}
    ); }; diff --git a/src/GenericModal.tsx b/src/GenericModal.tsx index 8aad01b6..66f4b9ac 100644 --- a/src/GenericModal.tsx +++ b/src/GenericModal.tsx @@ -11,7 +11,7 @@ export const GenericModal = React.forwardRef(({ onClose, children, className }: {children} diff --git a/src/UnlockModal.tsx b/src/UnlockModal.tsx index dd981239..6904ed25 100644 --- a/src/UnlockModal.tsx +++ b/src/UnlockModal.tsx @@ -20,14 +20,15 @@ export type TransportFactory = { export interface UnlockModalProps {} +// eslint-disable-next-line no-empty-pattern export const UnlockModal = ({}: UnlockModalProps) => { - let conn = useContext(ConnectionContext); - let lockState = useContext(LockStateContext); + const conn = useContext(ConnectionContext); + const lockState = useContext(LockStateContext); - let open = useMemo( + const open = useMemo( () => !!conn.conn && lockState != LockState.ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED, - [conn, lockState] + [conn, lockState], ); const dialog = useModalRef(open, false, false); diff --git a/src/behaviors/BehaviorBindingPicker.tsx b/src/behaviors/BehaviorBindingPicker.tsx index 8c3f54e7..bc225da1 100644 --- a/src/behaviors/BehaviorBindingPicker.tsx +++ b/src/behaviors/BehaviorBindingPicker.tsx @@ -19,7 +19,7 @@ function validateBinding( metadata: BehaviorBindingParametersSet[], layerIds: number[], param1?: number, - param2?: number + param2?: number, ): boolean { if ( (param1 === undefined || param1 === 0) && @@ -28,8 +28,8 @@ function validateBinding( return true; } - let matchingSet = metadata.find((s) => - validateValue(layerIds, param1, s.param1) + const matchingSet = metadata.find((s) => + validateValue(layerIds, param1, s.param1), ); if (!matchingSet) { @@ -51,12 +51,12 @@ export const BehaviorBindingPicker = ({ const metadata = useMemo( () => behaviors.find((b) => b.id == behaviorId)?.metadata, - [behaviorId, behaviors] + [behaviorId, behaviors], ); const sortedBehaviors = useMemo( () => behaviors.sort((a, b) => a.displayName.localeCompare(b.displayName)), - [behaviors] + [behaviors], ); useEffect(() => { @@ -71,7 +71,7 @@ export const BehaviorBindingPicker = ({ if (!metadata) { console.error( "Can't find metadata for the selected behaviorId", - behaviorId + behaviorId, ); return; } @@ -81,7 +81,7 @@ export const BehaviorBindingPicker = ({ metadata, layers.map(({ id }) => id), param1, - param2 + param2, ) ) { onBindingChanged({ @@ -90,6 +90,7 @@ export const BehaviorBindingPicker = ({ param2: param2 || 0, }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [behaviorId, param1, param2]); useEffect(() => { diff --git a/src/behaviors/HidUsagePicker.tsx b/src/behaviors/HidUsagePicker.tsx index 4cbf8566..ba473c95 100644 --- a/src/behaviors/HidUsagePicker.tsx +++ b/src/behaviors/HidUsagePicker.tsx @@ -38,13 +38,13 @@ type UsageSectionProps = HidUsagePage; const UsageSection = ({ id, min, max }: UsageSectionProps) => { const info = useMemo(() => hid_usage_page_get_ids(id), [id]); - let usages = useMemo(() => { + const usages = useMemo(() => { let usages = info?.UsageIds || []; if (max || min) { usages = usages.filter( (i) => (i.Id <= (max || Number.MAX_SAFE_INTEGER) && i.Id >= (min || 0)) || - (id === 7 && i.Id >= 0xe0 && i.Id <= 0xe7) + (id === 7 && i.Id >= 0xe0 && i.Id <= 0xe7), ); } @@ -57,8 +57,9 @@ const UsageSection = ({ id, min, max }: UsageSectionProps) => { {(i) => ( {i.Name} @@ -116,7 +117,7 @@ export const HidUsagePicker = ({ onValueChanged, }: HidUsagePickerProps) => { const mods = useMemo(() => { - let flags = value ? value >> 24 : 0; + const flags = value ? value >> 24 : 0; return all_mods.filter((m) => m & flags).map((m) => m.toLocaleString()); }, [value]); @@ -125,13 +126,13 @@ export const HidUsagePicker = ({ (e: Key | null) => { let value = typeof e == "number" ? e : undefined; if (value !== undefined) { - let mod_flags = mods_to_flags(mods.map((m) => parseInt(m))); + const mod_flags = mods_to_flags(mods.map((m) => parseInt(m))); value = value | (mod_flags << 24); } onValueChanged(value); }, - [onValueChanged, mods] + [onValueChanged, mods], ); const modifiersChanged = useCallback( @@ -140,15 +141,15 @@ export const HidUsagePicker = ({ return; } - let mod_flags = mods_to_flags(m.map((m) => parseInt(m))); - let new_value = mask_mods(value) | (mod_flags << 24); + const mod_flags = mods_to_flags(m.map((m) => parseInt(m))); + const new_value = mask_mods(value) | (mod_flags << 24); onValueChanged(new_value); }, - [value] + [onValueChanged, value], ); return ( -
    +
    {label && }
    - -
    - + {all_mods.map((m) => ( {mod_labels[m]} diff --git a/src/behaviors/ParameterValuePicker.tsx b/src/behaviors/ParameterValuePicker.tsx index ca0aaae4..f5c626e8 100644 --- a/src/behaviors/ParameterValuePicker.tsx +++ b/src/behaviors/ParameterValuePicker.tsx @@ -24,8 +24,10 @@ export const ParameterValuePicker = ({ className="h-8 rounded" onChange={(e) => onValueChanged(parseInt(e.target.value))} > - {values.map((v) => ( - + {values.map((v, i) => ( + ))}
    @@ -66,7 +68,9 @@ export const ParameterValuePicker = ({ onChange={(e) => onValueChanged(parseInt(e.target.value))} > {layers.map(({ name, id }) => ( - + ))}
    diff --git a/src/keyboard/HidUsageLabel.tsx b/src/keyboard/HidUsageLabel.tsx index aa15195c..dbf55e3e 100644 --- a/src/keyboard/HidUsageLabel.tsx +++ b/src/keyboard/HidUsageLabel.tsx @@ -12,20 +12,20 @@ function remove_prefix(s?: string) { } export const HidUsageLabel = ({ hid_usage }: HidUsageLabelProps) => { - let [page, id] = hid_usage_page_and_id_from_usage(hid_usage); + const [page, id] = hid_usage_page_and_id_from_usage(hid_usage); // TODO: Do something with implicit mods! - page &= 0xff; + let _page = page; - let labels = hid_usage_get_labels(page, id); + const labels = hid_usage_get_labels((_page &= 0xff), id); return ( ); diff --git a/src/keyboard/Key.tsx b/src/keyboard/Key.tsx index 80babd4d..94656997 100644 --- a/src/keyboard/Key.tsx +++ b/src/keyboard/Key.tsx @@ -33,7 +33,7 @@ interface KeyDimension { function makeSize( { width, height }: KeyDimension, - oneU: number + oneU: number, ): CSSProperties { width *= oneU; height *= oneU; @@ -56,7 +56,7 @@ export const Key = ({ const children = Children.map(props.children, (c) => (
    {c}
    @@ -64,7 +64,7 @@ export const Key = ({ return (
    20 ? "-md" : "" - } transition-all duration-100 m-auto p-0 b-0 box-border grid grid-rows-[0_var(--zmk-key-center-height)_0] grid-cols-[0_var(--zmk-key-center-width)_0] data-[zoomer=true]:hover:grid-rows-[1em_var(--zmk-key-center-height)_1em] data-[zoomer=true]:hover:grid-cols-[1em_var(--zmk-key-center-width)_1em] shadow-[0_0_0_1px_inset] shadow-base-content data-[zoomer=true]:shadow-base-200 data-[zoomer=true]:hover:shadow-base-content data-[zoomer=true]:hover:z-50 text-base-content bg-base-100 aria-selected:bg-primary aria-selected:text-primary-content grow @container`} + className={[ + oneU > 20 ? "rounded-md" : "rounded", + "m-auto box-border grid grow grid-cols-[0_var(--zmk-key-center-width)_0] grid-rows-[0_var(--zmk-key-center-height)_0] bg-base-100 p-0 text-base-content shadow-[0_0_0_1px_inset] shadow-base-content transition-all duration-100 @container aria-selected:bg-primary aria-selected:text-primary-content data-[zoomer=true]:shadow-base-200 data-[zoomer=true]:hover:z-50 data-[zoomer=true]:hover:grid-cols-[1em_var(--zmk-key-center-width)_1em] data-[zoomer=true]:hover:grid-rows-[1em_var(--zmk-key-center-height)_1em] data-[zoomer=true]:hover:shadow-base-content", + ].join(" ")} > {header && ( - + {header} )} diff --git a/src/keyboard/Keyboard.tsx b/src/keyboard/Keyboard.tsx index e8ffaf75..8b6fff8b 100644 --- a/src/keyboard/Keyboard.tsx +++ b/src/keyboard/Keyboard.tsx @@ -29,14 +29,18 @@ 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 { LayoutZoom } from "./PhysicalLayout"; import { useLocalStorageState } from "../misc/useLocalStorageState"; type BehaviorMap = Record; +function deserializeLayoutZoom(value: string): LayoutZoom { + return value === "auto" ? "auto" : parseFloat(value) || "auto"; +} + function useBehaviors(): BehaviorMap { - let connection = useContext(ConnectionContext); - let lockState = useContext(LockStateContext); + const connection = useContext(ConnectionContext); + const lockState = useContext(LockStateContext); const [behaviors, setBehaviors] = useState({}); @@ -56,25 +60,25 @@ function useBehaviors(): BehaviorMap { return; } - let get_behaviors: Request = { + const get_behaviors: Request = { behaviors: { listAllBehaviors: true }, requestId: 0, }; - let behavior_list = await call_rpc(connection.conn, get_behaviors); + const behavior_list = await call_rpc(connection.conn, get_behaviors); if (!ignore) { - let behavior_map: BehaviorMap = {}; - for (let behaviorId of behavior_list.behaviors?.listAllBehaviors + const behavior_map: BehaviorMap = {}; + for (const behaviorId of behavior_list.behaviors?.listAllBehaviors ?.behaviors || []) { if (ignore) { break; } - let details_req = { + const details_req = { behaviors: { getBehaviorDetails: { behaviorId } }, requestId: 0, }; - let behavior_details = await call_rpc(connection.conn, details_req); - let dets: GetBehaviorDetailsResponse | undefined = + const behavior_details = await call_rpc(connection.conn, details_req); + const dets: GetBehaviorDetailsResponse | undefined = behavior_details?.behaviors?.getBehaviorDetails; if (dets) { @@ -103,13 +107,13 @@ function useLayouts(): [ PhysicalLayout[] | undefined, React.Dispatch>, number, - React.Dispatch> + React.Dispatch>, ] { - let connection = useContext(ConnectionContext); - let lockState = useContext(LockStateContext); + const connection = useContext(ConnectionContext); + const lockState = useContext(LockStateContext); const [layouts, setLayouts] = useState( - undefined + undefined, ); const [selectedPhysicalLayoutIndex, setSelectedPhysicalLayoutIndex] = useState(0); @@ -130,14 +134,14 @@ function useLayouts(): [ return; } - let response = await call_rpc(connection.conn, { + const response = await call_rpc(connection.conn, { keymap: { getPhysicalLayouts: true }, }); if (!ignore) { setLayouts(response?.keymap?.getPhysicalLayouts?.layouts); setSelectedPhysicalLayoutIndex( - response?.keymap?.getPhysicalLayouts?.activeLayoutIndex || 0 + response?.keymap?.getPhysicalLayouts?.activeLayoutIndex || 0, ); } } @@ -171,12 +175,16 @@ export default function Keyboard() { console.log("Got the keymap!"); return keymap?.keymap?.getKeymap; }, - true + true, ); - const [keymapScale, setKeymapScale] = useLocalStorageState("keymapScale", "auto", { - deserialize: deserializeLayoutZoom, - }); + const [keymapScale, setKeymapScale] = useLocalStorageState( + "keymapScale", + "auto", + { + deserialize: deserializeLayoutZoom, + }, + ); const [selectedLayerIndex, setSelectedLayerIndex] = useState(0); const [selectedKeyPosition, setSelectedKeyPosition] = useState< @@ -198,27 +206,27 @@ export default function Keyboard() { return; } - let resp = await call_rpc(conn.conn, { + const resp = await call_rpc(conn.conn, { keymap: { setActivePhysicalLayout: selectedPhysicalLayoutIndex }, }); - let new_keymap = resp?.keymap?.setActivePhysicalLayout?.ok; + const new_keymap = resp?.keymap?.setActivePhysicalLayout?.ok; if (new_keymap) { setKeymap(new_keymap); } else { console.error( "Failed to set the active physical layout err:", - resp?.keymap?.setActivePhysicalLayout?.err + resp?.keymap?.setActivePhysicalLayout?.err, ); } } performSetRequest(); - }, [selectedPhysicalLayoutIndex]); + }, [conn.conn, layouts, selectedPhysicalLayoutIndex, setKeymap]); - let doSelectPhysicalLayout = useCallback( + const doSelectPhysicalLayout = useCallback( (i: number) => { - let oldLayout = selectedPhysicalLayoutIndex; + const oldLayout = selectedPhysicalLayoutIndex; undoRedo?.(async () => { setSelectedPhysicalLayoutIndex(i); @@ -227,14 +235,14 @@ export default function Keyboard() { }; }); }, - [undoRedo, selectedPhysicalLayoutIndex] + [selectedPhysicalLayoutIndex, undoRedo, setSelectedPhysicalLayoutIndex], ); - let doUpdateBinding = useCallback( + const doUpdateBinding = useCallback( (binding: BehaviorBinding) => { if (!keymap || selectedKeyPosition === undefined) { console.error( - "Can't update binding without a selected key position and loaded keymap" + "Can't update binding without a selected key position and loaded keymap", ); return; } @@ -248,7 +256,7 @@ export default function Keyboard() { throw new Error("Not connected"); } - let resp = await call_rpc(conn.conn, { + const resp = await call_rpc(conn.conn, { keymap: { setLayerBinding: { layerId, keyPosition, binding } }, }); @@ -257,9 +265,10 @@ export default function Keyboard() { SetLayerBindingResponse.SET_LAYER_BINDING_RESP_OK ) { setKeymap( + // eslint-disable-next-line @typescript-eslint/no-explicit-any produce((draft: any) => { draft.layers[layer].bindings[keyPosition] = binding; - }) + }), ); } else { console.error("Failed to set binding", resp.keymap?.setLayerBinding); @@ -270,7 +279,7 @@ export default function Keyboard() { return; } - let resp = await call_rpc(conn.conn, { + const resp = await call_rpc(conn.conn, { keymap: { setLayerBinding: { layerId, keyPosition, binding: oldBinding }, }, @@ -280,19 +289,26 @@ export default function Keyboard() { SetLayerBindingResponse.SET_LAYER_BINDING_RESP_OK ) { setKeymap( + // eslint-disable-next-line @typescript-eslint/no-explicit-any produce((draft: any) => { draft.layers[layer].bindings[keyPosition] = oldBinding; - }) + }), ); - } else { } }; }); }, - [conn, keymap, undoRedo, selectedLayerIndex, selectedKeyPosition] + [ + keymap, + selectedKeyPosition, + selectedLayerIndex, + undoRedo, + conn.conn, + setKeymap, + ], ); - let selectedBinding = useMemo(() => { + const selectedBinding = useMemo(() => { if (keymap == null || selectedKeyPosition == null) { return null; } @@ -307,7 +323,7 @@ export default function Keyboard() { return; } - let resp = await call_rpc(conn.conn, { + const resp = await call_rpc(conn.conn, { keymap: { moveLayer: { startIndex, destIndex } }, }); @@ -324,7 +340,7 @@ export default function Keyboard() { return () => doMove(end, start); }); }, - [undoRedo] + [conn.conn, setKeymap, undoRedo], ); const addLayer = useCallback(() => { @@ -338,10 +354,11 @@ export default function Keyboard() { if (resp.keymap?.addLayer?.ok) { const newSelection = keymap.layers.length; setKeymap( + // eslint-disable-next-line @typescript-eslint/no-explicit-any produce((draft: any) => { draft.layers.push(resp.keymap!.addLayer!.ok!.layer); draft.availableLayers--; - }) + }), ); setSelectedLayerIndex(newSelection); @@ -365,24 +382,25 @@ export default function Keyboard() { console.log(resp); if (resp.keymap?.removeLayer?.ok) { setKeymap( + // eslint-disable-next-line @typescript-eslint/no-explicit-any produce((draft: any) => { draft.layers.splice(layerIndex, 1); draft.availableLayers++; - }) + }), ); } else { console.error("Remove error", resp.keymap?.removeLayer?.err); throw new Error( - "Failed to remove layer:" + resp.keymap?.removeLayer?.err + "Failed to remove layer:" + resp.keymap?.removeLayer?.err, ); } } undoRedo?.(async () => { - let index = await doAdd(); + const index = await doAdd(); return () => doRemove(index); }); - }, [conn, undoRedo, keymap]); + }, [undoRedo, conn.conn, keymap, setKeymap]); const removeLayer = useCallback(() => { async function doRemove(layerIndex: number): Promise { @@ -399,15 +417,16 @@ export default function Keyboard() { setSelectedLayerIndex(layerIndex - 1); } setKeymap( + // eslint-disable-next-line @typescript-eslint/no-explicit-any produce((draft: any) => { draft.layers.splice(layerIndex, 1); draft.availableLayers++; - }) + }), ); } else { console.error("Remove error", resp.keymap?.removeLayer?.err); throw new Error( - "Failed to remove layer:" + resp.keymap?.removeLayer?.err + "Failed to remove layer:" + resp.keymap?.removeLayer?.err, ); } } @@ -424,16 +443,17 @@ export default function Keyboard() { console.log(resp); if (resp.keymap?.restoreLayer?.ok) { setKeymap( + // eslint-disable-next-line @typescript-eslint/no-explicit-any produce((draft: any) => { draft.layers.splice(atIndex, 0, resp!.keymap!.restoreLayer!.ok); draft.availableLayers--; - }) + }), ); setSelectedLayerIndex(atIndex); } else { console.error("Remove error", resp.keymap?.restoreLayer?.err); throw new Error( - "Failed to restore layer:" + resp.keymap?.restoreLayer?.err + "Failed to restore layer:" + resp.keymap?.restoreLayer?.err, ); } } @@ -442,13 +462,13 @@ export default function Keyboard() { throw new Error("No keymap loaded"); } - let index = selectedLayerIndex; - let layerId = keymap.layers[index].id; + const index = selectedLayerIndex; + const layerId = keymap.layers[index].id; undoRedo?.(async () => { await doRemove(index); return () => doRestore(layerId, index); }); - }, [conn, undoRedo, selectedLayerIndex]); + }, [keymap, selectedLayerIndex, undoRedo, conn.conn, setKeymap]); const changeLayerName = useCallback( (id: number, oldName: string, newName: string) => { @@ -466,16 +486,17 @@ export default function Keyboard() { SetLayerPropsResponse.SET_LAYER_PROPS_RESP_OK ) { setKeymap( + // eslint-disable-next-line @typescript-eslint/no-explicit-any produce((draft: any) => { const layer_index = draft.layers.findIndex( - (l: Layer) => l.id == layerId + (l: Layer) => l.id == layerId, ); draft.layers[layer_index].name = name; - }) + }), ); } else { throw new Error( - "Failed to change layer name:" + resp.keymap?.setLayerProps + "Failed to change layer name:" + resp.keymap?.setLayerProps, ); } } @@ -487,12 +508,12 @@ export default function Keyboard() { }; }); }, - [conn, undoRedo, keymap] + [undoRedo, conn.conn, setKeymap], ); return ( -
    -
    +
    +
    {layouts && (
    {layouts && keymap && behaviors && ( -
    +
    - - + {(l) => ( {l.name} -
    +
    {children} - + ); }; diff --git a/src/misc/LicenseNoticeModal.tsx b/src/misc/LicenseNoticeModal.tsx index 62500a65..ccfc3a83 100644 --- a/src/misc/LicenseNoticeModal.tsx +++ b/src/misc/LicenseNoticeModal.tsx @@ -15,20 +15,16 @@ export const LicenseNoticeModal = ({ const ref = useModalRef(open, true); return ( - +
    -
    +

    ZMK Studio is released under the open source Apache 2.0 license. A copy of the NOTICE file from the ZMK Studio repository is included here: