Skip to content

Commit 11db1d8

Browse files
committed
✨ add multiple layout support
1 parent cbf4a30 commit 11db1d8

15 files changed

Lines changed: 505 additions & 181 deletions

eslint.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import { includeIgnoreFile } from "@eslint/compat"
33
import js from "@eslint/js"
44
import prettierRecommended from "eslint-plugin-prettier/recommended"
55
import svelte from "eslint-plugin-svelte"
6+
import { defineConfig } from "eslint/config"
67
import globals from "globals"
78
import ts from "typescript-eslint"
89
import svelteConfig from "./svelte.config.js"
910

1011
const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url))
1112

12-
export default ts.config(
13+
export default defineConfig(
1314
includeIgnoreFile(gitignorePath),
1415
js.configs.recommended,
1516
...ts.configs.recommended,

src/lib/components/keyboard-editor/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import { ResizableHandle as Handle } from "../ui/resizable"
1717
import Container from "./keyboard-editor-container.svelte"
1818
import Keyboard from "./keyboard-editor-keyboard.svelte"
19+
import LayoutDialog from "./keyboard-editor-layout-dialog.svelte"
1920
import Menubar from "./keyboard-editor-menubar.svelte"
2021
import Pane from "./keyboard-editor-pane.svelte"
2122
import Root from "./keyboard-editor.svelte"
@@ -27,11 +28,13 @@ export {
2728
Handle,
2829
Keyboard,
2930
Menubar,
31+
LayoutDialog,
3032
//
3133
Root as KeyboardEditor,
3234
Pane as KeyboardEditorPane,
3335
Container as KeyboardEditorContainer,
3436
Handle as KeyboardEditorHandle,
3537
Keyboard as KeyboardEditorKeyboard,
3638
Menubar as KeyboardEditorMenubar,
39+
LayoutDialog as KeyboardEditorLayoutDialog,
3740
}

src/lib/components/keyboard-editor/keyboard-editor-keyboard.svelte

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,53 +14,32 @@ this program. If not, see <https://www.gnu.org/licenses/>.
1414
-->
1515

1616
<script lang="ts">
17-
import { keyboardContext } from "$lib/keyboard"
18-
import type { KeyboardLayout } from "$lib/keyboard/metadata"
17+
import {
18+
displayLayoutContext,
19+
type DisplayLayout,
20+
} from "$lib/configurator/context.svelte"
1921
import { unitToEM, unitToStyle } from "$lib/ui"
2022
import { cn, type WithoutChildren } from "$lib/utils"
2123
import type { Snippet } from "svelte"
2224
import type { HTMLAttributes } from "svelte/elements"
2325
2426
const {
2527
class: className,
26-
layout = keyboardContext.get().metadata.layout,
28+
displayLayout = displayLayoutContext.get(),
2729
keyGenerator,
2830
...props
2931
}: WithoutChildren<HTMLAttributes<HTMLDivElement>> & {
30-
layout?: KeyboardLayout
32+
displayLayout?: DisplayLayout
3133
keyGenerator?: Snippet<[number]>
3234
} = $props()
3335
3436
let containerWidth = $state(0)
3537
let containerHeight = $state(0)
3638
37-
const { keyboardWidth, keyboardHeight, coordinates } = $derived.by(() => {
38-
const ret = {
39-
keyboardWidth: 0,
40-
keyboardHeight: 0,
41-
coordinates: [] as { x: number; y: number }[],
42-
}
43-
const position = { x: 0, y: 0 }
44-
45-
for (const row of layout) {
46-
for (const { w, h, x, y } of row) {
47-
position.x += x
48-
position.y += y
49-
ret.keyboardWidth = Math.max(ret.keyboardWidth, position.x + w)
50-
ret.keyboardHeight = Math.max(ret.keyboardHeight, position.y + h)
51-
ret.coordinates.push({ ...position })
52-
position.x += w
53-
}
54-
position.x = 0
55-
position.y++
56-
}
57-
58-
return ret
59-
})
6039
const fontSize = $derived(
6140
Math.min(
62-
containerWidth / unitToEM(keyboardWidth),
63-
containerHeight / unitToEM(keyboardHeight),
41+
containerWidth / unitToEM(displayLayout.width),
42+
containerHeight / unitToEM(displayLayout.height),
6443
),
6544
)
6645
</script>
@@ -76,13 +55,10 @@ this program. If not, see <https://www.gnu.org/licenses/>.
7655
<div class="absolute inset-0 grid place-items-center">
7756
<div
7857
class="relative"
79-
style={unitToStyle(keyboardWidth, keyboardHeight)}
58+
style={unitToStyle(displayLayout.width, displayLayout.height)}
8059
>
81-
{#each layout.flat() as { key, w, h }, i (i)}
82-
<div
83-
class="absolute p-0.5"
84-
style={unitToStyle(w, h, coordinates[i].x, coordinates[i].y)}
85-
>
60+
{#each displayLayout.displayKeys as { key, w, h, x, y }, i (i)}
61+
<div class="absolute p-0.5" style={unitToStyle(w, h, x, y)}>
8662
{@render keyGenerator?.(key)}
8763
</div>
8864
{/each}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<!--
2+
This program is free software: you can redistribute it and/or modify it under
3+
the terms of the GNU General Public License as published by the Free Software
4+
Foundation, either version 3 of the License, or (at your option) any later
5+
version.
6+
7+
This program is distributed in the hope that it will be useful, but WITHOUT
8+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9+
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10+
details.
11+
12+
You should have received a copy of the GNU General Public License along with
13+
this program. If not, see <https://www.gnu.org/licenses/>.
14+
-->
15+
16+
<script lang="ts">
17+
import { WrenchIcon } from "@lucide/svelte"
18+
import { persistedStateContext } from "$lib/configurator/context.svelte"
19+
import { keyboardContext } from "$lib/keyboard"
20+
import type { WithoutChildrenOrChild } from "$lib/utils"
21+
import type { ComponentProps } from "svelte"
22+
import Switch from "../switch.svelte"
23+
import { buttonVariants } from "../ui/button"
24+
import { Label } from "../ui/label"
25+
import * as Popover from "../ui/popover"
26+
import * as Select from "../ui/select"
27+
28+
const {
29+
...props
30+
}: WithoutChildrenOrChild<ComponentProps<typeof Popover.Trigger>> = $props()
31+
32+
const persistedState = persistedStateContext.get()
33+
const { layoutOptions } = $derived(persistedState.current)
34+
const { labels } = keyboardContext.get().metadata.layout
35+
</script>
36+
37+
<Popover.Root>
38+
<Popover.Trigger class={buttonVariants({ size: "icon", variant: "outline" })}>
39+
<WrenchIcon />
40+
<span class="sr-only">Configure Layout</span>
41+
</Popover.Trigger>
42+
<Popover.Content align="end" class="flex flex-col gap-4">
43+
{#each labels as label, i (i)}
44+
{#if typeof label === "string"}
45+
<Switch
46+
bind:checked={
47+
() => layoutOptions[i] === 1,
48+
(v) => (persistedState.current.layoutOptions[i] = v ? 1 : 0)
49+
}
50+
id={label}
51+
title={label}
52+
/>
53+
{:else}
54+
<div class="flex flex-col gap-1.5">
55+
<div class="flex">
56+
<Label for={label[0]}>{label[0]}</Label>
57+
</div>
58+
<Select.Root
59+
bind:value={
60+
() => String(layoutOptions[i]),
61+
(v) => (persistedState.current.layoutOptions[i] = Number(v))
62+
}
63+
type="single"
64+
>
65+
<Select.Trigger id={label[0]} class="w-full" size="sm">
66+
<span class="flex items-center gap-2">
67+
{label[layoutOptions[i] + 1]}
68+
</span>
69+
</Select.Trigger>
70+
<Select.Content>
71+
{#each label.slice(1) as option, j (j)}
72+
<Select.Item value={String(j)}>{option}</Select.Item>
73+
{/each}
74+
</Select.Content>
75+
</Select.Root>
76+
</div>
77+
{/if}
78+
{/each}
79+
</Popover.Content>
80+
</Popover.Root>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Popover as PopoverPrimitive } from "bits-ui"
2+
import Content from "./popover-content.svelte"
3+
import Trigger from "./popover-trigger.svelte"
4+
5+
const Root = PopoverPrimitive.Root
6+
const Close = PopoverPrimitive.Close
7+
8+
export {
9+
Root,
10+
Content,
11+
Trigger,
12+
Close,
13+
//
14+
Root as Popover,
15+
Content as PopoverContent,
16+
Trigger as PopoverTrigger,
17+
Close as PopoverClose,
18+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script lang="ts">
2+
import { cn } from "$lib/utils.js"
3+
import { Popover as PopoverPrimitive } from "bits-ui"
4+
5+
let {
6+
ref = $bindable(null),
7+
class: className,
8+
sideOffset = 4,
9+
align = "center",
10+
portalProps,
11+
...restProps
12+
}: PopoverPrimitive.ContentProps & {
13+
portalProps?: PopoverPrimitive.PortalProps
14+
} = $props()
15+
</script>
16+
17+
<PopoverPrimitive.Portal {...portalProps}>
18+
<PopoverPrimitive.Content
19+
bind:ref
20+
data-slot="popover-content"
21+
{sideOffset}
22+
{align}
23+
class={cn(
24+
"z-50 w-72 origin-(--bits-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
25+
className,
26+
)}
27+
{...restProps}
28+
/>
29+
</PopoverPrimitive.Portal>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script lang="ts">
2+
import { cn } from "$lib/utils.js"
3+
import { Popover as PopoverPrimitive } from "bits-ui"
4+
5+
let {
6+
ref = $bindable(null),
7+
class: className,
8+
...restProps
9+
}: PopoverPrimitive.TriggerProps = $props()
10+
</script>
11+
12+
<PopoverPrimitive.Trigger
13+
bind:ref
14+
data-slot="popover-trigger"
15+
class={cn("", className)}
16+
{...restProps}
17+
/>

src/lib/configurator/advanced-keys/advanced-keys-menubar.svelte

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,18 @@ this program. If not, see <https://www.gnu.org/licenses/>.
1414
-->
1515

1616
<script lang="ts">
17-
import { KeyboardEditorMenubar } from "$lib/components/keyboard-editor"
17+
import * as KeyboardEditor from "$lib/components/keyboard-editor"
1818
import LayerSelect from "$lib/components/layer-select.svelte"
1919
import { advancedKeysStateContext } from "../context.svelte"
2020
2121
const advancedKeysState = advancedKeysStateContext.get()
2222
const { layer, index, create } = $derived(advancedKeysState)
2323
</script>
2424

25-
<KeyboardEditorMenubar>
25+
<KeyboardEditor.Menubar>
2626
<LayerSelect
2727
disabled={index !== null || create !== null}
2828
bind:layer={() => layer, (v) => advancedKeysState.setLayer(v)}
2929
/>
30-
</KeyboardEditorMenubar>
30+
<KeyboardEditor.LayoutDialog />
31+
</KeyboardEditor.Menubar>

0 commit comments

Comments
 (0)