Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit fec0bb0

Browse files
committed
feat: support filtering
1 parent bfc38a2 commit fec0bb0

23 files changed

Lines changed: 1008 additions & 40 deletions

‎components/DialogAddUpdateLocal.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export function DialogAddUpdateLocal(
9090
name="path"
9191
id="path"
9292
value={store?.path}
93-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 w-72 md:w-96"
93+
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 md:w-96"
9494
placeholder="Path (on server) to local store"
9595
required
9696
>
@@ -103,8 +103,9 @@ export function DialogAddUpdateLocal(
103103
type="text"
104104
name="name"
105105
id="name"
106+
autoComplete="off"
106107
value={store?.name}
107-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 w-72 md:w-96"
108+
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 md:w-96"
108109
placeholder="Friendly name (optional)"
109110
>
110111
</input>

‎components/DialogAddUpdateRemote.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export function DialogAddUpdateRemote(
122122
<input
123123
type="text"
124124
name="name"
125+
autoComplete="off"
125126
id="name"
126127
value={store?.name}
127128
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"

‎components/DialogQuery.tsx‎

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import type { KvFilterJSON } from "@kitsonk/kv-toolbox/query";
2+
import { useRef } from "preact/hooks";
3+
import { type Signal, useComputed, useSignal } from "@preact/signals";
4+
import { addNotification } from "$utils/state.ts";
5+
6+
import { CloseButton } from "./CloseButton.tsx";
7+
import { Dialog } from "./Dialog.tsx";
8+
import { type KvFilterIndeterminateJSON, QueryFilter } from "./QueryFilter.tsx";
9+
import IconPlus from "./icons/Plus.tsx";
10+
11+
export function DialogQuery(
12+
{ open, filters, active }: {
13+
open: Signal<boolean>;
14+
filters: Signal<KvFilterJSON[]>;
15+
active: Signal<boolean>;
16+
},
17+
) {
18+
const form = useRef<HTMLFormElement>(null);
19+
const localFilters = filters.peek().length
20+
? useSignal<(KvFilterJSON | KvFilterIndeterminateJSON)[]>(
21+
[...filters.value],
22+
)
23+
: useSignal<(KvFilterJSON | KvFilterIndeterminateJSON)[]>([{ kind: "" }]);
24+
25+
const handleFilterOnChange = (index: number, value: KvFilterJSON) =>
26+
localFilters.value = localFilters.value.map((filter, i) =>
27+
i === index ? value : filter
28+
);
29+
const handleFilterOnRemove = (index: number) =>
30+
localFilters.value = localFilters.value.filter((_, i) => i !== index);
31+
const handleAddOnClick = () => {
32+
localFilters.value = [...localFilters.value, { kind: "" }];
33+
};
34+
const filterComponents = useComputed(() =>
35+
localFilters.value.map((filter, index) => (
36+
<QueryFilter
37+
key={index}
38+
index={index}
39+
id={String(index)}
40+
filter={filter}
41+
onChange={handleFilterOnChange}
42+
onRemove={handleFilterOnRemove}
43+
/>
44+
))
45+
);
46+
return (
47+
<Dialog
48+
class="p-4 bg-white rounded-lg shadow dark:bg-gray-800 sm:p-5"
49+
open={open}
50+
>
51+
<div class="flex justify-between items-center pb-4 mb-4 rounded-t border-b sm:mb-5 dark:border-gray-600">
52+
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
53+
Filter
54+
</h3>
55+
<CloseButton
56+
onClick={() => {
57+
form.current?.reset();
58+
localFilters.value = filters.value.length
59+
? [...filters.value]
60+
: [{ kind: "" }];
61+
open.value = false;
62+
}}
63+
/>
64+
</div>
65+
<form
66+
method="dialog"
67+
ref={form}
68+
class="z-10 w-full max-w-screen-md space-y-3"
69+
onSubmit={(_) => {
70+
filters.value = localFilters.value.filter(
71+
(filter) => filter.kind !== "",
72+
);
73+
if (filters.value.length > 0) {
74+
active.value = true;
75+
addNotification("Filter applied", "success", true, 5);
76+
}
77+
open.value = false;
78+
}}
79+
>
80+
{filterComponents}
81+
<a
82+
href="#"
83+
class="flex items-center pb-2 text-sm font-medium border-b dark:border-gray-600 text-primary-600 dark:text-primary-500 hover:underline"
84+
onClick={handleAddOnClick}
85+
>
86+
<IconPlus />
87+
Add Condition
88+
</a>
89+
<div class="flex items-center justify-between">
90+
<button
91+
type="submit"
92+
class="text-white bg-primary-600 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-primary-800"
93+
>
94+
Apply
95+
</button>
96+
<button
97+
type="reset"
98+
class="py-2.5 px-5 flex items-center hover:bg-gray-100 dark:hover:bg-gray-600 text-sm font-medium text-gray-900 focus:outline-none rounded-lg hover:text-black focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:text-gray-400 dark:hover:text-white"
99+
onClick={() => {
100+
form.current?.reset();
101+
localFilters.value = [{ kind: "" }];
102+
active.value = false;
103+
}}
104+
>
105+
<svg
106+
xmlns="http://www.w3.org/2000/svg"
107+
class="w-4 h-4 mr-1"
108+
viewBox="0 0 20 20"
109+
fill="currentColor"
110+
>
111+
<path
112+
fill-rule="evenodd"
113+
clip-rule="evenodd"
114+
aria-hidden="true"
115+
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
116+
/>
117+
</svg>
118+
Clear all
119+
</button>
120+
</div>
121+
</form>
122+
</Dialog>
123+
);
124+
}

‎components/KvKey.tsx‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import { type KvKeyJSON } from "@deno/kv-utils/json";
1+
import { type KvKeyJSON,KvKeyPartJSON } from "@deno/kv-utils/json";
22
import { type ComponentChildren } from "preact";
33
import { type Signal } from "@preact/signals";
44

55
import IconHome from "./icons/Home.tsx";
66
import { KvKeyPart } from "./KvKeyPart.tsx";
77

8+
function isKvKeyJSON(value: unknown): value is KvKeyJSON {
9+
return Array.isArray(value);
10+
}
11+
812
export function KvKey(
913
{ value, entry, showRoot, noLink }: {
1014
value: Signal<KvKeyJSON | undefined> | KvKeyJSON;
@@ -16,7 +20,7 @@ export function KvKey(
1620
let key: KvKeyJSON;
1721
let isSignal;
1822
let onClick;
19-
if (Array.isArray(value)) {
23+
if (isKvKeyJSON(value)) {
2024
isSignal = false;
2125
key = value;
2226
} else {
@@ -38,7 +42,7 @@ export function KvKey(
3842
key = [...key];
3943
const children: ComponentChildren[] = [];
4044
let part;
41-
while ((part = key.pop())) {
45+
while ((part = (keyasKvKeyPartJSON[]).pop())) {
4246
children.unshift(
4347
<KvKeyPart
4448
part={part}

‎components/KvSimpleValueEditor.tsx‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import type { Kinds } from "@kitsonk/kv-toolbox/query";
2+
3+
const TYPE_OPTIONS: [Kinds, string][] = [
4+
["string", "String"],
5+
["number", "Number"],
6+
["bigint", "BigInt"],
7+
["boolean", "Boolean"],
8+
["undefined", "Undefined"],
9+
["null", "Null"],
10+
["Array", "Array"],
11+
["Map", "Map"],
12+
["Set", "Set"],
13+
["object", "JSON"],
14+
["RegExp", "RegExp"],
15+
["Date", "Date"],
16+
["KvU64", "KvU64"],
17+
["ArrayBuffer", "ArrayBuffer"],
18+
["DataView", "DataView"],
19+
["Int8Array", "Int8Array"],
20+
["Uint8Array", "Uint8Array"],
21+
["Uint8ClampedArray", "Uint8ClampedArray"],
22+
["Int16Array", "Int16Array"],
23+
["Uint16Array", "Uint16Array"],
24+
["Int32Array", "Int32Array"],
25+
["Uint32Array", "Uint32Array"],
26+
["Float32Array", "Float32Array"],
27+
["Float64Array", "Float64Array"],
28+
["BigInt64Array", "BigInt64Array"],
29+
["BigUint64Array", "BigUint64Array"],
30+
];
31+
32+
function Editor(
33+
{ id, type, value, onChange }: {
34+
id: string;
35+
type: string;
36+
value: string;
37+
onChange: (event: Event) => void;
38+
},
39+
) {
40+
switch (type) {
41+
case "number":
42+
return (
43+
<input
44+
id={`value-${id}`}
45+
name={`value-${id}`}
46+
type="text"
47+
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 invalid:border-red-700"
48+
pattern="-?\d+(\.\d+)?|-?Infinity|NaN"
49+
placeholder="Number"
50+
required
51+
onChange={onChange}
52+
value={value}
53+
/>
54+
);
55+
case "bigint":
56+
case "KvU64":
57+
return (
58+
<input
59+
id={`value-${id}`}
60+
name={`value-${id}`}
61+
type="number"
62+
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 invalid:border-red-700"
63+
placeholder="Number"
64+
required
65+
onChange={onChange}
66+
value={value}
67+
/>
68+
);
69+
case "boolean":
70+
return (
71+
<select
72+
id={`value-${id}`}
73+
name={`value-${id}`}
74+
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
75+
onChange={onChange}
76+
value={value}
77+
>
78+
<option>true</option>
79+
<option>false</option>
80+
</select>
81+
);
82+
case "null":
83+
case "undefined":
84+
return (
85+
<input
86+
id={`value-${id}`}
87+
name={`value-${id}`}
88+
type="text"
89+
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
90+
value={type === "null" ? "null" : "undefined"}
91+
readOnly
92+
/>
93+
);
94+
case "RegExp":
95+
return (
96+
<input
97+
id={`value-${id}`}
98+
name={`value-${id}`}
99+
type="text"
100+
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 invalid:border-red-700"
101+
pattern="\/(?![*+?])([^\r\n\[\/\\]|\\.|\[([^\r\n\]\\]|\\.)*\])+/(g(im?|mi?)?|i(gm?|mg?)?|m(gi?|ig?)?)?"
102+
placeholder="RegExp"
103+
required
104+
onChange={onChange}
105+
value={value}
106+
/>
107+
);
108+
case "Date":
109+
return (
110+
<input
111+
id={`value-${id}`}
112+
name={`value-${id}`}
113+
type="text"
114+
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 invalid:border-red-700"
115+
pattern="[0-9]{4}-((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(0[469]|11)-(0[1-9]|[12][0-9]|30)|(02)-(0[1-9]|[12][0-9]))T(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])\.[0-9]{3}Z"
116+
placeholder="Date"
117+
required
118+
onChange={onChange}
119+
value={value}
120+
/>
121+
);
122+
case "ArrayBuffer":
123+
case "DataView":
124+
case "Int8Array":
125+
case "Uint8Array":
126+
case "Uint8ClampedArray":
127+
case "Int16Array":
128+
case "Uint16Array":
129+
case "Int32Array":
130+
case "Uint32Array":
131+
case "Float32Array":
132+
case "Float64Array":
133+
case "BigInt64Array":
134+
case "BigUint64Array":
135+
return (
136+
<input
137+
id={`value-${id}`}
138+
name={`value-${id}`}
139+
type="text"
140+
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 invalid:border-red-700"
141+
pattern="[-A-Za-z0-9+/]*={0,3}"
142+
placeholder="Base64"
143+
required
144+
onChange={onChange}
145+
value={value}
146+
/>
147+
);
148+
default:
149+
return (
150+
<input
151+
id={`value-${id}`}
152+
name={`value-${id}`}
153+
type="text"
154+
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
155+
placeholder="Value"
156+
required
157+
onChange={onChange}
158+
value={value}
159+
/>
160+
);
161+
}
162+
}
163+
164+
export function KvSimpleValueEditor(
165+
{ type, id, value, only, onChange }: {
166+
type: Kinds;
167+
id: string;
168+
value: string;
169+
only?: Kinds[] | undefined;
170+
onChange: (type: Kinds, value: string) => void;
171+
},
172+
) {
173+
const handleValueOnChange = (event: Event) => {
174+
onChange(type, (event.currentTarget as HTMLInputElement).value);
175+
};
176+
177+
return (
178+
<>
179+
<select
180+
id={`value_type-${id}`}
181+
name={`value_type-${id}`}
182+
value={type}
183+
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
184+
onChange={(evt) => onChange(evt.currentTarget.value as Kinds, value)}
185+
>
186+
{TYPE_OPTIONS
187+
.filter(([type]) => only ? only.includes(type) : true)
188+
.map(([type, label]) => <option value={type}>{label}</option>)}
189+
</select>
190+
<Editor
191+
id={id}
192+
type={type}
193+
value={value}
194+
onChange={handleValueOnChange}
195+
/>
196+
</>
197+
);
198+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /