mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-09 19:42:48 +00:00
feat: t4g support
This commit is contained in:
232
src/routes/(app)/e2e/+page.svelte.wip
Normal file
232
src/routes/(app)/e2e/+page.svelte.wip
Normal file
@@ -0,0 +1,232 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
compileLayout,
|
||||
type VisualLayout,
|
||||
} from "$lib/serialization/visual-layout";
|
||||
import ccxLayout from "$lib/assets/layouts/generic/103-key.yml";
|
||||
import keycodes from "./keycodes.json";
|
||||
|
||||
let width = $state(16);
|
||||
let height = $state(16);
|
||||
|
||||
let layout = $state(compileLayout(ccxLayout as VisualLayout));
|
||||
let layoutMargin = $state(0.2);
|
||||
|
||||
let timelineCanvas = $state<HTMLCanvasElement | undefined>(undefined);
|
||||
|
||||
interface Report {
|
||||
modifiers?: number;
|
||||
keys?: number[];
|
||||
}
|
||||
|
||||
interface Tick {
|
||||
ms?: number;
|
||||
reports?: Report[];
|
||||
keys?: number[];
|
||||
}
|
||||
|
||||
let test: Tick[] = $state([
|
||||
{ ms: 1, reports: [{ keys: [4] }], keys: [4] },
|
||||
{ ms: 2, reports: [{ keys: [4, 2] }], keys: [4, 12] },
|
||||
]);
|
||||
|
||||
function timelineData<T extends { ms: number }>(
|
||||
ticks: T[],
|
||||
value: (tick: T) => number[],
|
||||
) {
|
||||
let totalTicks = 0;
|
||||
const result = new Map<number, [number, number][]>();
|
||||
for (const tick of ticks) {
|
||||
const key = value(tick);
|
||||
}
|
||||
}
|
||||
|
||||
let timelineData = $derived.by(() => {
|
||||
const result = new Map<number, [number, number][]>();
|
||||
for (const tick of test) {
|
||||
if (!tick.keys) continue;
|
||||
if (Array.isArray(action)) {
|
||||
if (typeof action[0] === "number") {
|
||||
ticks.push([action[0]]);
|
||||
totalTicks++;
|
||||
} else if (action.length === 0) {
|
||||
ticks.push([1]);
|
||||
totalTicks++;
|
||||
}
|
||||
}
|
||||
if (typeof action !== "number") continue;
|
||||
if (action >= 0) {
|
||||
if (!result.has(action)) {
|
||||
result.set(action, []);
|
||||
}
|
||||
result.get(action)!.push([totalTicks, test.length - 1]);
|
||||
} else {
|
||||
const value = result.get(~action)?.at(-1);
|
||||
if (!value || value[1] !== test.length - 1) continue;
|
||||
value[1] = totalTicks;
|
||||
}
|
||||
}
|
||||
return {
|
||||
totalTicks,
|
||||
ticks,
|
||||
presses: [...result.entries()].sort(([a], [b]) => a - b),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1>E2E Testing</h1>
|
||||
|
||||
{#snippet Layout(keys: Set<number>)}
|
||||
<svg viewBox="0 0 {layout.size[0]} {layout.size[1]}">
|
||||
{#each layout.keys as key}
|
||||
{#if key.shape === "square"}
|
||||
<rect
|
||||
x={key.pos[0] + layoutMargin / 2}
|
||||
y={key.pos[1] + layoutMargin / 2}
|
||||
rx={0.5 - layoutMargin / 2}
|
||||
width={key.size[0] - layoutMargin}
|
||||
height={key.size[1] - layoutMargin}
|
||||
fill={keys.has(key.id)
|
||||
? "var(--md-sys-color-primary)"
|
||||
: "var(--md-sys-color-on-surface)"}
|
||||
opacity={keys.has(key.id) ? 1 : 0.1}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</svg>
|
||||
{/snippet}
|
||||
|
||||
<canvas bind:this={timelineCanvas}></canvas>
|
||||
|
||||
<div class="t">
|
||||
{#each test as { ms, reports, keys }}
|
||||
<div class="tick">
|
||||
{ms}ms
|
||||
<div class="keys">
|
||||
{#each keys ?? [] as key}
|
||||
<kbd>{keycodes[key] ?? key}</kbd>
|
||||
{/each}
|
||||
<button class="icon">+</button>
|
||||
</div>
|
||||
{@render Layout(new Set(keys))}
|
||||
{#each reports ?? [] as report}
|
||||
<div class="report">
|
||||
<div class="modifiers">{report.modifiers}</div>
|
||||
<div class="keys">
|
||||
{#each report.keys ?? [] as key}
|
||||
<kbd>{keycodes[key] ?? key}</kbd>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
{#each test as action, i}
|
||||
{@const isActionTick = Array.isArray(action)}
|
||||
{@const isActionPress = typeof action === "number" && action >= 0}
|
||||
{@const isActionRelease = typeof action === "number" && action < 0}
|
||||
{#if isActionTick}
|
||||
<div class="tick">
|
||||
<span class="icon">step_over</span>
|
||||
{action[0]}ms
|
||||
</div>
|
||||
{#if action[1]}
|
||||
<div class="report">
|
||||
{#each Array.from({ length: 8 }) as _, j}
|
||||
<div class="modifier">{j}</div>
|
||||
{/each}
|
||||
{#each action[1][1] as key}
|
||||
<div class="key">
|
||||
{key}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{:else if typeof action === "string"}
|
||||
<div>Command: {action}</div>
|
||||
{:else if isActionPress}
|
||||
<button class="release" onclick={() => (test[i] = ~action)}
|
||||
>{action}</button
|
||||
>
|
||||
{:else if isActionRelease}
|
||||
<button class="press" onclick={() => (test[i] = ~action)}
|
||||
>{~action}</button
|
||||
>
|
||||
{:else}
|
||||
<div>Unsupported {action}</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
svg {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
$shadow-inset: 1px;
|
||||
|
||||
.timeline {
|
||||
display: grid;
|
||||
grid-template-rows: auto repeat(auto-fit, minmax(var(--height), 1fr));
|
||||
}
|
||||
|
||||
.timeline-press {
|
||||
margin-inline: calc(var(--width) / 2);
|
||||
border-radius: calc(var(--height) / 2);
|
||||
background-color: var(--md-sys-color-surface-variant);
|
||||
height: var(--height);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tick {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: ew-resize;
|
||||
padding: 0.5rem;
|
||||
user-select: none;
|
||||
|
||||
span.icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
aspect-ratio: 1;
|
||||
user-select: none;
|
||||
|
||||
&.release {
|
||||
box-shadow:
|
||||
inset #{$shadow-inset} #{$shadow-inset} #{$shadow-inset * 2}
|
||||
rgba(0, 0, 0, 0.6),
|
||||
inset -#{$shadow-inset} -#{$shadow-inset} #{$shadow-inset * 2}
|
||||
rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&.press {
|
||||
box-shadow:
|
||||
#{$shadow-inset} #{$shadow-inset} #{$shadow-inset * 2}
|
||||
rgba(0, 0, 0, 0.6),
|
||||
-#{$shadow-inset} -#{$shadow-inset} #{$shadow-inset * 2}
|
||||
rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
251
src/routes/(app)/e2e/keycodes.json
Normal file
251
src/routes/(app)/e2e/keycodes.json
Normal file
@@ -0,0 +1,251 @@
|
||||
[
|
||||
"reserved",
|
||||
"esc",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"0",
|
||||
"-",
|
||||
"=",
|
||||
"bksp",
|
||||
"tab",
|
||||
"q",
|
||||
"w",
|
||||
"e",
|
||||
"r",
|
||||
"t",
|
||||
"y",
|
||||
"u",
|
||||
"i",
|
||||
"o",
|
||||
"p",
|
||||
"[",
|
||||
"]",
|
||||
"enter",
|
||||
"lctrl",
|
||||
"a",
|
||||
"s",
|
||||
"d",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"j",
|
||||
"k",
|
||||
"l",
|
||||
";",
|
||||
"'",
|
||||
"`",
|
||||
"lshift",
|
||||
"\\",
|
||||
"z",
|
||||
"x",
|
||||
"c",
|
||||
"v",
|
||||
"b",
|
||||
"n",
|
||||
"m",
|
||||
",",
|
||||
".",
|
||||
"/",
|
||||
"rshift",
|
||||
"kp*",
|
||||
"lalt",
|
||||
"_",
|
||||
"capslock",
|
||||
"f1",
|
||||
"f2",
|
||||
"f3",
|
||||
"f4",
|
||||
"f5",
|
||||
"f6",
|
||||
"f7",
|
||||
"f8",
|
||||
"f9",
|
||||
"f10",
|
||||
"numlock",
|
||||
"scrolllock",
|
||||
"kp7",
|
||||
"kp8",
|
||||
"kp9",
|
||||
"kp-",
|
||||
"kp4",
|
||||
"kp5",
|
||||
"kp6",
|
||||
"kp+",
|
||||
"kp1",
|
||||
"kp2",
|
||||
"kp3",
|
||||
"kp0",
|
||||
"kp.",
|
||||
"ksc_84",
|
||||
"zenkaku_hankaku",
|
||||
"102nd",
|
||||
"f11",
|
||||
"f12",
|
||||
"ro",
|
||||
"katakana",
|
||||
"hiragana",
|
||||
"henkan",
|
||||
"katakana_hiragana",
|
||||
"muhenkan",
|
||||
"kp,",
|
||||
"kp_enter",
|
||||
"rctrl",
|
||||
"kp/",
|
||||
"sysrq",
|
||||
"ralt",
|
||||
"linefeed",
|
||||
"home",
|
||||
"up",
|
||||
"pageup",
|
||||
"left",
|
||||
"right",
|
||||
"end",
|
||||
"down",
|
||||
"pagedown",
|
||||
"insert",
|
||||
"delete",
|
||||
"macro",
|
||||
"mute",
|
||||
"volume_down",
|
||||
"volume_up",
|
||||
"power",
|
||||
"kp=",
|
||||
"kp+-",
|
||||
"pause",
|
||||
"scale",
|
||||
"kp,",
|
||||
"hangeul",
|
||||
"hanja",
|
||||
"yen",
|
||||
"lmeta",
|
||||
"rmeta",
|
||||
"compose",
|
||||
"stop",
|
||||
"again",
|
||||
"props",
|
||||
"undo",
|
||||
"front",
|
||||
"copy",
|
||||
"open",
|
||||
"paste",
|
||||
"find",
|
||||
"cut",
|
||||
"help",
|
||||
"menu",
|
||||
"calc",
|
||||
"setup",
|
||||
"sleep",
|
||||
"wakeup",
|
||||
"file",
|
||||
"sendfile",
|
||||
"deletefile",
|
||||
"xfer",
|
||||
"prog1",
|
||||
"prog2",
|
||||
"www",
|
||||
"msdos",
|
||||
"coffee",
|
||||
"rotate_display",
|
||||
"cyclewindows",
|
||||
"mail",
|
||||
"bookmarks",
|
||||
"computer",
|
||||
"back",
|
||||
"forward",
|
||||
"close_cd",
|
||||
"eject_cd",
|
||||
"eject_close_cd",
|
||||
"next_song",
|
||||
"play_pause",
|
||||
"prev_song",
|
||||
"stop_cd",
|
||||
"record",
|
||||
"rewind",
|
||||
"phone",
|
||||
"iso",
|
||||
"config",
|
||||
"homepage",
|
||||
"refresh",
|
||||
"exit",
|
||||
"move",
|
||||
"edit",
|
||||
"scroll_up",
|
||||
"scroll_down",
|
||||
"kp_left_paren",
|
||||
"kp_right_paren",
|
||||
"new",
|
||||
"redo",
|
||||
"f13",
|
||||
"f14",
|
||||
"f15",
|
||||
"f16",
|
||||
"f17",
|
||||
"f18",
|
||||
"f19",
|
||||
"f20",
|
||||
"f21",
|
||||
"f22",
|
||||
"f23",
|
||||
"f24",
|
||||
"sc_195",
|
||||
"sc_196",
|
||||
"sc_197",
|
||||
"sc_198",
|
||||
"sc_199",
|
||||
"play_cd",
|
||||
"pause_cd",
|
||||
"prog3",
|
||||
"prog4",
|
||||
"all_applications",
|
||||
"suspend",
|
||||
"close",
|
||||
"play",
|
||||
"fastforward",
|
||||
"bass_boost",
|
||||
"print",
|
||||
"hp",
|
||||
"camera",
|
||||
"sound",
|
||||
"question",
|
||||
"email",
|
||||
"chat",
|
||||
"search",
|
||||
"connect",
|
||||
"finance",
|
||||
"sport",
|
||||
"shop",
|
||||
"alterase",
|
||||
"cancel",
|
||||
"brightness_down",
|
||||
"brightness_up",
|
||||
"media",
|
||||
"switch_video_mode",
|
||||
"kbd_illum_toggle",
|
||||
"kbd_illum_down",
|
||||
"kbd_illum_up",
|
||||
"send",
|
||||
"reply",
|
||||
"forward_mail",
|
||||
"save",
|
||||
"documents",
|
||||
"battery",
|
||||
"bluetooth",
|
||||
"wlan",
|
||||
"uwb",
|
||||
"unknown",
|
||||
"video_next",
|
||||
"video_prev",
|
||||
"brightness_cycle",
|
||||
"brightness_auto",
|
||||
"display_off",
|
||||
"wwan",
|
||||
"rfkill",
|
||||
"mic_mute"
|
||||
]
|
||||
Reference in New Issue
Block a user