diff --git a/src/lib/assets/quater-ring.svg b/src/lib/assets/quater-ring.svg
deleted file mode 100644
index c3566641..00000000
--- a/src/lib/assets/quater-ring.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
\ No newline at end of file
diff --git a/src/lib/assets/settings.yml b/src/lib/assets/settings.yml
new file mode 100644
index 00000000..6e11f464
--- /dev/null
+++ b/src/lib/assets/settings.yml
@@ -0,0 +1,118 @@
+settings:
+ 1:
+ title: Enable Serial Header
+ description: boolean 0 or 1, default is 0
+ 2:
+ title: Enable Serial Logging
+ description: boolean 0 or 1, default is 0
+ 3:
+ title: Enable Serial Debugging
+ description: boolean 0 or 1, default is 0
+ 4:
+ title: Enable Serial Raw
+ description: boolean 0 or 1, default is 0
+ 5:
+ title: Enable Serial Chord
+ description: boolean 0 or 1, default is 0
+ 6:
+ title: Enable Serial Keyboard
+ description: boolean 0 or 1, default is 0
+ 7:
+ title: Enable Serial Mouse
+ description: boolean 0 or 1, default is 0
+ 11:
+ title: Enable USB HID Keyboard
+ description: boolean 0 or 1, default is 1
+ 12:
+ title: Enable Character Entry
+ description: boolean 0 or 1
+ 13:
+ title: GUI-CTRL Swap Mode
+ description: boolean 0 or 1; 1 swaps keymap 0 and 1. (CCL only)
+ 14:
+ title: Key Scan Duration
+ description: scan rate described in milliseconds; default is 2ms = 500Hz
+ 15:
+ title: Key Debounce Press Duration
+ description: debounce time in milliseconds; default is 7ms on the One and 20ms on the Lite
+ 16:
+ title: Key Debounce Release Duration
+ description: debounce time in milliseconds; default is 7ms on the One and 20ms on the Lite
+ 17:
+ title: Keyboard Output Character Microsecond Delays
+ description: delay time in microseconds (one delay for press and again for release); default is 480us; max is 10240us; increments of 40us
+ 21:
+ title: Enable USB HID Mouse
+ description: boolean 0 or 1; default is 1
+ 22:
+ title: Slow Mouse Speed
+ description: pixels to move at the mouse poll rate; default for CC1 is 5 = 250px/s
+ 23:
+ title: Fast Mouse Speed
+ description: pixels to move at the mouse poll rate; default for CC1 is 25 = 1250px/s
+ 24:
+ title: Enable Active Mouse
+ description: boolean 0 or 1; moves mouse back and forth every 60s
+ 25:
+ title: Mouse Scroll Speed
+ description: default is 1; polls at 1/4th the rate of the mouse move updates
+ 26:
+ title: Mouse Poll Duration
+ description: poll rate described in milliseconds; default is 20ms = 50Hz
+ 31:
+ title: Enable Chording
+ description: boolean 0 or 1
+ 32:
+ title: Enable Chording Character Counter Timeout
+ description: boolean 0 or 1; default is 1
+ 33:
+ title: Chording Character Counter Timeout Timer
+ description: 0-255 deciseconds; default is 40 or 4.0 seconds
+ 34:
+ title: Chord Detection Press Tolerance(ms)
+ description: 1-50 milliseconds
+ 35:
+ title: Chord Detection Release Tolerance(ms)
+ description: 1-50 milliseconds
+ 41:
+ title: Enable Spurring
+ description: boolean 0 or 1; default is 1
+ 42:
+ title: Enable Spurring Character Counter Timeout
+ description: boolean 0 or 1; default is 1
+ 43:
+ title: Spurring Character Counter Timeout Timer
+ description: 0-255 seconds; default is 240
+ 51:
+ title: Enable Arpeggiates
+ description: boolean 0 or 1; default is 1
+ 54:
+ title: Arpeggiate Tolerance
+ description: in milliseconds; default 800ms
+ 61:
+ title: Enable Compound Chording (coming soon)
+ description: boolean 0 or 1; default is 0
+ 64:
+ title: Compound Tolerance
+ description: in milliseconds; default 1500ms
+ 81:
+ title: LED Brightness
+ description: 0-50 (CCL only); default is 5, which draws around 100 mA of current
+ 82:
+ title: LED Color Code
+ description: Color Codes to be listed (CCL only)
+ 83:
+ title: Enable LED Key Highlight (coming soon)
+ description: boolean 0 or 1 (CCL only)
+ 84:
+ title: Enable LEDs
+ description: boolean 0 or 1; default is 1 (CCL only)
+ 91:
+ title: Operating System
+ description: Operating system codes listed below
+ 92:
+ title: Enable Realtime Feedback
+ description: boolean 0 or 1; default is 1
+ 93:
+ title: Enable CharaChorder Ready on startup
+ description: boolean 0 or 1; default is 1
diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/lib/components/layout/GenericLayout.svelte b/src/lib/components/layout/GenericLayout.svelte
index 44ce670d..5e2cd5c6 100644
--- a/src/lib/components/layout/GenericLayout.svelte
+++ b/src/lib/components/layout/GenericLayout.svelte
@@ -1,7 +1,7 @@
-
-
- {#each $layout as layer, i}
-
- | counter_{i + 1} |
-
-
- {/each}
-
diff --git a/src/lib/components/layout/KeyText.svelte b/src/lib/components/layout/KeyText.svelte
index 78c3578d..c209ec86 100644
--- a/src/lib/components/layout/KeyText.svelte
+++ b/src/lib/components/layout/KeyText.svelte
@@ -1,10 +1,10 @@
{#each positions as position, layer}
- {@const [action, changed] = getActions(layer, key.id, $layout, $changes)}
+ {@const {action: actionId, isApplied} = $layout[layer][key.id]}
+ {@const {code, icon, id} = KEYMAP_CODES[actionId] ?? {code: actionId}}
{@const isActive = layer === $activeLayer}
{@const direction = [(middle[0] - margin * 3) / position[0], (middle[1] - margin * 3) / position[1]]}
- {#if action.code !== 0}
- {action.icon || action.id || `0x${action.code?.toString(16)}`}
+ {#if code !== 0}
+ {icon || id || `0x${code.toString(16)}`}
{/if}
- {#if changed}
+ {#if !isApplied}
•
{/if}
diff --git a/src/lib/components/layout/KeyboardKey.svelte b/src/lib/components/layout/KeyboardKey.svelte
index 9df065d0..6e1bc436 100644
--- a/src/lib/components/layout/KeyboardKey.svelte
+++ b/src/lib/components/layout/KeyboardKey.svelte
@@ -44,7 +44,6 @@
{@const multiplier = 1.25}
- import RingInput from "$lib/components/layout/RingInput.svelte"
-
- export let activeLayer = 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/lib/components/layout/RingInput.svelte b/src/lib/components/layout/RingInput.svelte
deleted file mode 100644
index 054d0d5f..00000000
--- a/src/lib/components/layout/RingInput.svelte
+++ /dev/null
@@ -1,186 +0,0 @@
-
-
-
- {#each [keys.n, keys.e, keys.s, keys.w, keys.d] as id, quadrant}
- {@const actions = getActions(id, $layout, $changes)}
-
- {/each}
-
-
-
diff --git a/src/lib/components/layout/get-actions.ts b/src/lib/components/layout/get-actions.ts
deleted file mode 100644
index 97a95825..00000000
--- a/src/lib/components/layout/get-actions.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type {CharaLayout} from "$lib/serialization/layout"
-import type {Change} from "$lib/serial/connection"
-import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
-import type {KeyInfo} from "$lib/serial/keymap-codes"
-
-export function getActions(
- layer: number,
- id: number,
- layout: CharaLayout,
- changes: Change[],
-): [KeyInfo, boolean] {
- const actionId = layout?.[layer][id]
- const changedId = changes.findLast(it => it?.layout?.[layer]?.[id] !== undefined)?.layout?.[layer]?.[id]
- if (changedId !== undefined) {
- return [KEYMAP_CODES[changedId] ?? {code: changedId}, true]
- } else {
- return [KEYMAP_CODES[actionId] ?? {code: actionId}, false]
- }
-}
diff --git a/src/lib/editable-layout.ts b/src/lib/editable-layout.ts
deleted file mode 100644
index 649b36be..00000000
--- a/src/lib/editable-layout.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import type {Action} from "svelte/action"
-import ActionSelector from "$lib/components/layout/ActionSelector.svelte"
-import {changes, layout} from "$lib/serial/connection"
-import {get} from "svelte/store"
-
-export const editableLayout: Action = (
- node,
- {id, activeLayer},
-) => {
- let component: ActionSelector | undefined
- function present() {
- component?.$destroy()
- component = new ActionSelector({
- target: document.body,
- props: {currentAction: get(layout)[activeLayer][id]},
- })
- component.$on("close", () => {
- component!.$destroy()
- })
- component.$on("select", ({detail}) => {
- changes.update(changes => {
- changes.push({layout: {[activeLayer]: {[id]: detail}}})
- return changes
- })
- component!.$destroy()
- })
- }
-
- node.addEventListener("click", present)
- return {
- destroy() {
- node.removeEventListener("click", present)
- },
- }
-}
diff --git a/src/lib/serial/connection.ts b/src/lib/serial/connection.ts
index eda23baa..70872e7d 100644
--- a/src/lib/serial/connection.ts
+++ b/src/lib/serial/connection.ts
@@ -5,6 +5,7 @@ import type {Writable} from "svelte/store"
import type {CharaLayout} from "$lib/serialization/layout"
import {persistentWritable} from "$lib/storage"
import {userPreferences} from "$lib/preferences"
+import settingInfo from "$lib/assets/settings.yml"
export const serialPort = writable()
@@ -15,27 +16,32 @@ export interface SerialLogEntry {
export const serialLog = writable([])
-export const chords = persistentWritable("chord-library", [], () => get(userPreferences).backup)
+/**
+ * Chords as read from the device
+ */
+export const deviceChords = persistentWritable(
+ "chord-library",
+ [],
+ () => get(userPreferences).backup,
+)
-export const layout = persistentWritable(
+/**
+ * Layout as read from the device
+ */
+export const deviceLayout = persistentWritable(
"layout",
[[], [], []],
() => get(userPreferences).backup,
)
-export interface Change {
- layout?: Record>
- chords?: Array>
- settings?: Record
-}
-
-export const changes = persistentWritable("changes", [])
-
-export const settings = writable({})
-
-export const unsavedChanges = writable(new Map())
-
-export const highlightActions: Writable = writable([])
+/**
+ * Settings as read from the device
+ */
+export const deviceSettings = persistentWritable(
+ "device-settings",
+ [],
+ () => get(userPreferences).backup,
+)
export const syncStatus: Writable<"done" | "error" | "downloading" | "uploading"> = writable("done")
@@ -45,19 +51,28 @@ export async function initSerial(manual = false) {
serialPort.set(device)
syncStatus.set("downloading")
+ const parsedSettings: number[] = []
+ for (const key in settingInfo.settings) {
+ try {
+ parsedSettings[Number.parseInt(key)] = await device.getSetting(Number.parseInt(key))
+ } catch {}
+ }
+ deviceSettings.set(parsedSettings)
+
const parsedLayout: CharaLayout = [[], [], []]
for (let layer = 1; layer <= 3; layer++) {
+ // TODO: this will fail for LITE!
for (let i = 0; i < 90; i++) {
parsedLayout[layer - 1][i] = await device.getLayoutKey(layer, i)
}
}
- layout.set(parsedLayout)
+ deviceLayout.set(parsedLayout)
const chordCount = await device.getChordCount()
const chordInfo = []
for (let i = 0; i < chordCount; i++) {
chordInfo.push(await device.getChord(i))
}
- chords.set(chordInfo)
+ deviceChords.set(chordInfo)
syncStatus.set("done")
}
diff --git a/src/lib/serial/device.ts b/src/lib/serial/device.ts
index 03dba120..0ed32abc 100644
--- a/src/lib/serial/device.ts
+++ b/src/lib/serial/device.ts
@@ -193,7 +193,7 @@ export class CharaDevice {
if (status !== "0") throw new Error(`Failed with status ${status}`)
}
- async deleteChord(chord: Chord) {
+ async deleteChord(chord: Pick) {
const status = await this.send(`CML C4 ${stringifyChordActions(chord.actions)}`)
if (status.at(-1) !== "0") throw new Error(`Failed with status ${status}`)
}
@@ -205,7 +205,8 @@ export class CharaDevice {
* @param action the assigned action id
*/
async setLayoutKey(layer: number, id: number, action: number) {
- const [status] = await this.send(`VAR B3 A${layer} ${id} ${action}`)
+ const [status] = await this.send(`VAR B4 A${layer} ${id} ${action}`)
+ console.log(status)
if (status !== "0") throw new Error(`Failed with status ${status}`)
}
diff --git a/src/lib/setting.ts b/src/lib/setting.ts
index b3288bea..9b85fffb 100644
--- a/src/lib/setting.ts
+++ b/src/lib/setting.ts
@@ -1,6 +1,5 @@
import type {Action} from "svelte/action"
-import {serialPort, unsavedChanges} from "$lib/serial/connection"
-import {get} from "svelte/store"
+import {changes, ChangeType, settings} from "$lib/undo-redo"
export const setting: Action = function (
node: HTMLInputElement,
@@ -9,15 +8,20 @@ export const setting: Action {
- if (port) {
+ const unsubscribe = settings.subscribe(async settings => {
+ if (id in settings) {
+ const {value, isApplied} = settings[id]
if (type === "number") {
- const value = Number(await port.getSetting(id).then(it => it.toString()))
node.value = (
inverse !== undefined ? inverse / value : scale !== undefined ? scale * value : value
).toString()
} else {
- node.checked = await port.getSetting(id).then(it => it !== 0)
+ node.checked = value !== 0
+ }
+ if (isApplied) {
+ node.classList.remove("pending-changes")
+ } else {
+ node.classList.add("pending-changes")
}
node.removeAttribute("disabled")
} else {
@@ -26,8 +30,7 @@ export const setting: Action {
- if (originalValue === value) {
- it.delete(id)
- } else if (!it.has(id)) {
- it.set(id, currentValue)
- }
- return it
+ changes.update(changes => {
+ changes.push({
+ type: ChangeType.Setting,
+ id: id,
+ setting: value,
+ })
+ return changes
})
}
node.addEventListener("input", listener)
diff --git a/src/lib/undo-redo.ts b/src/lib/undo-redo.ts
new file mode 100644
index 00000000..cc98f1cb
--- /dev/null
+++ b/src/lib/undo-redo.ts
@@ -0,0 +1,109 @@
+import {persistentWritable} from "$lib/storage"
+import {derived} from "svelte/store"
+import {serializeActions} from "$lib/serial/chord"
+import type {Chord} from "$lib/serial/chord"
+import {deviceChords, deviceLayout, deviceSettings} from "$lib/serial/connection"
+
+export enum ChangeType {
+ Layout,
+ Chord,
+ Setting,
+}
+
+export interface LayoutChange {
+ type: ChangeType.Layout
+ id: number
+ layer: number
+ action: number
+}
+
+export interface ChordChange {
+ type: ChangeType.Chord
+ actions: number[]
+ phrase: number[]
+}
+
+export interface SettingChange {
+ type: ChangeType.Setting
+ id: number
+ setting: number
+}
+
+export interface ChangeInfo {
+ isApplied: boolean
+ isCommitted?: boolean
+}
+
+export type Change = LayoutChange | ChordChange | SettingChange
+
+export const changes = persistentWritable("changes", [])
+
+export interface Overlay {
+ layout: [Map, Map, Map]
+ chords: Map
+ settings: Map
+}
+
+export const overlay = derived(changes, changes => {
+ console.time("overlay building")
+ const overlay: Overlay = {
+ layout: [new Map(), new Map(), new Map()],
+ chords: new Map(),
+ settings: new Map(),
+ }
+
+ for (const change of changes) {
+ switch (change.type) {
+ case ChangeType.Layout:
+ overlay.layout[change.layer].set(change.id, change.action)
+ break
+ case ChangeType.Chord:
+ overlay.chords.set(serializeActions(change.actions), change.phrase)
+ break
+ case ChangeType.Setting:
+ overlay.settings.set(change.id, change.setting)
+ break
+ }
+ }
+ console.timeEnd("overlay building")
+
+ return overlay
+})
+
+export const settings = derived([overlay, deviceSettings], ([overlay, settings]) =>
+ settings.map<{value: number} & ChangeInfo>((value, id) => ({
+ value: overlay.settings.get(id) ?? value,
+ isApplied: !overlay.settings.has(id),
+ })),
+)
+
+export type KeyInfo = {action: number} & ChangeInfo
+export const layout = derived([overlay, deviceLayout], ([overlay, layout]) =>
+ layout.map(
+ (actions, layer) =>
+ actions.map((action, id) => ({
+ action: overlay.layout[layer].get(id) ?? action,
+ isApplied: !overlay.layout[layer].has(id),
+ })) as [KeyInfo, KeyInfo, KeyInfo],
+ ),
+)
+
+export type ChordInfo = Chord & ChangeInfo
+export const chords = derived([overlay, deviceChords], ([overlay, chords]) =>
+ chords.map(chord => {
+ const key = serializeActions(chord.actions)
+ if (overlay.chords.has(key)) {
+ return {
+ actions: chord.actions,
+ phrase: overlay.chords.get(key)!,
+ isApplied: false,
+ }
+ } else {
+ return {
+ actions: chord.actions,
+ phrase: chord.phrase,
+ isApplied: true,
+ }
+ }
+ }),
+)
diff --git a/src/lib/versioning.ts b/src/lib/versioning.ts
new file mode 100644
index 00000000..70b786d1
--- /dev/null
+++ b/src/lib/versioning.ts
@@ -0,0 +1 @@
+// TODO
diff --git a/src/routes/BackupPopup.svelte b/src/routes/BackupPopup.svelte
index 42dec62a..29cfe3eb 100644
--- a/src/routes/BackupPopup.svelte
+++ b/src/routes/BackupPopup.svelte
@@ -1,6 +1,6 @@
diff --git a/src/routes/EditActions.svelte b/src/routes/EditActions.svelte
index 90b15741..2fe7deee 100644
--- a/src/routes/EditActions.svelte
+++ b/src/routes/EditActions.svelte
@@ -1,9 +1,10 @@
@@ -107,7 +154,11 @@
on:click={redo}>redo
-
+
{#if $changes.length !== 0}