sortable array + implicit any

main
Kyle Zheng 2024-08-30 20:19:53 -04:00
rodzic ef072f00dc
commit f6bafd0e0f
12 zmienionych plików z 186 dodań i 110 usunięć

Wyświetl plik

@ -4,7 +4,7 @@ type Props = {
};
export function ColorInput(props: Props) {
return (
<label class="border rounded-md font-mono inline-flex items-center py-1.5 px-2 gap-1 cursor-pointer hover:bg-fore-base/5 focus-within:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)">
<label class="border rounded-md font-mono inline-flex items-center py-1.5 px-2 gap-1 cursor-pointer bg-back-base hover:bg-fore-base/5 focus-within:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)">
{props.value}
<input
class="w-0 h-0"

Wyświetl plik

@ -1,6 +1,6 @@
import X from "lucide-solid/icons/x";
import { FlatButton } from "./Button";
import { Show } from "solid-js";
import { FlatButton } from "./Button";
type Props = {
value: File | null;
@ -12,7 +12,7 @@ export function ImageInput(props: Props) {
return (
<div class="inline-flex items-center gap-1">
<input
class="border rounded-md text-sm px-1 py-2 file:(bg-transparent border-none text-fore-base) hover:bg-fore-base/5 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)"
class="border rounded-md text-sm px-1 py-2 file:(bg-transparent border-none text-fore-base) bg-back-base hover:bg-fore-base/5 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)"
ref={input!}
type="file"
accept=".jpeg, .jpg, .png"

Wyświetl plik

@ -56,7 +56,7 @@ export function NumberInput(props: Props) {
</Slider.Track>
</Slider>
<NumberField
class="relative rounded-md focus-within:(ring-2 ring-fore-base ring-offset-2 ring-offset-back-base) hover:bg-fore-base/5"
class="relative rounded-md focus-within:(ring-2 ring-fore-base ring-offset-2 ring-offset-back-base) bg-back-base hover:bg-fore-base/5"
minValue={props.min}
maxValue={props.max}
rawValue={focused() ? rawValue() : props.value}

Wyświetl plik

@ -27,7 +27,6 @@ export function Select(props: Props) {
props.setValue(retainedValue());
}
}}
// @ts-expect-error e is typed wtf
onKeyDown={(e) => {
const index = props.options.indexOf(props.value);
switch (e.key) {
@ -65,7 +64,7 @@ export function Select(props: Props) {
</KSelect.Item>
)}
>
<KSelect.Trigger class="leading-tight w-full inline-flex justify-between items-center rounded-md border pl-3 pr-2 py-2 focus:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base) hover:bg-fore-base/5">
<KSelect.Trigger class="leading-tight w-full inline-flex justify-between items-center rounded-md border pl-3 pr-2 py-2 focus:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base) bg-back-base hover:bg-fore-base/5">
<KSelect.Value>
{(state) => state.selectedOption() as string}
</KSelect.Value>

Wyświetl plik

@ -1,21 +1,21 @@
import { Button } from "@kobalte/core/button";
import { DropdownMenu } from "@kobalte/core/dropdown-menu";
import { NumberField } from "@kobalte/core/number-field";
import { Popover } from "@kobalte/core/popover";
import ChevronDown from "lucide-solid/icons/chevron-down";
import Download from "lucide-solid/icons/download";
import { createSignal } from "solid-js";
import { FillButton } from "./Button";
type Props = {
onClick: (width: number, height: number) => void;
onClick: (width, height) => void;
};
export function SplitButton(props: Props) {
const [customWidth, setCustomWidth] = createSignal(1000);
const [customHeight, setCustomHeight] = createSignal(1000);
const onClick = (width: number, height: number) => {
const onClick = (width, height) => {
props.onClick(width, height);
setOpen(false)
setOpen(false);
};
const [open, setOpen] = createSignal(false);
return (
@ -24,31 +24,24 @@ export function SplitButton(props: Props) {
<Download size={20} />
PNG
</Button>
<DropdownMenu gutter={4} open={open()} onOpenChange={setOpen}>
<DropdownMenu.Trigger
class="dropdown-menu__trigger border rounded-md rounded-s-none hover:bg-fore-base/5 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base) p-2"
>
<DropdownMenu.Icon class="block data-[expanded]:rotate-180 transition-transform">
<ChevronDown size={20} />
</DropdownMenu.Icon>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content class="dropdown-menu__content bg-back-base rounded-md border p-2 outline-none min-w-150px leading-tight">
<Popover gutter={4} open={open()} onOpenChange={setOpen}>
<Popover.Trigger class="group border rounded-md rounded-s-none hover:bg-fore-base/5 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base) p-2">
<ChevronDown
size={20}
class="block group-data-[expanded]:rotate-180 transition-transform"
/>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content class="bg-back-base rounded-md border p-2 outline-none min-w-150px leading-tight">
<div class="flex flex-col gap-2">
<div class="text-sm font-bold">Select size</div>
<FillButton
class="w-full p-2"
onClick={() => onClick(300, 300)}
>
<FillButton class="w-full p-2" onClick={() => onClick(300, 300)}>
300x300
</FillButton>
<FillButton
class="w-full p-2"
onClick={() => onClick(500, 500)}
>
<FillButton class="w-full p-2" onClick={() => onClick(500, 500)}>
500x500
</FillButton>
<DropdownMenu.Separator class="dropdown-menu__separator" />
<hr />
<div class="text-sm font-bold">Custom</div>
<div class="flex gap-2">
<MenuNumberInput
@ -71,9 +64,9 @@ export function SplitButton(props: Props) {
Download custom
</FillButton>
</div>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
</Popover.Content>
</Popover.Portal>
</Popover>
</div>
);
}
@ -88,7 +81,7 @@ type NumberProps = {
function MenuNumberInput(props: NumberProps) {
const [rawValue, setRawValue] = createSignal(props.value);
const safeSetValue = (value: number) => {
const safeSetValue = (value) => {
setRawValue(value);
if (
value < props.min ||

Wyświetl plik

@ -17,7 +17,7 @@ export function Switch(props: Props) {
<KSwitch.Label class="text-sm">{props.label}</KSwitch.Label>
)}
<KSwitch.Input class="peer" />
<KSwitch.Control class="inline-flex items-center w-11 h-6 px-0.5 bg-fore-base/20 data-[checked]:bg-fore-base transition-colors border rounded-3 peer-focus:(ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)">
<KSwitch.Control class="inline-flex items-center w-11 h-6 px-0.5 bg-back-distinct data-[checked]:bg-fore-base transition-colors border rounded-3 peer-focus:(ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)">
<KSwitch.Thumb class="h-5 w-5 rounded-2.5 bg-back-base data-[checked]:translate-x-[calc(100%-1px)] transition-transform" />
</KSwitch.Control>
</KSwitch>

Wyświetl plik

@ -0,0 +1,155 @@
import Minus from "lucide-solid/icons/minus";
import Plus from "lucide-solid/icons/plus";
import { createSignal, For, Index, Show } from "solid-js";
import { Dynamic } from "solid-js/web";
import { PARAM_COMPONENTS } from "~/lib/params";
import { useQrContext } from "~/lib/QrContext";
import { FlatButton } from "../Button";
import { transformStyle, useDragDropContext } from "@thisbeyond/solid-dnd";
import {
DragDropProvider,
DragDropSensors,
DragOverlay,
SortableProvider,
createSortable,
closestCenter,
} from "@thisbeyond/solid-dnd";
import GripVertical from "lucide-solid/icons/grip-vertical";
export function ParamsEditor() {
const { paramsSchema, params, setParams } = useQrContext();
return (
<div class="flex flex-col gap-2 mb-4">
<For each={Object.entries(paramsSchema())}>
{([label, { type, ...other }]) => {
if (type === "Array") {
return <ArrayParam label={label} other={other} />;
}
return (
<>
<div class="flex justify-between">
<div class="text-sm py-2 w-36 shrink-0">{label}</div>
<Dynamic
component={PARAM_COMPONENTS[type]}
{...other}
value={params[label]}
setValue={(v: any) => setParams(label, v)}
/>
</div>
</>
);
}}
</For>
</div>
);
}
function ArrayParam({ label, other }) {
const { params, setParams } = useQrContext();
// 0 is falsey and not a valid key
const idFromIndex = (i) => i + 1;
const indexFromId = (k) => k - 1;
const [activeId, setActiveId] = createSignal(null);
const onDragStart = ({ draggable }) => setActiveId(draggable.id);
const onDragEnd = ({ draggable, droppable }) => {
const fromIndex = indexFromId(draggable.id);
const toIndex = indexFromId(droppable.id);
if (fromIndex !== toIndex) {
setParams(label, (prev: any[]) => {
const updatedItems = prev.slice();
updatedItems.splice(toIndex, 0, ...updatedItems.splice(fromIndex, 1));
return updatedItems;
});
}
};
return (
<div class="grid grid-cols-[144px_1fr] justify-items-end gap-y-2">
<div class="text-sm py-2 w-36">{label}</div>
<div class="flex gap-1">
<Show when={other.resizable}>
<FlatButton
class="p-1.5"
onClick={() => setParams(label, (prev: any[]) => prev.slice(0, -1))}
>
<Minus />
</FlatButton>
<FlatButton
class="p-1.5"
onClick={() =>
setParams(label, (prev: any[]) => [...prev, other.props.default])
}
>
<Plus />
</FlatButton>
</Show>
</div>
<DragDropProvider
onDragStart={onDragStart}
// @ts-expect-error droppable always exists
onDragEnd={onDragEnd}
collisionDetector={closestCenter}
>
<DragDropSensors />
<SortableProvider
ids={Array.from({ length: params[label].length }, (_, i) =>
idFromIndex(i)
)}
>
<Index each={params[label]}>
{(v, i) => {
const sortable = createSortable(idFromIndex(i));
const [state] = useDragDropContext()!;
return (
<>
<div class="text-sm py-2 pl-4 w-full text-left">{i}</div>
<div
class="flex w-full justify-end items-center"
classList={{
"opacity-25": sortable.isActiveDraggable,
"transition-transform": !!state.active.draggable,
}}
ref={sortable.ref}
style={transformStyle(sortable.transform)}
>
<Dynamic
component={
PARAM_COMPONENTS[
other.props.type as keyof typeof PARAM_COMPONENTS
]
}
{...other.props}
value={v()}
setValue={(v: any) => setParams(label, i, v)}
/>
<div class="px-1 cursor-move" {...sortable.dragActivators}>
<GripVertical />
</div>
</div>
</>
);
}}
</Index>
</SortableProvider>
<DragOverlay>
<div class="flex w-full justify-end items-center">
<Dynamic
component={
PARAM_COMPONENTS[
other.props.type as keyof typeof PARAM_COMPONENTS
]
}
{...other.props}
value={params[label][indexFromId(activeId()!)]}
/>
<div class="px-1 cursor-move">
<GripVertical />
</div>
</div>
</DragOverlay>
</DragDropProvider>
</div>
);
}

Wyświetl plik

@ -29,6 +29,7 @@ import { Settings } from "./Settings";
import { clearToasts, toastError } from "../ErrorToasts";
import Minus from "lucide-solid/icons/minus";
import Plus from "lucide-solid/icons/plus";
import { ParamsEditor } from "./ParamsEditor";
type Props = {
class?: string;
@ -55,7 +56,6 @@ export function Editor(props: Props) {
setInputQr,
paramsSchema,
setParamsSchema,
params,
setParams,
renderKey,
setRenderKey,
@ -429,79 +429,7 @@ export function Editor(props: Props) {
</Preview>
</div>
</div>
<div class="flex flex-col gap-2 mb-4">
<For each={Object.entries(paramsSchema())}>
{([label, { type, ...props }]) => {
if (type === "Array") {
return (
<div>
<div class="grid grid-cols-[144px_1fr] justify-items-end gap-y-2">
<div class="text-sm py-2 w-36">{label}</div>
<div class="flex gap-1">
<Show when={props.resizable}>
<FlatButton
class="p-1.5"
onClick={() =>
setParams(label, (prev: any[]) =>
prev.slice(0, -1)
)
}
>
<Minus />
</FlatButton>
<FlatButton
class="p-1.5"
onClick={() =>
setParams(label, (prev: any[]) => [
...prev,
props.props.default,
])
}
>
<Plus />
</FlatButton>
</Show>
</div>
<Index each={params[label]}>
{(v, i) => (
<>
<div class="text-sm py-2 pl-4 w-full text-left">
{i}
</div>
<Dynamic
component={
PARAM_COMPONENTS[
props.props
.type as keyof typeof PARAM_COMPONENTS
]
}
{...props.props}
value={v()}
setValue={(v: any) => setParams(label, i, v)}
/>
</>
)}
</Index>
</div>
</div>
);
}
return (
<>
<div class="flex justify-between">
<div class="text-sm py-2 w-36 shrink-0">{label}</div>
<Dynamic
component={PARAM_COMPONENTS[type]}
{...props}
value={params[label]}
setValue={(v: any) => setParams(label, v)}
/>
</div>
</>
);
}}
</For>
</div>
<ParamsEditor/>
<CodeEditor
initialValue={code()}
onSave={(code, updateThumbnail) => {

Wyświetl plik

@ -123,7 +123,6 @@ function parseField(value: any) {
() => value.props.default
);
} else {
// @ts-expect-error adding default, type validated above
value.default = PARAM_DEFAULTS[value.type];
}
}

Wyświetl plik

@ -1,4 +1,4 @@
export function debounce(func: any, delay: number) {
export function debounce(func, delay) {
let timer: any;
return (...args: any) => {
clearTimeout(timer);

Wyświetl plik

@ -10,6 +10,7 @@
"allowJs": true,
"noEmit": true,
"strict": true,
"noImplicitAny": false,
"types": ["vinxi/types/client"],
"isolatedModules": true,
"paths": {

Wyświetl plik

@ -13,6 +13,7 @@ export default defineConfig({
back: {
base: "#0e0f0f",
subtle: "#1a1b1c",
distinct: "#3d3e3e"
},
},
animation: {