mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
optimize elements by reducing the bundle size required
This commit is contained in:
50
app/build.ts
50
app/build.ts
@@ -1,4 +1,7 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
import { $ } from "bun";
|
import { $ } from "bun";
|
||||||
|
import { replace } from "esbuild-plugin-replace";
|
||||||
import * as tsup from "tsup";
|
import * as tsup from "tsup";
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
@@ -80,7 +83,8 @@ await tsup.build({
|
|||||||
"src/ui/index.ts",
|
"src/ui/index.ts",
|
||||||
"src/ui/client/index.ts",
|
"src/ui/client/index.ts",
|
||||||
"src/ui/elements/index.ts",
|
"src/ui/elements/index.ts",
|
||||||
"src/ui/main.css"
|
"src/ui/main.css",
|
||||||
|
"src/ui/styles.css"
|
||||||
],
|
],
|
||||||
outDir: "dist/ui",
|
outDir: "dist/ui",
|
||||||
external: [
|
external: [
|
||||||
@@ -108,6 +112,50 @@ await tsup.build({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Building UI Elements
|
||||||
|
* - tailwind-merge is mocked, no exclude
|
||||||
|
* - ui/client is external, and after built replaced with "bknd/client"
|
||||||
|
*/
|
||||||
|
await tsup.build({
|
||||||
|
minify,
|
||||||
|
sourcemap,
|
||||||
|
watch,
|
||||||
|
entry: ["src/ui/elements/index.ts"],
|
||||||
|
outDir: "dist/ui/elements",
|
||||||
|
external: [
|
||||||
|
"ui/client",
|
||||||
|
"react",
|
||||||
|
"react-dom",
|
||||||
|
"react/jsx-runtime",
|
||||||
|
"react/jsx-dev-runtime",
|
||||||
|
"use-sync-external-store"
|
||||||
|
],
|
||||||
|
metafile: true,
|
||||||
|
platform: "browser",
|
||||||
|
format: ["esm"],
|
||||||
|
splitting: false,
|
||||||
|
bundle: true,
|
||||||
|
treeshake: true,
|
||||||
|
loader: {
|
||||||
|
".svg": "dataurl"
|
||||||
|
},
|
||||||
|
esbuildOptions: (options) => {
|
||||||
|
options.alias = {
|
||||||
|
// not important for elements, mock to reduce bundle
|
||||||
|
"tailwind-merge": "./src/ui/elements/mocks/tailwind-merge.ts"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
// manually replace ui/client with bknd/client
|
||||||
|
const path = "./dist/ui/elements/index.js";
|
||||||
|
const bundle = await Bun.file(path).text();
|
||||||
|
await Bun.write(path, bundle.replaceAll("ui/client", "bknd/client"));
|
||||||
|
|
||||||
|
delayTypes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Building adapters
|
* Building adapters
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"build:types": "tsc --emitDeclarationOnly && tsc-alias",
|
"build:types": "tsc --emitDeclarationOnly && tsc-alias",
|
||||||
"updater": "bun x npm-check-updates -ui",
|
"updater": "bun x npm-check-updates -ui",
|
||||||
"cli": "LOCAL=1 bun src/cli/index.ts",
|
"cli": "LOCAL=1 bun src/cli/index.ts",
|
||||||
"prepublishOnly": "bun run test && bun run build:all"
|
"prepublishOnly": "bun run types && bun run test && bun run build:all"
|
||||||
},
|
},
|
||||||
"license": "FSL-1.1-MIT",
|
"license": "FSL-1.1-MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -29,12 +29,12 @@
|
|||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"fast-xml-parser": "^4.4.0",
|
"fast-xml-parser": "^4.4.0",
|
||||||
"hono": "^4.6.12",
|
"hono": "^4.6.12",
|
||||||
|
"json-schema-form-react": "^0.0.2",
|
||||||
"kysely": "^0.27.4",
|
"kysely": "^0.27.4",
|
||||||
"liquidjs": "^10.15.0",
|
"liquidjs": "^10.15.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"oauth4webapi": "^2.11.1",
|
"oauth4webapi": "^2.11.1",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5"
|
||||||
"json-schema-form-react": "^0.0.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.613.0",
|
"@aws-sdk/client-s3": "^3.613.0",
|
||||||
@@ -62,6 +62,7 @@
|
|||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@vitejs/plugin-react": "^4.3.3",
|
||||||
"@xyflow/react": "^12.3.2",
|
"@xyflow/react": "^12.3.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"esbuild-postcss": "^0.0.4",
|
"esbuild-postcss": "^0.0.4",
|
||||||
"jotai": "^2.10.1",
|
"jotai": "^2.10.1",
|
||||||
"open": "^10.1.0",
|
"open": "^10.1.0",
|
||||||
@@ -168,7 +169,8 @@
|
|||||||
"import": "./dist/adapter/astro/index.js",
|
"import": "./dist/adapter/astro/index.js",
|
||||||
"require": "./dist/adapter/astro/index.cjs"
|
"require": "./dist/adapter/astro/index.cjs"
|
||||||
},
|
},
|
||||||
"./dist/styles.css": "./dist/ui/main.css",
|
"./dist/main.css": "./dist/ui/main.css",
|
||||||
|
"./dist/styles.css": "./dist/ui/styles.css",
|
||||||
"./dist/manifest.json": "./dist/static/.vite/manifest.json"
|
"./dist/manifest.json": "./dist/static/.vite/manifest.json"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type BaseModuleApiOptions, ModuleApi, type PrimaryFieldType } from "modules/ModuleApi";
|
import { type BaseModuleApiOptions, ModuleApi, type PrimaryFieldType } from "modules/ModuleApi";
|
||||||
import type { FileWithPath } from "ui/modules/media/components/dropzone/file-selector";
|
import type { FileWithPath } from "ui/elements/media/file-selector";
|
||||||
|
|
||||||
export type MediaApiOptions = BaseModuleApiOptions & {};
|
export type MediaApiOptions = BaseModuleApiOptions & {};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { TObject, TString } from "@sinclair/typebox";
|
import type { TObject, TString } from "@sinclair/typebox";
|
||||||
import { type Constructor, Registry } from "core";
|
import { type Constructor, Registry } from "core";
|
||||||
|
|
||||||
export { MIME_TYPES } from "./storage/mime-types";
|
//export { MIME_TYPES } from "./storage/mime-types";
|
||||||
|
export { guess as guessMimeType } from "./storage/mime-types-tiny";
|
||||||
export {
|
export {
|
||||||
Storage,
|
Storage,
|
||||||
type StorageAdapter,
|
type StorageAdapter,
|
||||||
@@ -19,7 +20,7 @@ import { type S3AdapterConfig, StorageS3Adapter } from "./storage/adapters/Stora
|
|||||||
export { StorageS3Adapter, type S3AdapterConfig, StorageCloudinaryAdapter, type CloudinaryConfig };
|
export { StorageS3Adapter, type S3AdapterConfig, StorageCloudinaryAdapter, type CloudinaryConfig };
|
||||||
|
|
||||||
export * as StorageEvents from "./storage/events";
|
export * as StorageEvents from "./storage/events";
|
||||||
export { type FileUploadedEventData } from "./storage/events";
|
export type { FileUploadedEventData } from "./storage/events";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
|
|
||||||
type ClassThatImplements<T> = Constructor<T> & { prototype: T };
|
type ClassThatImplements<T> = Constructor<T> & { prototype: T };
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export type ClientProviderProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ClientProvider = ({ children, baseUrl, user }: ClientProviderProps) => {
|
export const ClientProvider = ({ children, baseUrl, user }: ClientProviderProps) => {
|
||||||
//const [actualBaseUrl, setActualBaseUrl] = useState<string | null>(null);
|
|
||||||
const winCtx = useBkndWindowContext();
|
const winCtx = useBkndWindowContext();
|
||||||
const _ctx_baseUrl = useBaseUrl();
|
const _ctx_baseUrl = useBaseUrl();
|
||||||
let actualBaseUrl = baseUrl ?? _ctx_baseUrl ?? "";
|
let actualBaseUrl = baseUrl ?? _ctx_baseUrl ?? "";
|
||||||
@@ -31,6 +30,7 @@ export const ClientProvider = ({ children, baseUrl, user }: ClientProviderProps)
|
|||||||
console.error("error .....", e);
|
console.error("error .....", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("api init", { host: actualBaseUrl, user: user ?? winCtx.user });
|
||||||
const api = new Api({ host: actualBaseUrl, user: user ?? winCtx.user });
|
const api = new Api({ host: actualBaseUrl, user: user ?? winCtx.user });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -73,23 +73,3 @@ export const useAuth = (options?: { baseUrl?: string }): UseAuth => {
|
|||||||
verify
|
verify
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type AuthStrategyData = Pick<AppAuthSchema, "strategies" | "basepath">;
|
|
||||||
export const useAuthStrategies = (options?: { baseUrl?: string }): Partial<AuthStrategyData> & {
|
|
||||||
loading: boolean;
|
|
||||||
} => {
|
|
||||||
const [data, setData] = useState<AuthStrategyData>();
|
|
||||||
const api = useApi(options?.baseUrl);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
const res = await api.auth.strategies();
|
|
||||||
//console.log("res", res);
|
|
||||||
if (res.res.ok) {
|
|
||||||
setData(res.body);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [options?.baseUrl]);
|
|
||||||
|
|
||||||
return { strategies: data?.strategies, basepath: data?.basepath, loading: !data };
|
|
||||||
};
|
|
||||||
|
|||||||
29
app/src/ui/components/form/Formy/BooleanInputMantine.tsx
Normal file
29
app/src/ui/components/form/Formy/BooleanInputMantine.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Switch } from "@mantine/core";
|
||||||
|
import { forwardRef, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export const BooleanInputMantine = forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||||
|
(props, ref) => {
|
||||||
|
const [checked, setChecked] = useState(Boolean(props.value));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setChecked(Boolean(props.value));
|
||||||
|
}, [props.value]);
|
||||||
|
|
||||||
|
function handleCheck(e) {
|
||||||
|
setChecked(e.target.checked);
|
||||||
|
props.onChange?.(e.target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<Switch
|
||||||
|
ref={ref}
|
||||||
|
checked={checked}
|
||||||
|
onChange={handleCheck}
|
||||||
|
disabled={props.disabled}
|
||||||
|
id={props.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Switch } from "@mantine/core";
|
|
||||||
import { getBrowser } from "core/utils";
|
import { getBrowser } from "core/utils";
|
||||||
import type { Field } from "data";
|
import type { Field } from "data";
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||||
import { TbCalendar, TbChevronDown, TbInfoCircle } from "react-icons/tb";
|
import { TbCalendar, TbChevronDown, TbInfoCircle } from "react-icons/tb";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import { useEvent } from "../../hooks/use-event";
|
import { IconButton } from "ui/components/buttons/IconButton";
|
||||||
import { IconButton } from "../buttons/IconButton";
|
import { useEvent } from "ui/hooks/use-event";
|
||||||
|
|
||||||
export const Group: React.FC<React.ComponentProps<"div"> & { error?: boolean }> = ({
|
export const Group: React.FC<React.ComponentProps<"div"> & { error?: boolean }> = ({
|
||||||
error,
|
error,
|
||||||
@@ -131,17 +130,6 @@ export const BooleanInput = forwardRef<HTMLInputElement, React.ComponentProps<"i
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row">
|
|
||||||
<Switch
|
|
||||||
ref={ref}
|
|
||||||
checked={checked}
|
|
||||||
onChange={handleCheck}
|
|
||||||
disabled={props.disabled}
|
|
||||||
id={props.id}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
/*return (
|
|
||||||
<div className="h-11 flex items-center">
|
<div className="h-11 flex items-center">
|
||||||
<input
|
<input
|
||||||
{...props}
|
{...props}
|
||||||
@@ -153,7 +141,7 @@ export const BooleanInput = forwardRef<HTMLInputElement, React.ComponentProps<"i
|
|||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);*/
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
17
app/src/ui/components/form/Formy/index.ts
Normal file
17
app/src/ui/components/form/Formy/index.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { BooleanInputMantine } from "./BooleanInputMantine";
|
||||||
|
import { DateInput, Input, Textarea } from "./components";
|
||||||
|
|
||||||
|
export const formElementFactory = (element: string, props: any) => {
|
||||||
|
switch (element) {
|
||||||
|
case "date":
|
||||||
|
return DateInput;
|
||||||
|
case "boolean":
|
||||||
|
return BooleanInputMantine;
|
||||||
|
case "textarea":
|
||||||
|
return Textarea;
|
||||||
|
default:
|
||||||
|
return Input;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export * from "./components";
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import type { ValueError } from "@sinclair/typebox/value";
|
import type { ValueError } from "@sinclair/typebox/value";
|
||||||
import type { AppAuthOAuthStrategy, AppAuthSchema } from "auth/auth-schema";
|
import type { AppAuthOAuthStrategy, AppAuthSchema } from "auth/auth-schema";
|
||||||
|
import clsx from "clsx";
|
||||||
import { type TSchema, Type, Value } from "core/utils";
|
import { type TSchema, Type, Value } from "core/utils";
|
||||||
import { Form, type Validator } from "json-schema-form-react";
|
import { Form, type Validator } from "json-schema-form-react";
|
||||||
import { transform } from "lodash-es";
|
import { transform } from "lodash-es";
|
||||||
import type { ComponentPropsWithoutRef } from "react";
|
import type { ComponentPropsWithoutRef } from "react";
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
import { Button } from "ui/components/buttons/Button";
|
import { Button } from "ui/components/buttons/Button";
|
||||||
import { Group, Input, Label } from "ui/components/form/Formy";
|
import { Group, Input, Label } from "ui/components/form/Formy/components";
|
||||||
import { SocialLink } from "ui/modules/auth/SocialLink";
|
import { SocialLink } from "./SocialLink";
|
||||||
|
|
||||||
export type LoginFormProps = Omit<ComponentPropsWithoutRef<"form">, "onSubmit" | "action"> & {
|
export type LoginFormProps = Omit<ComponentPropsWithoutRef<"form">, "onSubmit" | "action"> & {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -86,7 +86,7 @@ export function AuthForm({
|
|||||||
schema={schema}
|
schema={schema}
|
||||||
validator={validator}
|
validator={validator}
|
||||||
validationMode="change"
|
validationMode="change"
|
||||||
className={twMerge("flex flex-col gap-3 w-full", className)}
|
className={clsx("flex flex-col gap-3 w-full", className)}
|
||||||
>
|
>
|
||||||
{({ errors, submitting }) => (
|
{({ errors, submitting }) => (
|
||||||
<>
|
<>
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { useAuthStrategies } from "ui/client/schema/auth/use-auth";
|
import { useAuthStrategies } from "../hooks/use-auth";
|
||||||
import { Logo } from "ui/components/display/Logo";
|
import { AuthForm } from "./AuthForm";
|
||||||
import { Link } from "ui/components/wouter/Link";
|
|
||||||
import { AuthForm } from "ui/modules/auth/AuthForm";
|
|
||||||
|
|
||||||
export type AuthScreenProps = {
|
export type AuthScreenProps = {
|
||||||
method?: "POST" | "GET";
|
method?: "POST" | "GET";
|
||||||
@@ -18,13 +16,7 @@ export function AuthScreen({ method = "POST", action = "login", logo, intro }: A
|
|||||||
<div className="flex flex-1 flex-col select-none h-dvh w-dvw justify-center items-center bknd-admin">
|
<div className="flex flex-1 flex-col select-none h-dvh w-dvw justify-center items-center bknd-admin">
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<div className="flex flex-col gap-4 items-center w-96 px-6 py-7">
|
<div className="flex flex-col gap-4 items-center w-96 px-6 py-7">
|
||||||
{typeof logo !== "undefined" ? (
|
{logo ? logo : null}
|
||||||
logo
|
|
||||||
) : (
|
|
||||||
<Link href={"/"} className="link">
|
|
||||||
<Logo scale={0.25} />
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
{typeof intro !== "undefined" ? (
|
{typeof intro !== "undefined" ? (
|
||||||
intro
|
intro
|
||||||
) : (
|
) : (
|
||||||
9
app/src/ui/elements/auth/index.ts
Normal file
9
app/src/ui/elements/auth/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { AuthForm } from "./AuthForm";
|
||||||
|
import { AuthScreen } from "./AuthScreen";
|
||||||
|
import { SocialLink } from "./SocialLink";
|
||||||
|
|
||||||
|
export const Auth = {
|
||||||
|
Screen: AuthScreen,
|
||||||
|
Form: AuthForm,
|
||||||
|
SocialLink: SocialLink
|
||||||
|
};
|
||||||
23
app/src/ui/elements/hooks/use-auth.ts
Normal file
23
app/src/ui/elements/hooks/use-auth.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { AppAuthSchema } from "auth/auth-schema";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useApi } from "ui/client";
|
||||||
|
|
||||||
|
type AuthStrategyData = Pick<AppAuthSchema, "strategies" | "basepath">;
|
||||||
|
export const useAuthStrategies = (options?: { baseUrl?: string }): Partial<AuthStrategyData> & {
|
||||||
|
loading: boolean;
|
||||||
|
} => {
|
||||||
|
const [data, setData] = useState<AuthStrategyData>();
|
||||||
|
const api = useApi(options?.baseUrl);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const res = await api.auth.strategies();
|
||||||
|
//console.log("res", res);
|
||||||
|
if (res.res.ok) {
|
||||||
|
setData(res.body);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [options?.baseUrl]);
|
||||||
|
|
||||||
|
return { strategies: data?.strategies, basepath: data?.basepath, loading: !data };
|
||||||
|
};
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export { Auth } from "ui/modules/auth/index";
|
export { Auth } from "./auth";
|
||||||
export * from "./media";
|
export * from "./media";
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { PreviewWrapperMemoized } from "ui/modules/media/components/dropzone/Dropzone";
|
|
||||||
import { DropzoneContainer } from "ui/modules/media/components/dropzone/DropzoneContainer";
|
|
||||||
|
|
||||||
export const Media = {
|
|
||||||
Dropzone: DropzoneContainer,
|
|
||||||
Preview: PreviewWrapperMemoized
|
|
||||||
};
|
|
||||||
|
|
||||||
export type {
|
|
||||||
PreviewComponentProps,
|
|
||||||
FileState,
|
|
||||||
DropzoneProps,
|
|
||||||
DropzoneRenderProps
|
|
||||||
} from "ui/modules/media/components/dropzone/Dropzone";
|
|
||||||
export type { DropzoneContainerProps } from "ui/modules/media/components/dropzone/DropzoneContainer";
|
|
||||||
@@ -4,13 +4,8 @@ import type { TAppMediaConfig } from "media/media-schema";
|
|||||||
import { useId } from "react";
|
import { useId } from "react";
|
||||||
import { useApi, useBaseUrl, useEntityQuery, useInvalidate } from "ui/client";
|
import { useApi, useBaseUrl, useEntityQuery, useInvalidate } from "ui/client";
|
||||||
import { useEvent } from "ui/hooks/use-event";
|
import { useEvent } from "ui/hooks/use-event";
|
||||||
import {
|
import { Dropzone, type DropzoneProps, type DropzoneRenderProps, type FileState } from "./Dropzone";
|
||||||
Dropzone,
|
import { mediaItemsToFileStates } from "./helper";
|
||||||
type DropzoneProps,
|
|
||||||
type DropzoneRenderProps,
|
|
||||||
type FileState
|
|
||||||
} from "ui/modules/media/components/dropzone/Dropzone";
|
|
||||||
import { mediaItemsToFileStates } from "ui/modules/media/helper";
|
|
||||||
|
|
||||||
export type DropzoneContainerProps = {
|
export type DropzoneContainerProps = {
|
||||||
children?: (props: DropzoneRenderProps) => JSX.Element;
|
children?: (props: DropzoneRenderProps) => JSX.Element;
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* MIT License (2020 Roland Groza)
|
* MIT License (2020 Roland Groza)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MIME_TYPES } from "media";
|
import { guess } from "media/storage/mime-types-tiny";
|
||||||
|
|
||||||
const FILES_TO_IGNORE = [
|
const FILES_TO_IGNORE = [
|
||||||
// Thumbnail cache files for macOS and Windows
|
// Thumbnail cache files for macOS and Windows
|
||||||
@@ -47,10 +47,8 @@ function withMimeType(file: FileWithPath) {
|
|||||||
console.log("withMimeType", name, hasExtension);
|
console.log("withMimeType", name, hasExtension);
|
||||||
|
|
||||||
if (hasExtension && !file.type) {
|
if (hasExtension && !file.type) {
|
||||||
const ext = name.split(".").pop()!.toLowerCase();
|
const type = guess(name);
|
||||||
const type = MIME_TYPES.get(ext);
|
console.log("guessed", type);
|
||||||
|
|
||||||
console.log("withMimeType:in", ext, type);
|
|
||||||
|
|
||||||
if (type) {
|
if (type) {
|
||||||
Object.defineProperty(file, "type", {
|
Object.defineProperty(file, "type", {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { MediaFieldSchema } from "media/AppMedia";
|
import type { MediaFieldSchema } from "media/AppMedia";
|
||||||
import type { FileState } from "./components/dropzone/Dropzone";
|
import type { FileState } from "./Dropzone";
|
||||||
|
|
||||||
export function mediaItemToFileState(
|
export function mediaItemToFileState(
|
||||||
item: MediaFieldSchema,
|
item: MediaFieldSchema,
|
||||||
15
app/src/ui/elements/media/index.ts
Normal file
15
app/src/ui/elements/media/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { PreviewWrapperMemoized } from "./Dropzone";
|
||||||
|
import { DropzoneContainer } from "./DropzoneContainer";
|
||||||
|
|
||||||
|
export const Media = {
|
||||||
|
Dropzone: DropzoneContainer,
|
||||||
|
Preview: PreviewWrapperMemoized
|
||||||
|
};
|
||||||
|
|
||||||
|
export type {
|
||||||
|
PreviewComponentProps,
|
||||||
|
FileState,
|
||||||
|
DropzoneProps,
|
||||||
|
DropzoneRenderProps
|
||||||
|
} from "./Dropzone";
|
||||||
|
export type { DropzoneContainerProps } from "./DropzoneContainer";
|
||||||
3
app/src/ui/elements/mocks/tailwind-merge.ts
Normal file
3
app/src/ui/elements/mocks/tailwind-merge.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function twMerge(...classes: string[]) {
|
||||||
|
return classes.filter(Boolean).join(" ");
|
||||||
|
}
|
||||||
@@ -1,211 +1,77 @@
|
|||||||
@import "./components/form/json-schema/styles.css";
|
|
||||||
@import "@xyflow/react/dist/style.css";
|
|
||||||
@import "@mantine/core/styles.css";
|
|
||||||
@import "@mantine/notifications/styles.css";
|
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
html.fixed,
|
|
||||||
html.fixed body {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
position: fixed;
|
|
||||||
overflow: hidden;
|
|
||||||
overscroll-behavior-x: contain;
|
|
||||||
touch-action: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#bknd-admin,
|
|
||||||
.bknd-admin {
|
|
||||||
--color-primary: 9 9 11; /* zinc-950 */
|
|
||||||
--color-background: 250 250 250; /* zinc-50 */
|
|
||||||
--color-muted: 228 228 231; /* ? */
|
|
||||||
--color-darkest: 0 0 0; /* black */
|
|
||||||
--color-lightest: 255 255 255; /* white */
|
|
||||||
|
|
||||||
&.dark {
|
|
||||||
--color-primary: 250 250 250; /* zinc-50 */
|
|
||||||
--color-background: 30 31 34;
|
|
||||||
--color-muted: 47 47 52;
|
|
||||||
--color-darkest: 255 255 255; /* white */
|
|
||||||
--color-lightest: 24 24 27; /* black */
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
--mantine-color-body: rgb(250 250 250);
|
|
||||||
}
|
|
||||||
@mixin dark {
|
|
||||||
--mantine-color-body: rgb(9 9 11);
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
font-size: 14px;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
overscroll-behavior-y: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#bknd-admin {
|
#bknd-admin {
|
||||||
@apply bg-background text-primary overflow-hidden h-dvh w-dvw;
|
@apply bg-background text-primary overflow-hidden h-dvh w-dvw;
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
@apply bg-muted;
|
@apply bg-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
&::selection {
|
&::selection {
|
||||||
@apply bg-primary/15;
|
@apply bg-primary/15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body,
|
body,
|
||||||
#bknd-admin {
|
#bknd-admin {
|
||||||
@apply flex flex-1 flex-col h-dvh w-dvw;
|
@apply flex flex-1 flex-col h-dvh w-dvw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
.link {
|
.link {
|
||||||
@apply transition-colors active:translate-y-px;
|
@apply transition-colors active:translate-y-px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.img-responsive {
|
.img-responsive {
|
||||||
@apply max-h-full w-auto;
|
@apply max-h-full w-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* debug classes
|
* debug classes
|
||||||
*/
|
*/
|
||||||
.bordered-red {
|
.bordered-red {
|
||||||
@apply border-2 border-red-500;
|
@apply border-2 border-red-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bordered-green {
|
.bordered-green {
|
||||||
@apply border-2 border-green-500;
|
@apply border-2 border-green-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bordered-blue {
|
.bordered-blue {
|
||||||
@apply border-2 border-blue-500;
|
@apply border-2 border-blue-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bordered-violet {
|
.bordered-violet {
|
||||||
@apply border-2 border-violet-500;
|
@apply border-2 border-violet-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bordered-yellow {
|
.bordered-yellow {
|
||||||
@apply border-2 border-yellow-500;
|
@apply border-2 border-yellow-500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
|
||||||
.app-scrollbar::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
/* Hide scrollbar for IE, Edge and Firefox */
|
|
||||||
.app-scrollbar {
|
|
||||||
-ms-overflow-style: none; /* IE and Edge */
|
|
||||||
scrollbar-width: none; /* Firefox */
|
|
||||||
}
|
|
||||||
|
|
||||||
div[data-radix-scroll-area-viewport] > div:first-child {
|
|
||||||
display: block !important;
|
|
||||||
min-width: 100% !important;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* hide calendar icon on inputs */
|
|
||||||
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
|
|
||||||
input[type="date"]::-webkit-calendar-picker-indicator {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* cm */
|
|
||||||
.cm-editor {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-fade-in {
|
|
||||||
animation: fadeInAnimation 200ms ease;
|
|
||||||
}
|
|
||||||
@keyframes fadeInAnimation {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input[readonly]::placeholder,
|
|
||||||
input[disabled]::placeholder {
|
|
||||||
opacity: 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-flow__pane,
|
|
||||||
.react-flow__renderer,
|
|
||||||
.react-flow__node,
|
|
||||||
.react-flow__edge {
|
|
||||||
cursor: inherit !important;
|
|
||||||
.drag-handle {
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.react-flow .react-flow__edge path,
|
|
||||||
.react-flow__connectionline path {
|
|
||||||
stroke-width: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mantine-TextInput-wrapper input {
|
|
||||||
font-family: inherit;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-editor {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.cm-editor.cm-focused {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-animate {
|
|
||||||
transition: flex-grow 0.2s ease, background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
.flex-initial {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
}
|
|
||||||
.flex-open {
|
|
||||||
flex: 1 1 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#bknd-admin,
|
#bknd-admin,
|
||||||
.bknd-admin {
|
.bknd-admin {
|
||||||
/* Chrome, Edge, and Safari */
|
/* Chrome, Edge, and Safari */
|
||||||
& *::-webkit-scrollbar {
|
& *::-webkit-scrollbar {
|
||||||
@apply w-1;
|
@apply w-1;
|
||||||
&:horizontal {
|
&:horizontal {
|
||||||
@apply h-px;
|
@apply h-px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& *::-webkit-scrollbar-track {
|
& *::-webkit-scrollbar-track {
|
||||||
@apply bg-transparent w-1;
|
@apply bg-transparent w-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
& *::-webkit-scrollbar-thumb {
|
& *::-webkit-scrollbar-thumb {
|
||||||
@apply bg-primary/25;
|
@apply bg-primary/25;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as React from "react";
|
|||||||
import * as ReactDOM from "react-dom/client";
|
import * as ReactDOM from "react-dom/client";
|
||||||
import Admin from "./Admin";
|
import Admin from "./Admin";
|
||||||
import "./main.css";
|
import "./main.css";
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import { AuthForm } from "ui/modules/auth/AuthForm";
|
|
||||||
import { AuthScreen } from "ui/modules/auth/AuthScreen";
|
|
||||||
import { SocialLink } from "ui/modules/auth/SocialLink";
|
|
||||||
|
|
||||||
export const Auth = {
|
|
||||||
Screen: AuthScreen,
|
|
||||||
Form: AuthForm,
|
|
||||||
SocialLink: SocialLink
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,18 @@
|
|||||||
|
import { Logo } from "ui/components/display/Logo";
|
||||||
|
import { Link } from "ui/components/wouter/Link";
|
||||||
|
import { Auth } from "ui/elements";
|
||||||
import { useBrowserTitle } from "ui/hooks/use-browser-title";
|
import { useBrowserTitle } from "ui/hooks/use-browser-title";
|
||||||
import { AuthScreen } from "ui/modules/auth/AuthScreen";
|
|
||||||
|
|
||||||
export function AuthLogin() {
|
export function AuthLogin() {
|
||||||
useBrowserTitle(["Login"]);
|
useBrowserTitle(["Login"]);
|
||||||
return <AuthScreen action="login" />;
|
return (
|
||||||
|
<Auth.Screen
|
||||||
|
action="login"
|
||||||
|
logo={
|
||||||
|
<Link href={"/"} className="link">
|
||||||
|
<Logo scale={0.25} />
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
133
app/src/ui/styles.css
Normal file
133
app/src/ui/styles.css
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
@import "./components/form/json-schema/styles.css";
|
||||||
|
@import "@xyflow/react/dist/style.css";
|
||||||
|
@import "@mantine/core/styles.css";
|
||||||
|
@import "@mantine/notifications/styles.css";
|
||||||
|
|
||||||
|
html.fixed,
|
||||||
|
html.fixed body {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
overflow: hidden;
|
||||||
|
overscroll-behavior-x: contain;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bknd-admin,
|
||||||
|
.bknd-admin {
|
||||||
|
--color-primary: 9 9 11; /* zinc-950 */
|
||||||
|
--color-background: 250 250 250; /* zinc-50 */
|
||||||
|
--color-muted: 228 228 231; /* ? */
|
||||||
|
--color-darkest: 0 0 0; /* black */
|
||||||
|
--color-lightest: 255 255 255; /* white */
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
--color-primary: 250 250 250; /* zinc-50 */
|
||||||
|
--color-background: 30 31 34;
|
||||||
|
--color-muted: 47 47 52;
|
||||||
|
--color-darkest: 255 255 255; /* white */
|
||||||
|
--color-lightest: 24 24 27; /* black */
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin light {
|
||||||
|
--mantine-color-body: rgb(250 250 250);
|
||||||
|
}
|
||||||
|
@mixin dark {
|
||||||
|
--mantine-color-body: rgb(9 9 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
font-size: 14px;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
overscroll-behavior-y: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
|
.app-scrollbar::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
/* Hide scrollbar for IE, Edge and Firefox */
|
||||||
|
.app-scrollbar {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
div[data-radix-scroll-area-viewport] > div:first-child {
|
||||||
|
display: block !important;
|
||||||
|
min-width: 100% !important;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hide calendar icon on inputs */
|
||||||
|
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
|
||||||
|
input[type="date"]::-webkit-calendar-picker-indicator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cm */
|
||||||
|
.cm-editor {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in {
|
||||||
|
animation: fadeInAnimation 200ms ease;
|
||||||
|
}
|
||||||
|
@keyframes fadeInAnimation {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[readonly]::placeholder,
|
||||||
|
input[disabled]::placeholder {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__pane,
|
||||||
|
.react-flow__renderer,
|
||||||
|
.react-flow__node,
|
||||||
|
.react-flow__edge {
|
||||||
|
cursor: inherit !important;
|
||||||
|
.drag-handle {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.react-flow .react-flow__edge path,
|
||||||
|
.react-flow__connectionline path {
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mantine-TextInput-wrapper input {
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-editor {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.cm-editor.cm-focused {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-animate {
|
||||||
|
transition: flex-grow 0.2s ease, background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.flex-initial {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
.flex-open {
|
||||||
|
flex: 1 1 0;
|
||||||
|
}
|
||||||
@@ -19,6 +19,11 @@
|
|||||||
"trailingCommas": "none"
|
"trailingCommas": "none"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"css": {
|
||||||
|
"formatter": {
|
||||||
|
"indentWidth": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
@@ -74,4 +79,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user