mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-20 00:43:04 +00:00
feat: 4th layer support
This commit is contained in:
28
src/routes/recipes/+page.svelte
Normal file
28
src/routes/recipes/+page.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import Recipe from "./Recipe.svelte";
|
||||
|
||||
const tests = import.meta.glob("$lib/assets/tests/**/*.yml");
|
||||
</script>
|
||||
|
||||
<h1>Recipes</h1>
|
||||
|
||||
<p>These are example uses, taken directly from our E2E testing library.</p>
|
||||
|
||||
<div class="recipes">
|
||||
{#each Object.entries(tests) as [path, resolver]}
|
||||
{#await resolver() then module}
|
||||
<section>
|
||||
<Recipe test={module.default} />
|
||||
</section>
|
||||
{/await}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.recipes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
148
src/routes/recipes/Recipe.svelte
Normal file
148
src/routes/recipes/Recipe.svelte
Normal file
@@ -0,0 +1,148 @@
|
||||
<script lang="ts">
|
||||
import CharRecorder from "$lib/charrecorder/CharRecorder.svelte";
|
||||
import type { Replay } from "$lib/charrecorder/core/types";
|
||||
import type { E2eTest } from "./test-types";
|
||||
|
||||
let { test }: { test: E2eTest } = $props();
|
||||
|
||||
const replace = new Map<string, string>([
|
||||
["SPACE", "Space"],
|
||||
["ENTER", "Enter"],
|
||||
["BKSP", "Backspace"],
|
||||
["e", "KeyE"],
|
||||
["t", "KeyT"],
|
||||
]);
|
||||
|
||||
const replaceOut = new Map<string, string>([
|
||||
["SPACE", " "],
|
||||
["ENTER", "\n"],
|
||||
["BKSP", "Backspace"],
|
||||
]);
|
||||
|
||||
let timescale = $state(10);
|
||||
let idleHold = $state(500);
|
||||
let replayDelay = $state(1000);
|
||||
|
||||
let replay: Replay = $derived.by(() => {
|
||||
const replay: Replay = {
|
||||
start: 0,
|
||||
finish: 0,
|
||||
keys: [],
|
||||
};
|
||||
let timeIndex = 0;
|
||||
let held = new Map<string, any>();
|
||||
for (const it of test.test) {
|
||||
if (it.keys) {
|
||||
for (const key of it.keys) {
|
||||
if (held.has(key)) continue;
|
||||
held.set(key, [
|
||||
replaceOut.get(key) ?? key,
|
||||
replace.get(key) ?? key,
|
||||
timeIndex,
|
||||
0,
|
||||
]);
|
||||
replay.keys.push(held.get(key));
|
||||
}
|
||||
for (const [key, value] of held) {
|
||||
if (!it.keys.includes(key)) {
|
||||
value[3] = timeIndex - value[2];
|
||||
held.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
timeIndex += timescale * (it.step ?? 1);
|
||||
if (it.idle) {
|
||||
timeIndex += idleHold;
|
||||
}
|
||||
}
|
||||
|
||||
replay.finish = timeIndex;
|
||||
return replay;
|
||||
});
|
||||
|
||||
let graph = $derived.by(() => {
|
||||
const rows: string[][] = [[]];
|
||||
for (const it of test.test) {
|
||||
if (it.keys?.includes("BKSP")) {
|
||||
if (rows.at(-1)!.at(-1) === " ") {
|
||||
rows.at(-1)!.pop();
|
||||
} else {
|
||||
rows.push(Array.from({ length: rows.at(-1)!.length - 1 }, () => " "));
|
||||
}
|
||||
} else {
|
||||
for (const key of it.keys ?? []) {
|
||||
if (key === "SPACE") {
|
||||
rows.at(-1)!.push("␣");
|
||||
} else if (key === "ENTER") {
|
||||
rows.at(-1)!.push("↵");
|
||||
} else {
|
||||
rows.at(-1)!.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
});
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<div class="replay">
|
||||
<CharRecorder
|
||||
{replay}
|
||||
cursor={true}
|
||||
ondone={() => setTimeout(() => (replay = { ...replay }), replayDelay)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary>Breakdown</summary>
|
||||
<div class="graph">
|
||||
{#each graph as row, i}
|
||||
{#each row as char, j}
|
||||
{#if char !== " "}
|
||||
<div
|
||||
class:deleted={(graph[i + 1]?.findIndex((it) => it !== " ") ??
|
||||
Infinity) <= j}
|
||||
style:grid-row={i + 1}
|
||||
style:grid-column={j + 1}
|
||||
>
|
||||
{char}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
section {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
margin-top: 0.5rem;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.replay {
|
||||
background: #f0f0f0;
|
||||
padding: 0.6rem;
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.graph {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.deleted {
|
||||
opacity: 0.6;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
</style>
|
||||
20
src/routes/recipes/test-types.ts
Normal file
20
src/routes/recipes/test-types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export interface E2eAddChord {
|
||||
input: string[][];
|
||||
output: string[];
|
||||
}
|
||||
|
||||
export interface E2eTestItem {
|
||||
keys?: string[];
|
||||
modifiers?: Record<string, boolean>;
|
||||
press?: string[];
|
||||
release?: string[];
|
||||
step?: number;
|
||||
idle?: boolean;
|
||||
clearChords?: boolean;
|
||||
addChords?: E2eAddChord[];
|
||||
settings: Record<string, Record<string, string | number>>;
|
||||
}
|
||||
|
||||
export interface E2eTest {
|
||||
test: E2eTestItem[];
|
||||
}
|
||||
Reference in New Issue
Block a user