refactor config

This commit is contained in:
karamvir
2023-08-03 21:48:37 -07:00
parent 6ce14ffc80
commit 760c5d521b
8 changed files with 438 additions and 387 deletions

View File

@@ -1,15 +1,12 @@
import { ReactNode } from "react";
import { ReactNode, useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@ui/card";
import { StringConfig } from "./components/string";
import { ArrayConfig } from "./components/array";
import { BooleanConfig } from "./components/boolean";
import { NumberConfig } from "./components/number";
import { Button } from "@ui/button";
import { Input } from "@ui/input";
import { Switch } from "@ui/switch";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@ui/card";
import { Selector, SearchableSelector, SelectorItem } from "./Selector";
import { Tabs, TabsList } from "@ui/tabs";
import { TabsContent, TabsTrigger } from "@radix-ui/react-tabs";
export type ConfigSetter<T> = (
update: (curr: Partial<T>) => Partial<T>
@@ -42,6 +39,20 @@ type ArrayDefaults<T extends Record<string, unknown>> = {
[P in keyof T]?: T[P] extends unknown[] ? T[P][number] : never;
};
function findDeepDisabled<T extends Record<string, unknown>>(
field: string,
disabled?: boolean | DeepDisabled<T>
) {
if (disabled === undefined) {
return false;
}
if (typeof disabled === "boolean") {
return disabled;
} else {
return disabled[field];
}
}
export function Config<T extends Record<string, unknown>>({
config,
update,
@@ -59,386 +70,118 @@ export function Config<T extends Record<string, unknown>>({
arrayDefaults?: ArrayDefaults<T>;
disabled?: boolean | DeepDisabled<T>;
}) {
const config_keys = Object.keys(config);
const [show, setShow] = useState(config_keys[0]);
return (
<div className="grid lg:grid-cols-2 gap-4">
{Object.entries(config).map(([field, value]) => {
const val = update[field] ?? value;
const overide = overrides?.[field];
if (overide && typeof overide === "function") {
return overide(update[field] ?? (value as T[string]), (updt) =>
set((curr) => ({
...curr,
[field]: updt(
update[field] ?? (config[field] as Partial<T[string]>)
),
}))
) as ReactNode;
}
if (typeof val === "string") {
return (
<StringConfig
key={field}
field={field}
val={val}
set={(u) =>
set((curr) => ({
...curr,
[field]: u(val),
}))
}
description={descriptions?.[field] as string | undefined}
disabled={findDeepDisabled(field, disabled) as boolean}
/>
);
}
if (typeof val === "boolean") {
return (
<BooleanConfig
key={field}
field={field}
val={val}
set={(u) => set((curr) => ({ ...curr, [field]: u(val) }))}
description={descriptions?.[field] as string | undefined}
disabled={findDeepDisabled(field, disabled) as boolean}
/>
);
}
if (typeof val === "number") {
return (
<NumberConfig
key={field}
field={field}
val={val}
set={(u) => set((curr) => ({ ...curr, [field]: u(val) }))}
description={descriptions?.[field] as string | undefined}
disabled={findDeepDisabled(field, disabled) as boolean}
/>
);
}
if (Array.isArray(value)) {
const val = (update[field] ? update[field] : value) as unknown[];
return (
<ArrayConfig
key={field}
field={field}
val={val}
set={set}
defaultNew={arrayDefaults?.[field] ?? ""}
disabled={findDeepDisabled(field, disabled) as boolean}
/>
);
}
return (
<Card key={field}>
<CardHeader>
<CardTitle>{field.replaceAll("_", " ")}</CardTitle>
</CardHeader>
<CardContent>
<Config
config={config[field] as Record<string, unknown>}
update={update[field] ?? {}}
set={(update) => {
<div className="flex gap-4">
<div className="flex flex-col gap-4">
{Object.keys(config).map((config) => (
<Button
key={config}
onClick={() => setShow(config)}
variant="outline"
>
{config}
</Button>
))}
</div>
<div className="w-full h-fit">
{Object.entries(config).map(([field, value]) => {
if (show !== field) return null;
const val = update[field] ?? value;
const overide = overrides?.[field];
if (overide && typeof overide === "function") {
return overide(update[field] ?? (value as T[string]), (updt) =>
set((curr) => ({
...curr,
[field]: updt(
update[field] ?? (config[field] as Partial<T[string]>)
),
}))
) as ReactNode;
}
if (typeof val === "string") {
return (
<StringConfig
key={field}
field={field}
val={val}
set={(u) =>
set((curr) => ({
...curr,
[field]: update(
curr[field] ?? (config[field] as Partial<T[string]>)
),
}));
}}
overrides={
overrides?.[field] as Overides<Record<string, unknown>>
[field]: u(val),
}))
}
disabled={findDeepDisabled(field, disabled)}
description={descriptions?.[field] as string | undefined}
disabled={findDeepDisabled(field, disabled) as boolean}
/>
</CardContent>
</Card>
);
})}
);
}
if (typeof val === "boolean") {
return (
<BooleanConfig
key={field}
field={field}
val={val}
set={(u) => set((curr) => ({ ...curr, [field]: u(val) }))}
description={descriptions?.[field] as string | undefined}
disabled={findDeepDisabled(field, disabled) as boolean}
/>
);
}
if (typeof val === "number") {
return (
<NumberConfig
key={field}
field={field}
val={val}
set={(u) => set((curr) => ({ ...curr, [field]: u(val) }))}
description={descriptions?.[field] as string | undefined}
disabled={findDeepDisabled(field, disabled) as boolean}
/>
);
}
if (Array.isArray(value)) {
const val = (update[field] ? update[field] : value) as unknown[];
return (
<ArrayConfig
key={field}
field={field}
val={val}
set={set}
defaultNew={arrayDefaults?.[field] ?? ""}
disabled={findDeepDisabled(field, disabled) as boolean}
/>
);
}
return (
<Card key={field}>
<CardHeader>
<CardTitle>{field.replaceAll("_", " ")}</CardTitle>
</CardHeader>
<CardContent>
<Config
config={config[field] as Record<string, unknown>}
update={update[field] ?? {}}
set={(update) => {
set((curr) => ({
...curr,
[field]: update(
curr[field] ?? (config[field] as Partial<T[string]>)
),
}));
}}
overrides={
overrides?.[field] as Overides<Record<string, unknown>>
}
disabled={findDeepDisabled(field, disabled)}
/>
</CardContent>
</Card>
);
})}
</div>
</div>
);
}
export function StringConfig({
field,
val,
set,
description,
disabled,
}: {
field: string;
val: string;
set: ConfigSetter<string>;
description?: string;
disabled?: boolean;
}) {
const readable_field = field.replaceAll("_", " ");
return (
<Card>
<CardHeader>
<CardTitle>{readable_field}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</CardHeader>
<CardContent>
<Input
id={field}
className="w-full"
value={val}
onChange={(e) => set(() => e.target.value)}
disabled={disabled}
/>
</CardContent>
</Card>
);
}
export function StringSelectorConfig({
field,
val,
set,
items,
label,
description,
disabled,
}: {
field: string;
val: string;
set: ConfigSetter<string>;
items: SelectorItem[];
label?: (val: string) => string;
description?: string;
disabled?: boolean;
}) {
return (
<Card>
<CardHeader className="flex flex-row justify-between items-center">
<div className="flex gap-2">
<CardTitle>{field.replaceAll("_", " ")}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</div>
<Selector
width="180px"
value={{
value: val,
label: label ? label(val) : val,
}}
items={items}
onSelect={({ value }) => set(() => value)}
disabled={disabled}
/>
</CardHeader>
</Card>
);
}
export function SearchableStringSelectorConfig({
field,
val,
set,
items,
label,
description,
disabled,
}: {
field: string;
val: string;
set: ConfigSetter<string>;
items: SelectorItem[];
label?: (val: string) => string;
description?: string;
disabled?: boolean;
}) {
return (
<Card>
<CardHeader className="flex flex-row justify-between items-center">
<div className="flex gap-2">
<CardTitle>{field.replaceAll("_", " ")}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</div>
<SearchableSelector
width="180px"
value={{
value: val,
label: label ? label(val) : val,
}}
items={items}
onSelect={(value) => set(() => value)}
disabled={disabled}
/>
</CardHeader>
</Card>
);
}
export function BooleanConfig({
field,
val,
set,
description,
disabled,
}: {
field: string;
val: boolean;
set: ConfigSetter<boolean>;
description?: string;
disabled?: boolean;
}) {
const readable_field = field.replaceAll("_", " ");
const onChange = (checked: boolean) => set(() => checked);
return (
<Card
className="flex flex-row justify-between items-center cursor-pointer"
onClick={() => onChange(!val)}
>
<CardHeader>
<CardTitle>{readable_field}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</CardHeader>
<Switch
id={field}
className="m-6"
checked={val}
onCheckedChange={onChange}
disabled={disabled}
/>
</Card>
);
}
export function NumberConfig({
field,
val,
set,
disabled,
description,
}: {
field: string;
val: number;
set: ConfigSetter<number>;
description?: string;
disabled?: boolean;
}) {
const readable_field = field.replaceAll("_", " ");
return (
<Card>
<CardHeader>
<CardTitle>{readable_field}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</CardHeader>
<CardContent>
<Input
className="w-[250px]"
type="number"
defaultValue={val}
onChange={(e) => set(() => e.target.valueAsNumber)}
disabled={disabled}
/>
</CardContent>
</Card>
);
}
export function ArrayConfig<T, D>({
field,
val,
set,
defaultNew,
description,
disabled,
}: {
field: string;
val: unknown[];
set: ConfigSetter<T>;
defaultNew: D;
description?: string;
disabled?: boolean;
}) {
return (
<Card>
<CardHeader className="flex flex-row justify-between items-center">
<div className="flex flex-col items-center">
<CardTitle>{field.replaceAll("_", " ")}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</div>
<Button
onClick={() =>
set((curr) => ({ ...curr, [field]: [...val, defaultNew] }))
}
disabled={disabled}
>
+
</Button>
</CardHeader>
{val.length > 0 && (
<CardContent className="flex flex-col gap-4">
{val.map((item, i) => {
return (
<div
key={i.toString()}
className="flex flex-row gap-4 justify-between items-center"
>
{typeof item === "string" ? (
<Input
value={val[i] as string}
onChange={(e) =>
set((curr) => {
return {
...curr,
[field]: [
...val.slice(0, i),
e.target.value,
...val.slice(i + 1),
],
};
})
}
disabled={disabled}
/>
) : typeof item === "object" ? (
<Config
config={item as Record<string, unknown>}
update={item as Record<string, unknown>}
set={(upd) =>
set((curr) => ({
...curr,
[field]: [
...val.slice(0, i),
upd(item as Record<string, unknown>),
...val.slice(i + 1),
],
}))
}
/>
) : null}
<Button
onClick={() => {
set((curr) => ({
...curr,
[field]: val.filter((_, index) => i !== index),
}));
}}
disabled={disabled}
>
-
</Button>
</div>
);
})}
</CardContent>
)}
</Card>
);
}
function findDeepDisabled<T extends Record<string, unknown>>(
field: string,
disabled?: boolean | DeepDisabled<T>
) {
if (disabled === undefined) {
return false;
}
if (typeof disabled === "boolean") {
return disabled;
} else {
return disabled[field];
}
}

View File

@@ -0,0 +1,102 @@
import { Button } from "@ui/button";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "@ui/card";
import { Input } from "@ui/input";
import { Config, ConfigSetter } from "../Config";
export function ArrayConfig<T, D>({
field,
val,
set,
defaultNew,
description,
disabled,
}: {
field: string;
val: unknown[];
set: ConfigSetter<T>;
defaultNew: D;
description?: string;
disabled?: boolean;
}) {
return (
<Card>
<CardHeader className="flex flex-row justify-between items-center">
<div className="flex flex-col items-center">
<CardTitle>{field.replaceAll("_", " ")}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</div>
<Button
onClick={() =>
set((curr) => ({ ...curr, [field]: [...val, defaultNew] }))
}
disabled={disabled}
>
+
</Button>
</CardHeader>
{val.length > 0 && (
<CardContent className="flex flex-col gap-4">
{val.map((item, i) => {
return (
<div
key={i.toString()}
className="flex flex-row gap-4 justify-between items-center"
>
{typeof item === "string" ? (
<Input
value={val[i] as string}
onChange={(e) =>
set((curr) => {
return {
...curr,
[field]: [
...val.slice(0, i),
e.target.value,
...val.slice(i + 1),
],
};
})
}
disabled={disabled}
/>
) : typeof item === "object" ? (
<Config
config={item as Record<string, unknown>}
update={item as Record<string, unknown>}
set={(upd) =>
set((curr) => ({
...curr,
[field]: [
...val.slice(0, i),
upd(item as Record<string, unknown>),
...val.slice(i + 1),
],
}))
}
/>
) : null}
<Button
onClick={() => {
set((curr) => ({
...curr,
[field]: val.filter((_, index) => i !== index),
}));
}}
disabled={disabled}
>
-
</Button>
</div>
);
})}
</CardContent>
)}
</Card>
);
}

View File

@@ -0,0 +1,38 @@
import { Switch } from "@ui/switch";
import { Card, CardHeader, CardTitle, CardDescription } from "@ui/card";
import { ConfigSetter } from "../Config";
export const BooleanConfig = ({
field,
val,
set,
description,
disabled,
}: {
field: string;
val: boolean;
set: ConfigSetter<boolean>;
description?: string;
disabled?: boolean;
}) => {
const readable_field = field.replaceAll("_", " ");
const onChange = (checked: boolean) => set(() => checked);
return (
<Card
className="flex flex-row justify-between items-center cursor-pointer"
onClick={() => onChange(!val)}
>
<CardHeader>
<CardTitle>{readable_field}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</CardHeader>
<Switch
id={field}
className="m-6"
checked={val}
onCheckedChange={onChange}
disabled={disabled}
/>
</Card>
);
};

View File

@@ -0,0 +1,42 @@
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "@ui/card";
import { Input } from "@ui/input";
import { ConfigSetter } from "../Config";
export function NumberConfig({
field,
val,
set,
disabled,
description,
}: {
field: string;
val: number;
set: ConfigSetter<number>;
description?: string;
disabled?: boolean;
}) {
const readable_field = field.replaceAll("_", " ");
return (
<Card>
<CardHeader>
<CardTitle>{readable_field}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</CardHeader>
<CardContent>
<Input
className="w-[250px]"
type="number"
defaultValue={val}
onChange={(e) => set(() => e.target.valueAsNumber)}
disabled={disabled}
/>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,42 @@
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
import { ConfigSetter } from "../Config";
import { SelectorItem, SearchableSelector } from "../Selector";
export function SearchableStringSelectorConfig({
field,
val,
set,
items,
label,
description,
disabled,
}: {
field: string;
val: string;
set: ConfigSetter<string>;
items: SelectorItem[];
label?: (val: string) => string;
description?: string;
disabled?: boolean;
}) {
return (
<Card>
<CardHeader className="flex flex-row justify-between items-center">
<div className="flex gap-2">
<CardTitle>{field.replaceAll("_", " ")}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</div>
<SearchableSelector
width="180px"
value={{
value: val,
label: label ? label(val) : val,
}}
items={items}
onSelect={(value) => set(() => value)}
disabled={disabled}
/>
</CardHeader>
</Card>
);
}

View File

@@ -0,0 +1,42 @@
import { Card, CardHeader, CardTitle, CardDescription } from "@ui/card";
import { ConfigSetter } from "../Config";
import { SelectorItem, Selector } from "../Selector";
interface StringSelectorConfigProps {
field: string;
val: string;
set: ConfigSetter<string>;
items: SelectorItem[];
label?: (val: string) => string;
description?: string;
disabled?: boolean;
}
export const StringSelectorConfig = ({
field,
val,
set,
items,
label,
description,
disabled,
}: StringSelectorConfigProps) => (
<Card>
<CardHeader className="flex flex-row justify-between items-center">
<div className="flex gap-2">
<CardTitle>{field.replaceAll("_", " ")}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</div>
<Selector
width="180px"
value={{
value: val,
label: label ? label(val) : val,
}}
items={items}
onSelect={({ value }) => set(() => value)}
disabled={disabled}
/>
</CardHeader>
</Card>
);

View File

@@ -0,0 +1,42 @@
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "@ui/card";
import { Input } from "@ui/input";
import { ConfigSetter } from "../Config";
export const StringConfig = ({
field,
val,
set,
description,
disabled,
}: {
field: string;
val: string;
set: ConfigSetter<string>;
description?: string;
disabled?: boolean;
}) => {
const readable_field = field.replaceAll("_", " ");
return (
<Card>
<CardHeader>
<CardTitle>{readable_field}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</CardHeader>
<CardContent>
<Input
id={field}
className="w-full"
value={val}
onChange={(e) => set(() => e.target.value)}
disabled={disabled}
/>
</CardContent>
</Card>
);
};

View File

@@ -15,7 +15,7 @@ export const RecentlyViewed = () => {
actions=""
>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{recents?.map(({ type, id }) => {
{recents?.slice(0, 6).map(({ type, id }) => {
if (type === "Deployment") return <DeploymentCard key={id} id={id} />;
if (type === "Build") return <BuildCard key={id} id={id} />;
if (type === "Server") return <ServerCard key={id} id={id} />;