refactor: better shortcut hook
This commit is contained in:
@@ -74,6 +74,11 @@ export function View({ children, style }: ViewProps) {
|
||||
justifyContent: attr(style, "justifyContent", "string"),
|
||||
flexShrink: attr(style, "flexShrink", "number"),
|
||||
flexDirection: attr(style, "flexDirection", "string"),
|
||||
top: attr(style, "top", "number"),
|
||||
zIndex: attr(style, "zIndex", "number"),
|
||||
left: attr(style, "left", "number"),
|
||||
right: attr(style, "right", "number"),
|
||||
bottom: attr(style, "bottom", "number"),
|
||||
flexGrow:
|
||||
attr(style, "flex", "number") || attr(style, "flexGrow", "number"),
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
timestamp,
|
||||
pgEnum,
|
||||
uniqueIndex,
|
||||
numeric,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable(
|
||||
@@ -65,3 +66,25 @@ export const plaidAccessTokens = pgTable("plaidAccessToken", {
|
||||
token: text("token").notNull(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const budget = pgTable("budget", {
|
||||
id: text("id").primaryKey(),
|
||||
orgId: text("org_id").notNull(),
|
||||
label: text("label").notNull(),
|
||||
createdBy: text("created_by").notNull(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const category = pgTable("category", {
|
||||
id: text("id").primaryKey(),
|
||||
budgetId: text("budget_id").notNull(),
|
||||
amount: decimal("amount").notNull(),
|
||||
every: text("every", { enum: ["year", "month", "week"] }).notNull(),
|
||||
order: numeric("order").notNull(),
|
||||
label: text("label").notNull(),
|
||||
color: text("color").notNull(),
|
||||
createdBy: text("created_by").notNull(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Transaction } from "@rocicorp/zero";
|
||||
import type { AuthData } from "./auth";
|
||||
import { authDataSchema, type AuthData } from "./auth";
|
||||
import { type Schema } from "./zero-schema.gen";
|
||||
import { isLoggedIn } from "./zql";
|
||||
|
||||
@@ -39,6 +39,17 @@ export function createMutators(authData: AuthData | null) {
|
||||
}
|
||||
},
|
||||
},
|
||||
budget: {
|
||||
async create(tx: Tx, { id }: { id: string }) {
|
||||
isLoggedIn(authData);
|
||||
await tx.mutate.budget.insert({
|
||||
id,
|
||||
orgId: authData.user.id,
|
||||
label: "New Budget",
|
||||
createdBy: authData.user.id,
|
||||
});
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,4 +60,20 @@ export const queries = {
|
||||
.orderBy("createdAt", "desc");
|
||||
},
|
||||
),
|
||||
getBudgets: syncedQueryWithContext(
|
||||
"getBudgets",
|
||||
z.tuple([]),
|
||||
(authData: AuthData | null) => {
|
||||
isLoggedIn(authData);
|
||||
return builder.budget.limit(10);
|
||||
},
|
||||
),
|
||||
getBudgetCategories: syncedQueryWithContext(
|
||||
"getBudgetCategories",
|
||||
z.tuple([]),
|
||||
(authData: AuthData | null) => {
|
||||
isLoggedIn(authData);
|
||||
return builder.category.orderBy("order", "desc");
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
@@ -112,6 +112,170 @@ export const schema = {
|
||||
},
|
||||
primaryKey: ["id"],
|
||||
},
|
||||
budget: {
|
||||
name: "budget",
|
||||
columns: {
|
||||
id: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"budget",
|
||||
"id"
|
||||
>,
|
||||
},
|
||||
orgId: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"budget",
|
||||
"orgId"
|
||||
>,
|
||||
serverName: "org_id",
|
||||
},
|
||||
label: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"budget",
|
||||
"label"
|
||||
>,
|
||||
},
|
||||
createdBy: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"budget",
|
||||
"createdBy"
|
||||
>,
|
||||
serverName: "created_by",
|
||||
},
|
||||
createdAt: {
|
||||
type: "number",
|
||||
optional: true,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"budget",
|
||||
"createdAt"
|
||||
>,
|
||||
serverName: "created_at",
|
||||
},
|
||||
updatedAt: {
|
||||
type: "number",
|
||||
optional: true,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"budget",
|
||||
"updatedAt"
|
||||
>,
|
||||
serverName: "updated_at",
|
||||
},
|
||||
},
|
||||
primaryKey: ["id"],
|
||||
},
|
||||
category: {
|
||||
name: "category",
|
||||
columns: {
|
||||
id: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"category",
|
||||
"id"
|
||||
>,
|
||||
},
|
||||
budgetId: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"category",
|
||||
"budgetId"
|
||||
>,
|
||||
serverName: "budget_id",
|
||||
},
|
||||
amount: {
|
||||
type: "number",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"category",
|
||||
"amount"
|
||||
>,
|
||||
},
|
||||
every: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"category",
|
||||
"every"
|
||||
>,
|
||||
},
|
||||
order: {
|
||||
type: "number",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"category",
|
||||
"order"
|
||||
>,
|
||||
},
|
||||
label: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"category",
|
||||
"label"
|
||||
>,
|
||||
},
|
||||
color: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"category",
|
||||
"color"
|
||||
>,
|
||||
},
|
||||
createdBy: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"category",
|
||||
"createdBy"
|
||||
>,
|
||||
serverName: "created_by",
|
||||
},
|
||||
createdAt: {
|
||||
type: "number",
|
||||
optional: true,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"category",
|
||||
"createdAt"
|
||||
>,
|
||||
serverName: "created_at",
|
||||
},
|
||||
updatedAt: {
|
||||
type: "number",
|
||||
optional: true,
|
||||
customType: null as unknown as ZeroCustomType<
|
||||
ZeroSchema,
|
||||
"category",
|
||||
"updatedAt"
|
||||
>,
|
||||
serverName: "updated_at",
|
||||
},
|
||||
},
|
||||
primaryKey: ["id"],
|
||||
},
|
||||
plaidAccessTokens: {
|
||||
name: "plaidAccessTokens",
|
||||
columns: {
|
||||
@@ -433,6 +597,16 @@ export type Schema = typeof schema;
|
||||
* This type is auto-generated from your Drizzle schema definition.
|
||||
*/
|
||||
export type Balance = Row<Schema["tables"]["balance"]>;
|
||||
/**
|
||||
* Represents a row from the "budget" table.
|
||||
* This type is auto-generated from your Drizzle schema definition.
|
||||
*/
|
||||
export type Budget = Row<Schema["tables"]["budget"]>;
|
||||
/**
|
||||
* Represents a row from the "category" table.
|
||||
* This type is auto-generated from your Drizzle schema definition.
|
||||
*/
|
||||
export type Category = Row<Schema["tables"]["category"]>;
|
||||
/**
|
||||
* Represents a row from the "plaidAccessTokens" table.
|
||||
* This type is auto-generated from your Drizzle schema definition.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useKeyboard } from "../src/useKeyboard";
|
||||
import type { ReactNode } from "react";
|
||||
import { useEffect, type ReactNode } from "react";
|
||||
import { Text, Pressable } from "react-native";
|
||||
|
||||
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
||||
|
||||
export interface ButtonProps {
|
||||
children: ReactNode;
|
||||
onPress?: () => void;
|
||||
@@ -21,10 +22,16 @@ const STYLES: Record<
|
||||
export function Button({ children, variant, onPress, shortcut }: ButtonProps) {
|
||||
const { backgroundColor, color } = STYLES[variant || "default"];
|
||||
|
||||
useKeyboard((key) => {
|
||||
if (!shortcut || !onPress) return;
|
||||
if (key.name == shortcut) onPress();
|
||||
});
|
||||
// if (shortcut) {
|
||||
// useKeys((key) => {
|
||||
// if (
|
||||
// typeof shortcut == "object"
|
||||
// ? key.name == shortcut.name
|
||||
// : key.name == shortcut
|
||||
// )
|
||||
// return onPress;
|
||||
// });
|
||||
// }
|
||||
|
||||
return (
|
||||
<Pressable onPress={onPress} style={{ backgroundColor }}>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createContext, type ReactNode } from "react";
|
||||
import { Modal, View, Text } from "react-native";
|
||||
import { useKeyboard } from "../src/useKeyboard";
|
||||
|
||||
export interface DialogState {
|
||||
close?: () => void;
|
||||
@@ -15,12 +14,6 @@ interface ProviderProps {
|
||||
close?: () => void;
|
||||
}
|
||||
export function Provider({ children, visible, close }: ProviderProps) {
|
||||
useKeyboard((key) => {
|
||||
if (key.name == "escape") {
|
||||
if (close) close();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ close }}>
|
||||
<Modal transparent visible={visible}>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { createContext, use, useState, type ReactNode } from "react";
|
||||
import {
|
||||
createContext,
|
||||
use,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { useKeyboard } from "../src/useKeyboard";
|
||||
import type { KeyEvent } from "@opentui/core";
|
||||
import { useShortcut } from "../lib/shortcuts/hooks";
|
||||
|
||||
const HEADER_COLOR = "#7158e2";
|
||||
const TABLE_COLORS = ["#ddd", "#eee"];
|
||||
@@ -58,33 +65,12 @@ export function Provider<T extends ValidRecord>({
|
||||
const [idx, setIdx] = useState(0);
|
||||
const [selectedFrom, setSelectedFrom] = useState<number>();
|
||||
|
||||
useKeyboard(
|
||||
(key) => {
|
||||
if (key.name == "j" || key.name == "down") {
|
||||
if (key.shift && selectedFrom == undefined) {
|
||||
setSelectedFrom(idx);
|
||||
}
|
||||
useShortcut("j", () => {
|
||||
setIdx((prev) => Math.min(prev + 1, data.length - 1));
|
||||
} else if (key.name == "k" || key.name == "up") {
|
||||
if (key.shift && selectedFrom == undefined) {
|
||||
setSelectedFrom(idx);
|
||||
}
|
||||
});
|
||||
useShortcut("k", () => {
|
||||
setIdx((prev) => Math.max(prev - 1, 0));
|
||||
} else if (key.name == "g" && key.shift) {
|
||||
setIdx(data.length - 1);
|
||||
} else if (key.name == "v") {
|
||||
setSelectedFrom(idx);
|
||||
} else if (key.name == "escape") {
|
||||
setSelectedFrom(undefined);
|
||||
} else {
|
||||
const from = selectedFrom ? Math.min(idx, selectedFrom) : idx;
|
||||
const to = selectedFrom ? Math.max(idx, selectedFrom) : idx;
|
||||
const selected = data.slice(from, to + 1);
|
||||
if (onKey) onKey(key, selected);
|
||||
}
|
||||
},
|
||||
[data, idx, selectedFrom],
|
||||
);
|
||||
});
|
||||
|
||||
const columnMap = new Map(
|
||||
columns.map((col) => {
|
||||
|
||||
30
packages/ui/lib/shortcuts/Debug.tsx
Normal file
30
packages/ui/lib/shortcuts/Debug.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { keysStore } from "./store";
|
||||
|
||||
export function ShortcutDebug() {
|
||||
const entries = useSyncExternalStore(
|
||||
keysStore.subscribe,
|
||||
keysStore.getSnapshot,
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 100,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
backgroundColor: "black",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "red", fontFamily: "mono" }}>
|
||||
{entries
|
||||
.values()
|
||||
.map(([key, _]) => key)
|
||||
.toArray()
|
||||
.join(",")}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
12
packages/ui/lib/shortcuts/Provider.tsx
Normal file
12
packages/ui/lib/shortcuts/Provider.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useKeyboard } from "@opentui/react";
|
||||
import { keysStore } from "./store";
|
||||
|
||||
export function ShortcutProvider({ children }: { children: ReactNode }) {
|
||||
useKeyboard((e) => {
|
||||
const fn = keysStore.getHandler(e.name);
|
||||
fn?.();
|
||||
});
|
||||
|
||||
return children;
|
||||
}
|
||||
13
packages/ui/lib/shortcuts/Provider.web.tsx
Normal file
13
packages/ui/lib/shortcuts/Provider.web.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { keysStore } from "./store";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("keydown", (e) => {
|
||||
const fn = keysStore.getHandler(e.key);
|
||||
fn?.();
|
||||
});
|
||||
}
|
||||
|
||||
export function ShortcutProvider({ children }: { children: ReactNode }) {
|
||||
return children;
|
||||
}
|
||||
14
packages/ui/lib/shortcuts/hooks.ts
Normal file
14
packages/ui/lib/shortcuts/hooks.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { keysStore } from "./store";
|
||||
|
||||
export const useShortcut = (key: string, handler: () => void) => {
|
||||
const ref = useRef(handler);
|
||||
ref.current = handler;
|
||||
|
||||
useEffect(() => {
|
||||
keysStore.register(key, ref);
|
||||
return () => {
|
||||
keysStore.deregister(key);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
3
packages/ui/lib/shortcuts/index.ts
Normal file
3
packages/ui/lib/shortcuts/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./Debug";
|
||||
export * from "./Provider";
|
||||
export * from "./store";
|
||||
40
packages/ui/lib/shortcuts/store.ts
Normal file
40
packages/ui/lib/shortcuts/store.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { type RefObject } from "react";
|
||||
|
||||
// internal map
|
||||
const keys = new Map<string, RefObject<() => void>>();
|
||||
|
||||
// cached snapshot (stable reference)
|
||||
let snapshot: [string, RefObject<() => void>][] = [];
|
||||
|
||||
let listeners = new Set<() => void>();
|
||||
|
||||
function emit() {
|
||||
// refresh snapshot ONLY when keys actually change
|
||||
snapshot = Array.from(keys.entries());
|
||||
for (const fn of listeners) fn();
|
||||
}
|
||||
|
||||
export const keysStore = {
|
||||
subscribe(fn: () => void) {
|
||||
listeners.add(fn);
|
||||
return () => listeners.delete(fn);
|
||||
},
|
||||
|
||||
getSnapshot() {
|
||||
return snapshot; // stable unless emit() ran
|
||||
},
|
||||
|
||||
register(key: string, ref: RefObject<() => void>) {
|
||||
keys.set(key, ref);
|
||||
emit();
|
||||
},
|
||||
|
||||
deregister(key: string) {
|
||||
keys.delete(key);
|
||||
emit();
|
||||
},
|
||||
|
||||
getHandler(key: string) {
|
||||
return keys.get(key)?.current;
|
||||
},
|
||||
};
|
||||
72
packages/ui/src/budget.tsx
Normal file
72
packages/ui/src/budget.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { use } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { RouterContext } from ".";
|
||||
import { queries, type Mutators, type Schema } from "@money/shared";
|
||||
import { useQuery, useZero } from "@rocicorp/zero/react";
|
||||
import * as Table from "../components/Table";
|
||||
import { Button } from "../components/Button";
|
||||
|
||||
const COLUMNS: Table.Column[] = [{ name: "label", label: "Name" }];
|
||||
|
||||
export function Budget() {
|
||||
const { auth } = use(RouterContext);
|
||||
const [budgets] = useQuery(queries.getBudgets(auth));
|
||||
// const [items] = useQuery(queries.getBudgetCategories(auth));
|
||||
|
||||
const items: any[] = [];
|
||||
|
||||
const z = useZero<Schema, Mutators>();
|
||||
|
||||
const newBudget = () => {
|
||||
const id = new Date().getTime().toString();
|
||||
z.mutate.budget.create({
|
||||
id,
|
||||
});
|
||||
};
|
||||
|
||||
if (budgets.length == 0)
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flex: 1,
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontFamily: "mono" }}>
|
||||
No budgets, please create a new budget
|
||||
</Text>
|
||||
<Button onPress={newBudget} shortcut="n">
|
||||
New budget
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
|
||||
const budget = budgets[0]!;
|
||||
|
||||
return (
|
||||
<>
|
||||
<View>
|
||||
<Text style={{ fontFamily: "mono" }}>
|
||||
Selected Budget: {budget.label}
|
||||
</Text>
|
||||
</View>
|
||||
<Table.Provider
|
||||
data={items}
|
||||
columns={COLUMNS}
|
||||
onKey={(event) => {
|
||||
if (event.name == "n" && event.shift) {
|
||||
newBudget();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ flexShrink: 0 }}>
|
||||
<Table.Body />
|
||||
</View>
|
||||
</View>
|
||||
</Table.Provider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,24 @@
|
||||
import { createContext, use } from "react";
|
||||
import { createContext, use, useEffect, useRef } from "react";
|
||||
import { Transactions } from "./transactions";
|
||||
import { View, Text } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { Settings } from "./settings";
|
||||
import { useKeyboard } from "./useKeyboard";
|
||||
import type { AuthData } from "@money/shared/auth";
|
||||
import { Budget } from "./budget";
|
||||
import { ShortcutProvider, ShortcutDebug, keysStore } from "../lib/shortcuts";
|
||||
import { useShortcut } from "../lib/shortcuts/hooks";
|
||||
|
||||
const PAGES = {
|
||||
"/": {
|
||||
screen: <Transactions />,
|
||||
key: "1",
|
||||
},
|
||||
"/budget": {
|
||||
screen: <Budget />,
|
||||
key: "2",
|
||||
},
|
||||
"/settings": {
|
||||
screen: <Settings />,
|
||||
key: "2",
|
||||
key: "3",
|
||||
children: {
|
||||
"/accounts": {},
|
||||
"/family": {},
|
||||
@@ -59,7 +65,10 @@ type AppProps = {
|
||||
export function App({ auth, route, setRoute }: AppProps) {
|
||||
return (
|
||||
<RouterContext.Provider value={{ auth, route, setRoute }}>
|
||||
<ShortcutProvider>
|
||||
<ShortcutDebug />
|
||||
<Main />
|
||||
</ShortcutProvider>
|
||||
</RouterContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -67,17 +76,9 @@ export function App({ auth, route, setRoute }: AppProps) {
|
||||
function Main() {
|
||||
const { route, setRoute } = use(RouterContext);
|
||||
|
||||
useKeyboard((key) => {
|
||||
const screen = Object.entries(PAGES).find(
|
||||
([, screen]) => screen.key == key.name,
|
||||
);
|
||||
|
||||
if (!screen) return;
|
||||
|
||||
const [route] = screen as [Route, never];
|
||||
|
||||
setRoute(route);
|
||||
});
|
||||
for (const [route, page] of Object.entries(PAGES)) {
|
||||
useShortcut(page.key, () => setRoute(route as Route));
|
||||
}
|
||||
|
||||
const match =
|
||||
route in PAGES
|
||||
|
||||
@@ -4,8 +4,6 @@ import { RouterContext, type Route } from ".";
|
||||
import { General } from "./settings/general";
|
||||
import { Accounts } from "./settings/accounts";
|
||||
import { Family } from "./settings/family";
|
||||
import { useKeyboard } from "./useKeyboard";
|
||||
import { Modal } from "react-native-opentui";
|
||||
|
||||
type SettingsRoute = Extract<Route, `/settings${string}`>;
|
||||
|
||||
@@ -32,28 +30,27 @@ type Tab = keyof typeof TABS;
|
||||
export function Settings() {
|
||||
const { route, setRoute } = use(RouterContext);
|
||||
|
||||
useKeyboard(
|
||||
(key) => {
|
||||
if (key.name == "h") {
|
||||
const currentIdx = Object.entries(TABS).findIndex(
|
||||
([tabRoute, _]) => tabRoute == route,
|
||||
);
|
||||
const routes = Object.keys(TABS) as SettingsRoute[];
|
||||
const last = routes[currentIdx - 1];
|
||||
if (!last) return;
|
||||
setRoute(last);
|
||||
} else if (key.name == "l") {
|
||||
const currentIdx = Object.entries(TABS).findIndex(
|
||||
([tabRoute, _]) => tabRoute == route,
|
||||
);
|
||||
const routes = Object.keys(TABS) as SettingsRoute[];
|
||||
const next = routes[currentIdx + 1];
|
||||
if (!next) return;
|
||||
setRoute(next);
|
||||
}
|
||||
},
|
||||
[route],
|
||||
);
|
||||
// useKeyboard(
|
||||
// (key) => {
|
||||
// if (key.name == "h") {
|
||||
// const currentIdx = Object.entries(TABS).findIndex(
|
||||
// ([tabRoute, _]) => tabRoute == route,
|
||||
// );
|
||||
// const routes = Object.keys(TABS) as SettingsRoute[];
|
||||
// const last = routes[currentIdx - 1];
|
||||
// if (!last) return;
|
||||
// setRoute(last);
|
||||
// } else if (key.name == "l") {
|
||||
// const currentIdx = Object.entries(TABS).findIndex(
|
||||
// ([tabRoute, _]) => tabRoute == route,
|
||||
// );
|
||||
// const routes = Object.keys(TABS) as SettingsRoute[];
|
||||
// const next = routes[currentIdx + 1];
|
||||
// if (!next) return;
|
||||
// setRoute(next);
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { queries, type Mutators, type Schema } from "@money/shared";
|
||||
import { use, useEffect, useState } from "react";
|
||||
import { RouterContext } from "..";
|
||||
import { View, Text, Linking } from "react-native";
|
||||
import { useKeyboard } from "../useKeyboard";
|
||||
import { Button } from "../../components/Button";
|
||||
import * as Table from "../../components/Table";
|
||||
import * as Dialog from "../../components/Dialog";
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { useKeyboard as useOpentuiKeyboard } from "@opentui/react";
|
||||
|
||||
export function useKeyboard(
|
||||
handler: Parameters<typeof useOpentuiKeyboard>[0],
|
||||
_deps: any[] = [],
|
||||
) {
|
||||
return useOpentuiKeyboard(handler);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import type { KeyboardEvent } from "react";
|
||||
import type { KeyEvent } from "@opentui/core";
|
||||
|
||||
function convertName(keyName: string): string {
|
||||
const result = keyName.toLowerCase();
|
||||
if (result == "arrowdown") return "down";
|
||||
if (result == "arrowup") return "up";
|
||||
return result;
|
||||
}
|
||||
|
||||
export function useKeyboard(
|
||||
handler: (key: KeyEvent) => void,
|
||||
deps: any[] = [],
|
||||
) {
|
||||
useEffect(() => {
|
||||
const handlerWeb = (event: KeyboardEvent) => {
|
||||
// @ts-ignore
|
||||
handler({
|
||||
name: convertName(event.key),
|
||||
ctrl: event.ctrlKey,
|
||||
meta: event.metaKey,
|
||||
shift: event.shiftKey,
|
||||
option: event.metaKey,
|
||||
sequence: "",
|
||||
number: false,
|
||||
raw: "",
|
||||
eventType: "press",
|
||||
source: "raw",
|
||||
code: event.code,
|
||||
super: false,
|
||||
hyper: false,
|
||||
capsLock: false,
|
||||
numLock: false,
|
||||
baseCode: event.keyCode,
|
||||
preventDefault: () => event.preventDefault(),
|
||||
});
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
window.addEventListener("keydown", handlerWeb);
|
||||
return () => {
|
||||
// @ts-ignore
|
||||
window.removeEventListener("keydown", handlerWeb);
|
||||
};
|
||||
}, deps);
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext"],
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
|
||||
Reference in New Issue
Block a user