mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge pull request #108 from bknd-io/feat/update-react-deps
upgrade dependencies
This commit is contained in:
@@ -32,7 +32,7 @@ describe("MediaApi", () => {
|
|||||||
host,
|
host,
|
||||||
basepath,
|
basepath,
|
||||||
});
|
});
|
||||||
expect(api.getFileUploadUrl({ path: "path" })).toBe(`${host}${basepath}/upload/path`);
|
expect(api.getFileUploadUrl({ path: "path" } as any)).toBe(`${host}${basepath}/upload/path`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have correct upload headers", () => {
|
it("should have correct upload headers", () => {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ describe("OAuthStrategy", async () => {
|
|||||||
const strategy = new OAuthStrategy({
|
const strategy = new OAuthStrategy({
|
||||||
type: "oidc",
|
type: "oidc",
|
||||||
client: {
|
client: {
|
||||||
client_id: process.env.OAUTH_CLIENT_ID,
|
client_id: process.env.OAUTH_CLIENT_ID!,
|
||||||
client_secret: process.env.OAUTH_CLIENT_SECRET,
|
client_secret: process.env.OAUTH_CLIENT_SECRET!,
|
||||||
},
|
},
|
||||||
name: "google",
|
name: "google",
|
||||||
});
|
});
|
||||||
@@ -19,11 +19,6 @@ describe("OAuthStrategy", async () => {
|
|||||||
const config = await strategy.getConfig();
|
const config = await strategy.getConfig();
|
||||||
console.log("config", JSON.stringify(config, null, 2));
|
console.log("config", JSON.stringify(config, null, 2));
|
||||||
|
|
||||||
const request = await strategy.request({
|
|
||||||
redirect_uri,
|
|
||||||
state,
|
|
||||||
});
|
|
||||||
|
|
||||||
const server = Bun.serve({
|
const server = Bun.serve({
|
||||||
fetch: async (req) => {
|
fetch: async (req) => {
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
@@ -39,6 +34,11 @@ describe("OAuthStrategy", async () => {
|
|||||||
return new Response("Bun!");
|
return new Response("Bun!");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const request = await strategy.request({
|
||||||
|
redirect_uri,
|
||||||
|
state,
|
||||||
|
});
|
||||||
console.log("request", request);
|
console.log("request", request);
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100000));
|
await new Promise((resolve) => setTimeout(resolve, 100000));
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import Database from "libsql";
|
|||||||
import { format as sqlFormat } from "sql-formatter";
|
import { format as sqlFormat } from "sql-formatter";
|
||||||
import { type Connection, EntityManager, SqliteLocalConnection } from "../src/data";
|
import { type Connection, EntityManager, SqliteLocalConnection } from "../src/data";
|
||||||
import type { em as protoEm } from "../src/data/prototype";
|
import type { em as protoEm } from "../src/data/prototype";
|
||||||
|
import { writeFile } from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { slugify } from "core/utils/strings";
|
||||||
|
|
||||||
export function getDummyDatabase(memory: boolean = true): {
|
export function getDummyDatabase(memory: boolean = true): {
|
||||||
dummyDb: SqliteDatabase;
|
dummyDb: SqliteDatabase;
|
||||||
@@ -71,3 +74,46 @@ export function schemaToEm(s: ReturnType<typeof protoEm>, conn?: Connection): En
|
|||||||
|
|
||||||
export const assetsPath = `${import.meta.dir}/_assets`;
|
export const assetsPath = `${import.meta.dir}/_assets`;
|
||||||
export const assetsTmpPath = `${import.meta.dir}/_assets/tmp`;
|
export const assetsTmpPath = `${import.meta.dir}/_assets/tmp`;
|
||||||
|
|
||||||
|
export async function enableFetchLogging() {
|
||||||
|
const originalFetch = global.fetch;
|
||||||
|
|
||||||
|
global.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
const response = await originalFetch(input, init);
|
||||||
|
const url = input instanceof URL || typeof input === "string" ? input : input.url;
|
||||||
|
|
||||||
|
// Only clone if it's a supported content type
|
||||||
|
const contentType = response.headers.get("content-type") || "";
|
||||||
|
const isSupported =
|
||||||
|
contentType.includes("json") ||
|
||||||
|
contentType.includes("text") ||
|
||||||
|
contentType.includes("xml");
|
||||||
|
|
||||||
|
if (isSupported) {
|
||||||
|
const clonedResponse = response.clone();
|
||||||
|
let extension = "txt";
|
||||||
|
let body: string;
|
||||||
|
|
||||||
|
if (contentType.includes("json")) {
|
||||||
|
body = JSON.stringify(await clonedResponse.json(), null, 2);
|
||||||
|
extension = "json";
|
||||||
|
} else if (contentType.includes("xml")) {
|
||||||
|
body = await clonedResponse.text();
|
||||||
|
extension = "xml";
|
||||||
|
} else {
|
||||||
|
body = await clonedResponse.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = `${new Date().getTime()}_${init?.method ?? "GET"}_${slugify(String(url))}.${extension}`;
|
||||||
|
const filePath = join(assetsTmpPath, fileName);
|
||||||
|
|
||||||
|
await writeFile(filePath, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
global.fetch = originalFetch;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ function makeName(ext: string) {
|
|||||||
return randomString(10) + "." + ext;
|
return randomString(10) + "." + ext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*beforeAll(disableConsoleLog);
|
beforeAll(disableConsoleLog);
|
||||||
afterAll(enableConsoleLog);*/
|
afterAll(enableConsoleLog);
|
||||||
|
|
||||||
describe("MediaController", () => {
|
describe("MediaController", () => {
|
||||||
test.only("accepts direct", async () => {
|
test.only("accepts direct", async () => {
|
||||||
@@ -56,9 +56,9 @@ describe("MediaController", () => {
|
|||||||
console.log(result);
|
console.log(result);
|
||||||
expect(result.name).toBe(name);
|
expect(result.name).toBe(name);
|
||||||
|
|
||||||
/*const destFile = Bun.file(assetsTmpPath + "/" + name);
|
const destFile = Bun.file(assetsTmpPath + "/" + name);
|
||||||
expect(destFile.exists()).resolves.toBe(true);
|
expect(destFile.exists()).resolves.toBe(true);
|
||||||
await destFile.delete();*/
|
await destFile.delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("accepts form data", async () => {
|
test("accepts form data", async () => {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { randomString } from "../../../src/core/utils";
|
import { randomString } from "../../../src/core/utils";
|
||||||
import { StorageS3Adapter } from "../../../src/media";
|
import { StorageS3Adapter } from "../../../src/media";
|
||||||
|
|
||||||
import { config } from "dotenv";
|
import { config } from "dotenv";
|
||||||
|
//import { enableFetchLogging } from "../../helper";
|
||||||
const dotenvOutput = config({ path: `${import.meta.dir}/../../../.env` });
|
const dotenvOutput = config({ path: `${import.meta.dir}/../../../.env` });
|
||||||
const { R2_ACCESS_KEY, R2_SECRET_ACCESS_KEY, R2_URL, AWS_ACCESS_KEY, AWS_SECRET_KEY, AWS_S3_URL } =
|
const { R2_ACCESS_KEY, R2_SECRET_ACCESS_KEY, R2_URL, AWS_ACCESS_KEY, AWS_SECRET_KEY, AWS_S3_URL } =
|
||||||
dotenvOutput.parsed!;
|
dotenvOutput.parsed!;
|
||||||
@@ -11,7 +12,17 @@ const { R2_ACCESS_KEY, R2_SECRET_ACCESS_KEY, R2_URL, AWS_ACCESS_KEY, AWS_SECRET_
|
|||||||
const ALL_TESTS = !!process.env.ALL_TESTS;
|
const ALL_TESTS = !!process.env.ALL_TESTS;
|
||||||
console.log("ALL_TESTS?", ALL_TESTS);
|
console.log("ALL_TESTS?", ALL_TESTS);
|
||||||
|
|
||||||
describe.skipIf(true)("StorageS3Adapter", async () => {
|
/*
|
||||||
|
// @todo: preparation to mock s3 calls + replace fast-xml-parser
|
||||||
|
let cleanup: () => void;
|
||||||
|
beforeAll(async () => {
|
||||||
|
cleanup = await enableFetchLogging();
|
||||||
|
});
|
||||||
|
afterAll(() => {
|
||||||
|
cleanup();
|
||||||
|
}); */
|
||||||
|
|
||||||
|
describe.skipIf(ALL_TESTS)("StorageS3Adapter", async () => {
|
||||||
if (ALL_TESTS) return;
|
if (ALL_TESTS) return;
|
||||||
|
|
||||||
const versions = [
|
const versions = [
|
||||||
@@ -66,7 +77,7 @@ describe.skipIf(true)("StorageS3Adapter", async () => {
|
|||||||
|
|
||||||
test.skipIf(disabled("putObject"))("puts an object", async () => {
|
test.skipIf(disabled("putObject"))("puts an object", async () => {
|
||||||
objects = (await adapter.listObjects()).length;
|
objects = (await adapter.listObjects()).length;
|
||||||
expect(await adapter.putObject(filename, file)).toBeString();
|
expect(await adapter.putObject(filename, file as any)).toBeString();
|
||||||
});
|
});
|
||||||
|
|
||||||
test.skipIf(disabled("listObjects"))("lists objects", async () => {
|
test.skipIf(disabled("listObjects"))("lists objects", async () => {
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ describe("AppAuth", () => {
|
|||||||
const authField = make(name, _authFieldProto as any);
|
const authField = make(name, _authFieldProto as any);
|
||||||
const field = users.field(name)!;
|
const field = users.field(name)!;
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
expect(field.config[prop]).toBe(authField.config[prop]);
|
expect(field.config[prop]).toEqual(authField.config[prop]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
//import type { BkndConfig } from "./src";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
app: {
|
|
||||||
connection: {
|
|
||||||
type: "libsql",
|
|
||||||
config: {
|
|
||||||
//url: "http://localhost:8080"
|
|
||||||
url: ":memory:"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
import { $, type Subprocess } from "bun";
|
|
||||||
import * as esbuild from "esbuild";
|
|
||||||
import postcss from "esbuild-postcss";
|
|
||||||
import { entryOutputMeta } from "./internal/esbuild.entry-output-meta.plugin";
|
|
||||||
import { guessMimeType } from "./src/media/storage/mime-types";
|
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
const watch = args.includes("--watch");
|
|
||||||
const minify = args.includes("--minify");
|
|
||||||
const types = args.includes("--types");
|
|
||||||
const sourcemap = args.includes("--sourcemap");
|
|
||||||
|
|
||||||
type BuildOptions = esbuild.BuildOptions & { name: string };
|
|
||||||
|
|
||||||
const baseOptions: Partial<Omit<esbuild.BuildOptions, "plugins">> & { plugins?: any[] } = {
|
|
||||||
minify,
|
|
||||||
sourcemap,
|
|
||||||
metafile: true,
|
|
||||||
format: "esm",
|
|
||||||
drop: ["console", "debugger"],
|
|
||||||
loader: {
|
|
||||||
".svg": "dataurl",
|
|
||||||
},
|
|
||||||
define: {
|
|
||||||
__isDev: "0",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
type BuildFn = (format?: "esm" | "cjs") => BuildOptions;
|
|
||||||
|
|
||||||
// build BE
|
|
||||||
const builds: Record<string, BuildFn> = {
|
|
||||||
backend: (format = "esm") => ({
|
|
||||||
...baseOptions,
|
|
||||||
name: `backend ${format}`,
|
|
||||||
entryPoints: [
|
|
||||||
"src/index.ts",
|
|
||||||
"src/data/index.ts",
|
|
||||||
"src/core/index.ts",
|
|
||||||
"src/core/utils/index.ts",
|
|
||||||
"src/ui/index.ts",
|
|
||||||
"src/ui/main.css",
|
|
||||||
],
|
|
||||||
outdir: "dist",
|
|
||||||
outExtension: { ".js": format === "esm" ? ".js" : ".cjs" },
|
|
||||||
platform: "browser",
|
|
||||||
splitting: false,
|
|
||||||
bundle: true,
|
|
||||||
plugins: [postcss()],
|
|
||||||
//target: "es2022",
|
|
||||||
format,
|
|
||||||
}),
|
|
||||||
/*components: (format = "esm") => ({
|
|
||||||
...baseOptions,
|
|
||||||
name: `components ${format}`,
|
|
||||||
entryPoints: ["src/ui/index.ts", "src/ui/main.css"],
|
|
||||||
outdir: "dist/ui",
|
|
||||||
outExtension: { ".js": format === "esm" ? ".js" : ".cjs" },
|
|
||||||
format,
|
|
||||||
platform: "browser",
|
|
||||||
splitting: false,
|
|
||||||
//target: "es2022",
|
|
||||||
bundle: true,
|
|
||||||
//external: ["react", "react-dom", "@tanstack/react-query-devtools"],
|
|
||||||
plugins: [postcss()],
|
|
||||||
loader: {
|
|
||||||
".svg": "dataurl",
|
|
||||||
".js": "jsx"
|
|
||||||
}
|
|
||||||
}),*/
|
|
||||||
static: (format = "esm") => ({
|
|
||||||
...baseOptions,
|
|
||||||
name: `static ${format}`,
|
|
||||||
entryPoints: ["src/ui/main.tsx", "src/ui/main.css"],
|
|
||||||
entryNames: "[dir]/[name]-[hash]",
|
|
||||||
outdir: "dist/static",
|
|
||||||
outExtension: { ".js": format === "esm" ? ".js" : ".cjs" },
|
|
||||||
platform: "browser",
|
|
||||||
bundle: true,
|
|
||||||
splitting: true,
|
|
||||||
inject: ["src/ui/inject.js"],
|
|
||||||
target: "es2022",
|
|
||||||
format,
|
|
||||||
loader: {
|
|
||||||
".svg": "dataurl",
|
|
||||||
".js": "jsx",
|
|
||||||
},
|
|
||||||
define: {
|
|
||||||
__isDev: "0",
|
|
||||||
"process.env.NODE_ENV": '"production"',
|
|
||||||
},
|
|
||||||
chunkNames: "chunks/[name]-[hash]",
|
|
||||||
plugins: [
|
|
||||||
postcss(),
|
|
||||||
entryOutputMeta(async (info) => {
|
|
||||||
const manifest: Record<string, object> = {};
|
|
||||||
const toAsset = (output: string) => {
|
|
||||||
const name = output.split("/").pop()!;
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
path: output,
|
|
||||||
mime: guessMimeType(name),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
for (const { output, meta } of info) {
|
|
||||||
manifest[meta.entryPoint as string] = toAsset(output);
|
|
||||||
if (meta.cssBundle) {
|
|
||||||
manifest["src/ui/main.css"] = toAsset(meta.cssBundle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const manifest_file = "dist/static/manifest.json";
|
|
||||||
await Bun.write(manifest_file, JSON.stringify(manifest, null, 2));
|
|
||||||
console.log(`Manifest written to ${manifest_file}`, manifest);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
function adapter(adapter: string, overrides: Partial<esbuild.BuildOptions> = {}): BuildOptions {
|
|
||||||
return {
|
|
||||||
...baseOptions,
|
|
||||||
name: `adapter ${adapter} ${overrides?.format === "cjs" ? "cjs" : "esm"}`,
|
|
||||||
entryPoints: [`src/adapter/${adapter}`],
|
|
||||||
platform: "neutral",
|
|
||||||
outfile: `dist/adapter/${adapter}/index.${overrides?.format === "cjs" ? "cjs" : "js"}`,
|
|
||||||
external: [
|
|
||||||
"cloudflare:workers",
|
|
||||||
"@hono*",
|
|
||||||
"hono*",
|
|
||||||
"bknd*",
|
|
||||||
"*.html",
|
|
||||||
"node*",
|
|
||||||
"react*",
|
|
||||||
"next*",
|
|
||||||
"libsql",
|
|
||||||
"@libsql*",
|
|
||||||
],
|
|
||||||
splitting: false,
|
|
||||||
treeShaking: true,
|
|
||||||
bundle: true,
|
|
||||||
...overrides,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const adapters = [
|
|
||||||
adapter("vite", { platform: "node" }),
|
|
||||||
adapter("cloudflare"),
|
|
||||||
adapter("nextjs", { platform: "node", format: "esm" }),
|
|
||||||
adapter("nextjs", { platform: "node", format: "cjs" }),
|
|
||||||
adapter("remix", { format: "esm" }),
|
|
||||||
adapter("remix", { format: "cjs" }),
|
|
||||||
adapter("bun"),
|
|
||||||
adapter("node", { platform: "node", format: "esm" }),
|
|
||||||
adapter("node", { platform: "node", format: "cjs" }),
|
|
||||||
];
|
|
||||||
|
|
||||||
const collect = [
|
|
||||||
builds.static(),
|
|
||||||
builds.backend(),
|
|
||||||
//builds.components(),
|
|
||||||
builds.backend("cjs"),
|
|
||||||
//builds.components("cjs"),
|
|
||||||
...adapters,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (watch) {
|
|
||||||
const _state: {
|
|
||||||
timeout: Timer | undefined;
|
|
||||||
cleanup: Subprocess | undefined;
|
|
||||||
building: Subprocess | undefined;
|
|
||||||
} = {
|
|
||||||
timeout: undefined,
|
|
||||||
cleanup: undefined,
|
|
||||||
building: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
async function rebuildTypes() {
|
|
||||||
if (!types) return;
|
|
||||||
if (_state.timeout) {
|
|
||||||
clearTimeout(_state.timeout);
|
|
||||||
if (_state.cleanup) _state.cleanup.kill();
|
|
||||||
if (_state.building) _state.building.kill();
|
|
||||||
}
|
|
||||||
_state.timeout = setTimeout(async () => {
|
|
||||||
_state.cleanup = Bun.spawn(["bun", "clean:types"], {
|
|
||||||
onExit: () => {
|
|
||||||
_state.cleanup = undefined;
|
|
||||||
_state.building = Bun.spawn(["bun", "build:types"], {
|
|
||||||
onExit: () => {
|
|
||||||
_state.building = undefined;
|
|
||||||
console.log("Types rebuilt");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const { name, ...build } of collect) {
|
|
||||||
const ctx = await esbuild.context({
|
|
||||||
...build,
|
|
||||||
plugins: [
|
|
||||||
...(build.plugins ?? []),
|
|
||||||
{
|
|
||||||
name: "rebuild-notify",
|
|
||||||
setup(build) {
|
|
||||||
build.onEnd((result) => {
|
|
||||||
console.log(`rebuilt ${name} with ${result.errors.length} errors`);
|
|
||||||
rebuildTypes();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
ctx.watch();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await $`rm -rf dist`;
|
|
||||||
|
|
||||||
async function _build() {
|
|
||||||
let i = 0;
|
|
||||||
const count = collect.length;
|
|
||||||
for await (const { name, ...build } of collect) {
|
|
||||||
await esbuild.build({
|
|
||||||
...build,
|
|
||||||
plugins: [
|
|
||||||
...(build.plugins || []),
|
|
||||||
{
|
|
||||||
name: "progress",
|
|
||||||
setup(build) {
|
|
||||||
i++;
|
|
||||||
build.onEnd((result) => {
|
|
||||||
const errors = result.errors.length;
|
|
||||||
const from = String(i).padStart(String(count).length);
|
|
||||||
console.log(`[${from}/${count}] built ${name} with ${errors} errors`);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("All builds complete");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function _buildtypes() {
|
|
||||||
if (!types) return;
|
|
||||||
Bun.spawn(["bun", "build:types"], {
|
|
||||||
onExit: () => {
|
|
||||||
console.log("Types rebuilt");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([_build(), _buildtypes()]);
|
|
||||||
}
|
|
||||||
12
app/build.ts
12
app/build.ts
@@ -46,10 +46,18 @@ if (types && !watch) {
|
|||||||
buildTypes();
|
buildTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function banner(title: string) {
|
||||||
|
console.log("");
|
||||||
|
console.log("=".repeat(40));
|
||||||
|
console.log(title.toUpperCase());
|
||||||
|
console.log("-".repeat(40));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Building backend and general API
|
* Building backend and general API
|
||||||
*/
|
*/
|
||||||
async function buildApi() {
|
async function buildApi() {
|
||||||
|
banner("Building API");
|
||||||
await tsup.build({
|
await tsup.build({
|
||||||
minify,
|
minify,
|
||||||
sourcemap,
|
sourcemap,
|
||||||
@@ -109,6 +117,7 @@ async function buildUi() {
|
|||||||
},
|
},
|
||||||
} satisfies tsup.Options;
|
} satisfies tsup.Options;
|
||||||
|
|
||||||
|
banner("Building UI");
|
||||||
await tsup.build({
|
await tsup.build({
|
||||||
...base,
|
...base,
|
||||||
entry: ["src/ui/index.ts", "src/ui/main.css", "src/ui/styles.css"],
|
entry: ["src/ui/index.ts", "src/ui/main.css", "src/ui/styles.css"],
|
||||||
@@ -119,6 +128,7 @@ async function buildUi() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
banner("Building Client");
|
||||||
await tsup.build({
|
await tsup.build({
|
||||||
...base,
|
...base,
|
||||||
entry: ["src/ui/client/index.ts"],
|
entry: ["src/ui/client/index.ts"],
|
||||||
@@ -136,6 +146,7 @@ async function buildUi() {
|
|||||||
* - ui/client is external, and after built replaced with "bknd/client"
|
* - ui/client is external, and after built replaced with "bknd/client"
|
||||||
*/
|
*/
|
||||||
async function buildUiElements() {
|
async function buildUiElements() {
|
||||||
|
banner("Building UI Elements");
|
||||||
await tsup.build({
|
await tsup.build({
|
||||||
minify,
|
minify,
|
||||||
sourcemap,
|
sourcemap,
|
||||||
@@ -205,6 +216,7 @@ function baseConfig(adapter: string, overrides: Partial<tsup.Options> = {}): tsu
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function buildAdapters() {
|
async function buildAdapters() {
|
||||||
|
banner("Building Adapters");
|
||||||
// base adapter handles
|
// base adapter handles
|
||||||
await tsup.build({
|
await tsup.build({
|
||||||
...baseConfig(""),
|
...baseConfig(""),
|
||||||
|
|||||||
@@ -32,81 +32,83 @@
|
|||||||
},
|
},
|
||||||
"license": "FSL-1.1-MIT",
|
"license": "FSL-1.1-MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cfworker/json-schema": "^2.0.1",
|
"@cfworker/json-schema": "^4.1.1",
|
||||||
"@codemirror/lang-html": "^6.4.9",
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
"@codemirror/lang-json": "^6.0.1",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@codemirror/lang-liquid": "^6.2.1",
|
"@codemirror/lang-liquid": "^6.2.2",
|
||||||
"@hello-pangea/dnd": "^17.0.0",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@libsql/client": "^0.14.0",
|
"@libsql/client": "^0.14.0",
|
||||||
"@mantine/core": "^7.13.4",
|
"@mantine/core": "^7.17.1",
|
||||||
"@sinclair/typebox": "^0.32.34",
|
"@mantine/hooks": "^7.17.1",
|
||||||
"@tanstack/react-form": "0.19.2",
|
"@sinclair/typebox": "^0.34.30",
|
||||||
"@uiw/react-codemirror": "^4.23.6",
|
"@tanstack/react-form": "^1.0.5",
|
||||||
"@xyflow/react": "^12.3.2",
|
"@uiw/react-codemirror": "^4.23.10",
|
||||||
"aws4fetch": "^1.0.18",
|
"@xyflow/react": "^12.4.4",
|
||||||
|
"aws4fetch": "^1.0.20",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"fast-xml-parser": "^4.4.0",
|
"fast-xml-parser": "^5.0.8",
|
||||||
"hono": "^4.6.12",
|
"hono": "^4.7.4",
|
||||||
"json-schema-form-react": "^0.0.2",
|
"json-schema-form-react": "^0.0.2",
|
||||||
"json-schema-library": "^10.0.0-rc7",
|
"json-schema-library": "^10.0.0-rc7",
|
||||||
"json-schema-to-ts": "^3.1.1",
|
"json-schema-to-ts": "^3.1.1",
|
||||||
"kysely": "^0.27.4",
|
"kysely": "^0.27.6",
|
||||||
"liquidjs": "^10.15.0",
|
"liquidjs": "^10.21.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"oauth4webapi": "^2.11.1",
|
"oauth4webapi": "^2.11.1",
|
||||||
"object-path-immutable": "^4.1.2",
|
"object-path-immutable": "^4.1.2",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"radix-ui": "^1.1.2",
|
"radix-ui": "^1.1.3",
|
||||||
"swr": "^2.2.5"
|
"swr": "^2.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.613.0",
|
"@aws-sdk/client-s3": "^3.758.0",
|
||||||
"@bluwy/giget-core": "^0.1.2",
|
"@bluwy/giget-core": "^0.1.2",
|
||||||
"@dagrejs/dagre": "^1.1.4",
|
"@dagrejs/dagre": "^1.1.4",
|
||||||
"@mantine/modals": "^7.13.4",
|
"@hono/typebox-validator": "^0.3.2",
|
||||||
"@mantine/notifications": "^7.13.4",
|
"@hono/vite-dev-server": "^0.19.0",
|
||||||
"@hono/typebox-validator": "^0.2.6",
|
"@hookform/resolvers": "^4.1.3",
|
||||||
"@hono/vite-dev-server": "^0.17.0",
|
|
||||||
"@hono/zod-validator": "^0.4.1",
|
|
||||||
"@hookform/resolvers": "^3.9.1",
|
|
||||||
"@libsql/kysely-libsql": "^0.4.1",
|
"@libsql/kysely-libsql": "^0.4.1",
|
||||||
|
"@mantine/modals": "^7.17.1",
|
||||||
|
"@mantine/notifications": "^7.17.1",
|
||||||
"@rjsf/core": "5.22.2",
|
"@rjsf/core": "5.22.2",
|
||||||
"@tabler/icons-react": "3.18.0",
|
"@tabler/icons-react": "3.18.0",
|
||||||
"@types/node": "^22.10.0",
|
"@tailwindcss/postcss": "^4.0.12",
|
||||||
"@types/react": "^18.3.12",
|
"@tailwindcss/vite": "^4.0.12",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/node": "^22.13.10",
|
||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@types/react": "^19.0.10",
|
||||||
"autoprefixer": "^10.4.20",
|
"@types/react-dom": "^19.0.4",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"esbuild-postcss": "^0.0.4",
|
"jotai": "^2.12.2",
|
||||||
"jotai": "^2.10.1",
|
|
||||||
"kysely-d1": "^0.3.0",
|
"kysely-d1": "^0.3.0",
|
||||||
"open": "^10.1.0",
|
"open": "^10.1.0",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.5.3",
|
||||||
"postcss-preset-mantine": "^1.17.0",
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"react-hook-form": "^7.53.1",
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.54.2",
|
||||||
"react-icons": "5.2.1",
|
"react-icons": "5.2.1",
|
||||||
"react-json-view-lite": "^2.0.1",
|
"react-json-view-lite": "^2.4.1",
|
||||||
"sql-formatter": "^15.4.9",
|
"sql-formatter": "^15.4.11",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^3.0.2",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^4.0.12",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tsc-alias": "^1.8.10",
|
"tsc-alias": "^1.8.11",
|
||||||
"tsup": "^8.3.5",
|
"tsup": "^8.4.0",
|
||||||
"vite": "^5.4.10",
|
"vite": "^6.2.1",
|
||||||
"vite-plugin-static-copy": "^2.0.0",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"vite-tsconfig-paths": "^5.0.1",
|
"wouter": "^3.6.0"
|
||||||
"wouter": "^3.3.5"
|
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@hono/node-server": "^1.13.7"
|
"@hono/node-server": "^1.13.8"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=18",
|
"react": "^19.x",
|
||||||
"react-dom": ">=18"
|
"react-dom": "^19.x"
|
||||||
},
|
},
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -228,4 +230,4 @@
|
|||||||
"bun",
|
"bun",
|
||||||
"node"
|
"node"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
"postcss-import": {},
|
"@tailwindcss/postcss": {},
|
||||||
"tailwindcss/nesting": {},
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
"postcss-preset-mantine": {},
|
"postcss-preset-mantine": {},
|
||||||
"postcss-simple-vars": {
|
"postcss-simple-vars": {
|
||||||
variables: {
|
variables: {
|
||||||
|
|||||||
@@ -118,3 +118,17 @@ export function patternMatch(target: string, pattern: RegExp | string): boolean
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function slugify(str: string): string {
|
||||||
|
return (
|
||||||
|
String(str)
|
||||||
|
.normalize("NFKD") // split accented characters into their base characters and diacritical marks
|
||||||
|
// biome-ignore lint/suspicious/noMisleadingCharacterClass: <explanation>
|
||||||
|
.replace(/[\u0300-\u036f]/g, "") // remove all the accents, which happen to be all in the \u03xx UNICODE block.
|
||||||
|
.trim() // trim leading or trailing whitespace
|
||||||
|
.toLowerCase() // convert to lowercase
|
||||||
|
.replace(/[^a-z0-9 -]/g, "") // remove non-alphanumeric characters
|
||||||
|
.replace(/\s+/g, "-") // replace spaces with hyphens
|
||||||
|
.replace(/-+/g, "-") // remove consecutive hyphens
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Exception } from "core";
|
import { Exception, isDebug } from "core";
|
||||||
import { type Static, StringEnum, Type } from "core/utils";
|
import { type Static, StringEnum, Type } from "core/utils";
|
||||||
import { cors } from "hono/cors";
|
import { cors } from "hono/cors";
|
||||||
import { Module } from "modules/Module";
|
import { Module } from "modules/Module";
|
||||||
@@ -102,6 +102,12 @@ export class AppServer extends Module<typeof serverConfigSchema> {
|
|||||||
return c.json(err.toJSON(), err.code as any);
|
return c.json(err.toJSON(), err.code as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (err instanceof Error) {
|
||||||
|
if (isDebug()) {
|
||||||
|
return c.json({ error: err.message, stack: err.stack }, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return c.json({ error: err.message }, 500);
|
return c.json({ error: err.message }, 500);
|
||||||
});
|
});
|
||||||
this.setBuilt();
|
this.setBuilt();
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function BkndProvider({
|
|||||||
useState<Pick<BkndContext, "version" | "schema" | "config" | "permissions" | "fallback">>();
|
useState<Pick<BkndContext, "version" | "schema" | "config" | "permissions" | "fallback">>();
|
||||||
const [fetched, setFetched] = useState(false);
|
const [fetched, setFetched] = useState(false);
|
||||||
const [error, setError] = useState<boolean>();
|
const [error, setError] = useState<boolean>();
|
||||||
const errorShown = useRef<boolean>();
|
const errorShown = useRef<boolean>(false);
|
||||||
const fetching = useRef<Fetching>(Fetching.None);
|
const fetching = useRef<Fetching>(Fetching.None);
|
||||||
const [local_version, set_local_version] = useState(0);
|
const [local_version, set_local_version] = useState(0);
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
@@ -101,11 +101,13 @@ export function BkndProvider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
setSchema(newSchema);
|
document.startViewTransition(() => {
|
||||||
setWithSecrets(_includeSecrets);
|
setSchema(newSchema);
|
||||||
setFetched(true);
|
setWithSecrets(_includeSecrets);
|
||||||
set_local_version((v) => v + 1);
|
setFetched(true);
|
||||||
fetching.current = Fetching.None;
|
set_local_version((v) => v + 1);
|
||||||
|
fetching.current = Fetching.None;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ export function useBkndSystemTheme() {
|
|||||||
return {
|
return {
|
||||||
theme: $sys.theme,
|
theme: $sys.theme,
|
||||||
set: $sys.actions.theme.set,
|
set: $sys.actions.theme.set,
|
||||||
toggle: () => $sys.actions.theme.toggle(),
|
toggle: async () => {
|
||||||
|
document.startViewTransition(async () => {
|
||||||
|
await $sys.actions.theme.toggle();
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const styles = {
|
|||||||
outline: "border border-primary/20 bg-transparent hover:bg-primary/5 link text-primary/80",
|
outline: "border border-primary/20 bg-transparent hover:bg-primary/5 link text-primary/80",
|
||||||
red: "dark:bg-red-950 dark:hover:bg-red-900 bg-red-100 hover:bg-red-200 link text-primary/70",
|
red: "dark:bg-red-950 dark:hover:bg-red-900 bg-red-100 hover:bg-red-200 link text-primary/70",
|
||||||
subtlered:
|
subtlered:
|
||||||
"dark:text-red-950 text-red-700 dark:hover:bg-red-900 bg-transparent hover:bg-red-50 link",
|
"dark:text-red-700 text-red-700 dark:hover:bg-red-900 dark:hover:text-red-200 bg-transparent hover:bg-red-50 link",
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BaseProps = {
|
export type BaseProps = {
|
||||||
@@ -51,7 +51,7 @@ const Base = ({
|
|||||||
}: BaseProps) => ({
|
}: BaseProps) => ({
|
||||||
...props,
|
...props,
|
||||||
className: twMerge(
|
className: twMerge(
|
||||||
"flex flex-row flex-nowrap items-center font-semibold disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed transition-[opacity,background-color,color,border-color]",
|
"flex flex-row flex-nowrap items-center !font-semibold disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed transition-[opacity,background-color,color,border-color]",
|
||||||
sizes[size ?? "default"],
|
sizes[size ?? "default"],
|
||||||
styles[variant ?? "default"],
|
styles[variant ?? "default"],
|
||||||
props.className,
|
props.className,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type CanvasProps = ReactFlowProps & {
|
|||||||
externalProvider?: boolean;
|
externalProvider?: boolean;
|
||||||
backgroundStyle?: "lines" | "dots";
|
backgroundStyle?: "lines" | "dots";
|
||||||
minimap?: boolean | MiniMapProps;
|
minimap?: boolean | MiniMapProps;
|
||||||
children?: JSX.Element | ReactNode;
|
children?: Element | ReactNode;
|
||||||
onDropNewNode?: (base: any) => any;
|
onDropNewNode?: (base: any) => any;
|
||||||
onDropNewEdge?: (base: any) => any;
|
onDropNewEdge?: (base: any) => any;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { default as CodeMirror, type ReactCodeMirrorProps } from "@uiw/react-codemirror";
|
import { default as CodeMirror, type ReactCodeMirrorProps } from "@uiw/react-codemirror";
|
||||||
import { useBknd } from "ui/client/bknd";
|
|
||||||
|
|
||||||
import { json } from "@codemirror/lang-json";
|
import { json } from "@codemirror/lang-json";
|
||||||
import { type LiquidCompletionConfig, liquid } from "@codemirror/lang-liquid";
|
import { type LiquidCompletionConfig, liquid } from "@codemirror/lang-liquid";
|
||||||
|
import { useTheme } from "ui/client/use-theme";
|
||||||
|
|
||||||
export type CodeEditorProps = ReactCodeMirrorProps & {
|
export type CodeEditorProps = ReactCodeMirrorProps & {
|
||||||
_extensions?: Partial<{
|
_extensions?: Partial<{
|
||||||
@@ -17,8 +16,7 @@ export default function CodeEditor({
|
|||||||
_extensions = {},
|
_extensions = {},
|
||||||
...props
|
...props
|
||||||
}: CodeEditorProps) {
|
}: CodeEditorProps) {
|
||||||
const b = useBknd();
|
const { theme } = useTheme();
|
||||||
const theme = b.app.getAdminConfig().color_scheme;
|
|
||||||
const _basicSetup: Partial<ReactCodeMirrorProps["basicSetup"]> = !editable
|
const _basicSetup: Partial<ReactCodeMirrorProps["basicSetup"]> = !editable
|
||||||
? {
|
? {
|
||||||
...(typeof basicSetup === "object" ? basicSetup : {}),
|
...(typeof basicSetup === "object" ? basicSetup : {}),
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export default function ArrayFieldTemplate<
|
|||||||
{items.map(
|
{items.map(
|
||||||
({ key, children, ...itemProps }: ArrayFieldTemplateItemType<T, S, F>) => {
|
({ key, children, ...itemProps }: ArrayFieldTemplateItemType<T, S, F>) => {
|
||||||
const newChildren = cloneElement(children, {
|
const newChildren = cloneElement(children, {
|
||||||
|
// @ts-ignore
|
||||||
...children.props,
|
...children.props,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
title: undefined,
|
title: undefined,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
type ComponentPropsWithoutRef,
|
type ComponentPropsWithoutRef,
|
||||||
Fragment,
|
Fragment,
|
||||||
type ReactElement,
|
type ReactElement,
|
||||||
|
type ReactNode,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
@@ -11,7 +12,7 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import { useEvent } from "ui/hooks/use-event";
|
import { useEvent } from "ui/hooks/use-event";
|
||||||
|
|
||||||
export type DropdownItem =
|
export type DropdownItem =
|
||||||
| (() => JSX.Element)
|
| (() => ReactNode)
|
||||||
| {
|
| {
|
||||||
label: string | ReactElement;
|
label: string | ReactElement;
|
||||||
icon?: any;
|
icon?: any;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { DB } from "core";
|
|||||||
import {
|
import {
|
||||||
type ComponentPropsWithRef,
|
type ComponentPropsWithRef,
|
||||||
type ComponentPropsWithoutRef,
|
type ComponentPropsWithoutRef,
|
||||||
|
type ReactNode,
|
||||||
type RefObject,
|
type RefObject,
|
||||||
memo,
|
memo,
|
||||||
useEffect,
|
useEffect,
|
||||||
@@ -27,7 +28,7 @@ export type FileState = {
|
|||||||
export type FileStateWithData = FileState & { data: DB["media"] };
|
export type FileStateWithData = FileState & { data: DB["media"] };
|
||||||
|
|
||||||
export type DropzoneRenderProps = {
|
export type DropzoneRenderProps = {
|
||||||
wrapperRef: RefObject<HTMLDivElement>;
|
wrapperRef: RefObject<HTMLDivElement | null>;
|
||||||
inputProps: ComponentPropsWithRef<"input">;
|
inputProps: ComponentPropsWithRef<"input">;
|
||||||
state: {
|
state: {
|
||||||
files: FileState[];
|
files: FileState[];
|
||||||
@@ -59,7 +60,7 @@ export type DropzoneProps = {
|
|||||||
show?: boolean;
|
show?: boolean;
|
||||||
text?: string;
|
text?: string;
|
||||||
};
|
};
|
||||||
children?: (props: DropzoneRenderProps) => JSX.Element;
|
children?: (props: DropzoneRenderProps) => ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleUploadError(e: unknown) {
|
function handleUploadError(e: unknown) {
|
||||||
@@ -459,7 +460,7 @@ const UploadPlaceholder = ({ onClick, text = "Upload files" }) => {
|
|||||||
|
|
||||||
export type PreviewComponentProps = {
|
export type PreviewComponentProps = {
|
||||||
file: FileState;
|
file: FileState;
|
||||||
fallback?: (props: { file: FileState }) => JSX.Element;
|
fallback?: (props: { file: FileState }) => ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
onTouchStart?: () => void;
|
onTouchStart?: () => void;
|
||||||
@@ -486,7 +487,7 @@ type PreviewProps = {
|
|||||||
handleUpload: (file: FileState) => Promise<void>;
|
handleUpload: (file: FileState) => Promise<void>;
|
||||||
handleDelete: (file: FileState) => Promise<void>;
|
handleDelete: (file: FileState) => Promise<void>;
|
||||||
};
|
};
|
||||||
const Preview: React.FC<PreviewProps> = ({ file, handleUpload, handleDelete }) => {
|
const Preview = ({ file, handleUpload, handleDelete }: PreviewProps) => {
|
||||||
const dropdownItems = [
|
const dropdownItems = [
|
||||||
["initial", "uploaded"].includes(file.state) && {
|
["initial", "uploaded"].includes(file.state) && {
|
||||||
label: "Delete",
|
label: "Delete",
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const NavLink = <E extends React.ElementType = "a">({
|
|||||||
<Tag
|
<Tag
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"px-6 py-2 [&.active]:bg-muted [&.active]:hover:bg-primary/15 hover:bg-primary/5 flex flex-row items-center rounded-full gap-2.5 link",
|
"px-6 py-2 [&.active]:bg-muted [&.active]:hover:bg-primary/15 hover:bg-primary/5 flex flex-row items-center rounded-full gap-2.5 link transition-colors",
|
||||||
disabled && "opacity-50 cursor-not-allowed",
|
disabled && "opacity-50 cursor-not-allowed",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -80,7 +80,7 @@ export function Main({ children }) {
|
|||||||
data-shell="main"
|
data-shell="main"
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex flex-col flex-grow w-1 flex-shrink-1",
|
"flex flex-col flex-grow w-1 flex-shrink-1",
|
||||||
sidebar.open && "max-w-[calc(100%-350px)]",
|
sidebar.open && "md:max-w-[calc(100%-350px)]",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ucFirstAllSnakeToPascalWithSpaces } from "core/utils";
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { TbArrowLeft, TbDots } from "react-icons/tb";
|
import { TbArrowLeft, TbDots } from "react-icons/tb";
|
||||||
import { Link, useLocation } from "wouter";
|
import { Link, useLocation } from "wouter";
|
||||||
@@ -7,7 +6,7 @@ import { Dropdown } from "../../components/overlay/Dropdown";
|
|||||||
import { useEvent } from "../../hooks/use-event";
|
import { useEvent } from "../../hooks/use-event";
|
||||||
|
|
||||||
type Breadcrumb = {
|
type Breadcrumb = {
|
||||||
label: string | JSX.Element;
|
label: string | Element;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
href?: string;
|
href?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -107,8 +107,11 @@ export function createMantineTheme(scheme: "light" | "dark"): {
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Tabs: Tabs.extend({
|
Tabs: Tabs.extend({
|
||||||
classNames: (theme, props) => ({
|
vars: (theme, props) => ({
|
||||||
tab: "data-[active=true]:border-primary",
|
// https://mantine.dev/styles/styles-api/
|
||||||
|
root: {
|
||||||
|
"--tabs-color": "border-primary",
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Menu: Menu.extend({
|
Menu: Menu.extend({
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
@tailwind base;
|
@import "tailwindcss";
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
#bknd-admin.dark,
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
.dark .bknd-admin,
|
|
||||||
.bknd-admin.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 */
|
|
||||||
}
|
|
||||||
|
|
||||||
#bknd-admin,
|
#bknd-admin,
|
||||||
.bknd-admin {
|
.bknd-admin {
|
||||||
--color-primary: 9 9 11; /* zinc-950 */
|
--color-primary: rgb(9 9 11); /* zinc-950 */
|
||||||
--color-background: 250 250 250; /* zinc-50 */
|
--color-background: rgb(250 250 250); /* zinc-50 */
|
||||||
--color-muted: 228 228 231; /* ? */
|
--color-muted: rgb(228 228 231); /* ? */
|
||||||
--color-darkest: 0 0 0; /* black */
|
--color-darkest: rgb(0 0 0); /* black */
|
||||||
--color-lightest: 255 255 255; /* white */
|
--color-lightest: rgb(255 255 255); /* white */
|
||||||
|
|
||||||
@mixin light {
|
@mixin light {
|
||||||
--mantine-color-body: rgb(250 250 250);
|
--mantine-color-body: rgb(250 250 250);
|
||||||
@@ -32,6 +22,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark,
|
||||||
|
#bknd-admin.dark,
|
||||||
|
.bknd-admin.dark {
|
||||||
|
--color-primary: rgb(250 250 250); /* zinc-50 */
|
||||||
|
--color-background: rgb(30 31 34);
|
||||||
|
--color-muted: rgb(47 47 52);
|
||||||
|
--color-darkest: rgb(255 255 255); /* white */
|
||||||
|
--color-lightest: rgb(24 24 27); /* black */
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-primary: var(--color-primary);
|
||||||
|
--color-background: var(--color-background);
|
||||||
|
--color-muted: var(--color-muted);
|
||||||
|
--color-darkest: var(--color-darkest);
|
||||||
|
--color-lightest: var(--color-lightest);
|
||||||
|
}
|
||||||
|
|
||||||
#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;
|
||||||
|
|
||||||
@@ -51,37 +59,12 @@ body,
|
|||||||
@apply flex flex-1 flex-col h-dvh w-dvw;
|
@apply flex flex-1 flex-col h-dvh w-dvw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
.link {
|
||||||
.link {
|
@apply 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
|
|
||||||
*/
|
|
||||||
.bordered-red {
|
|
||||||
@apply border-2 border-red-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bordered-green {
|
|
||||||
@apply border-2 border-green-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bordered-blue {
|
|
||||||
@apply border-2 border-blue-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bordered-violet {
|
|
||||||
@apply border-2 border-violet-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bordered-yellow {
|
|
||||||
@apply border-2 border-yellow-500;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#bknd-admin,
|
#bknd-admin,
|
||||||
|
|||||||
@@ -4,11 +4,19 @@ import Admin from "./Admin";
|
|||||||
import "./main.css";
|
import "./main.css";
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
function render() {
|
||||||
<React.StrictMode>
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<Admin withProvider />
|
<React.StrictMode>
|
||||||
</React.StrictMode>,
|
<Admin withProvider />
|
||||||
);
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("startViewTransition" in document) {
|
||||||
|
document.startViewTransition(render);
|
||||||
|
} else {
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
// REGISTER ERROR OVERLAY
|
// REGISTER ERROR OVERLAY
|
||||||
const showOverlay = true;
|
const showOverlay = true;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { FieldApi, FormApi } from "@tanstack/react-form";
|
import type { FieldApi, ReactFormExtendedApi } from "@tanstack/react-form";
|
||||||
|
import type { JSX } from "react";
|
||||||
import {
|
import {
|
||||||
type Entity,
|
type Entity,
|
||||||
type EntityData,
|
type EntityData,
|
||||||
@@ -8,6 +9,7 @@ import {
|
|||||||
JsonSchemaField,
|
JsonSchemaField,
|
||||||
RelationField,
|
RelationField,
|
||||||
} from "data";
|
} from "data";
|
||||||
|
import { useStore } from "@tanstack/react-store";
|
||||||
import { MediaField } from "media/MediaField";
|
import { MediaField } from "media/MediaField";
|
||||||
import { type ComponentProps, Suspense } from "react";
|
import { type ComponentProps, Suspense } from "react";
|
||||||
import { JsonEditor } from "ui/components/code/JsonEditor";
|
import { JsonEditor } from "ui/components/code/JsonEditor";
|
||||||
@@ -20,13 +22,18 @@ import { EntityRelationalFormField } from "./fields/EntityRelationalFormField";
|
|||||||
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
||||||
import { Alert } from "ui/components/display/Alert";
|
import { Alert } from "ui/components/display/Alert";
|
||||||
|
|
||||||
|
// simplify react form types 🤦
|
||||||
|
export type FormApi = ReactFormExtendedApi<any, any, any, any, any, any, any, any, any, any>;
|
||||||
|
// biome-ignore format: ...
|
||||||
|
export type TFieldApi = FieldApi<any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any>;
|
||||||
|
|
||||||
type EntityFormProps = {
|
type EntityFormProps = {
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
entityId?: number;
|
entityId?: number;
|
||||||
data?: EntityData;
|
data?: EntityData;
|
||||||
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
||||||
fieldsDisabled: boolean;
|
fieldsDisabled: boolean;
|
||||||
Form: FormApi<any>;
|
Form: FormApi;
|
||||||
className?: string;
|
className?: string;
|
||||||
action: "create" | "update";
|
action: "create" | "update";
|
||||||
};
|
};
|
||||||
@@ -42,7 +49,6 @@ export function EntityForm({
|
|||||||
action,
|
action,
|
||||||
}: EntityFormProps) {
|
}: EntityFormProps) {
|
||||||
const fields = entity.getFillableFields(action, true);
|
const fields = entity.getFillableFields(action, true);
|
||||||
console.log("data", { data, fields });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
@@ -132,7 +138,7 @@ type EntityFormFieldProps<
|
|||||||
T extends keyof JSX.IntrinsicElements = "input",
|
T extends keyof JSX.IntrinsicElements = "input",
|
||||||
F extends Field = Field,
|
F extends Field = Field,
|
||||||
> = ComponentProps<T> & {
|
> = ComponentProps<T> & {
|
||||||
fieldApi: FieldApi<any, any>;
|
fieldApi: TFieldApi;
|
||||||
field: F;
|
field: F;
|
||||||
action: "create" | "update";
|
action: "create" | "update";
|
||||||
data?: EntityData;
|
data?: EntityData;
|
||||||
@@ -215,7 +221,7 @@ function EntityMediaFormField({
|
|||||||
entityId,
|
entityId,
|
||||||
disabled,
|
disabled,
|
||||||
}: {
|
}: {
|
||||||
formApi: FormApi<any>;
|
formApi: FormApi;
|
||||||
field: MediaField;
|
field: MediaField;
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
entityId?: number;
|
entityId?: number;
|
||||||
@@ -223,7 +229,7 @@ function EntityMediaFormField({
|
|||||||
}) {
|
}) {
|
||||||
if (!entityId) return;
|
if (!entityId) return;
|
||||||
|
|
||||||
const value = formApi.useStore((state) => {
|
const value = useStore(formApi.store, (state) => {
|
||||||
const val = state.values[field.name];
|
const val = state.values[field.name];
|
||||||
if (!val || typeof val === "undefined") return [];
|
if (!val || typeof val === "undefined") return [];
|
||||||
if (Array.isArray(val)) return val;
|
if (Array.isArray(val)) return val;
|
||||||
@@ -253,7 +259,7 @@ function EntityJsonFormField({
|
|||||||
fieldApi,
|
fieldApi,
|
||||||
field,
|
field,
|
||||||
...props
|
...props
|
||||||
}: { fieldApi: FieldApi<any, any>; field: JsonField }) {
|
}: { fieldApi: TFieldApi; field: JsonField }) {
|
||||||
const handleUpdate = useEvent((value: any) => {
|
const handleUpdate = useEvent((value: any) => {
|
||||||
fieldApi.handleChange(value);
|
fieldApi.handleChange(value);
|
||||||
});
|
});
|
||||||
@@ -289,7 +295,7 @@ function EntityEnumFormField({
|
|||||||
fieldApi,
|
fieldApi,
|
||||||
field,
|
field,
|
||||||
...props
|
...props
|
||||||
}: { fieldApi: FieldApi<any, any>; field: EnumField }) {
|
}: { fieldApi: TFieldApi; field: EnumField }) {
|
||||||
const handleUpdate = useEvent((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleUpdate = useEvent((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
fieldApi.handleChange(e.target.value);
|
fieldApi.handleChange(e.target.value);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { FieldApi } from "@tanstack/react-form";
|
|
||||||
import type { EntityData, JsonSchemaField } from "data";
|
import type { EntityData, JsonSchemaField } from "data";
|
||||||
import * as Formy from "ui/components/form/Formy";
|
import * as Formy from "ui/components/form/Formy";
|
||||||
import { FieldLabel } from "ui/components/form/Formy";
|
import { FieldLabel } from "ui/components/form/Formy";
|
||||||
import { JsonSchemaForm } from "ui/components/form/json-schema";
|
import { JsonSchemaForm } from "ui/components/form/json-schema";
|
||||||
|
import type { TFieldApi } from "ui/modules/data/components/EntityForm";
|
||||||
|
|
||||||
export function EntityJsonSchemaFormField({
|
export function EntityJsonSchemaFormField({
|
||||||
fieldApi,
|
fieldApi,
|
||||||
@@ -11,7 +11,7 @@ export function EntityJsonSchemaFormField({
|
|||||||
disabled,
|
disabled,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
fieldApi: FieldApi<any, any>;
|
fieldApi: TFieldApi;
|
||||||
field: JsonSchemaField;
|
field: JsonSchemaField;
|
||||||
data?: EntityData;
|
data?: EntityData;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { getHotkeyHandler, useHotkeys } from "@mantine/hooks";
|
import { getHotkeyHandler, useHotkeys } from "@mantine/hooks";
|
||||||
import type { FieldApi } from "@tanstack/react-form";
|
|
||||||
import { ucFirst } from "core/utils";
|
import { ucFirst } from "core/utils";
|
||||||
import type { EntityData, RelationField } from "data";
|
import type { EntityData, RelationField } from "data";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
@@ -12,10 +11,11 @@ import { Popover } from "ui/components/overlay/Popover";
|
|||||||
import { Link } from "ui/components/wouter/Link";
|
import { Link } from "ui/components/wouter/Link";
|
||||||
import { routes } from "ui/lib/routes";
|
import { routes } from "ui/lib/routes";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
import { EntityTable, type EntityTableProps } from "../EntityTable";
|
import type { EntityTableProps } from "../EntityTable";
|
||||||
import type { ResponseObject } from "modules/ModuleApi";
|
import type { ResponseObject } from "modules/ModuleApi";
|
||||||
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
||||||
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
||||||
|
import type { TFieldApi } from "ui/modules/data/components/EntityForm";
|
||||||
|
|
||||||
// @todo: allow clear if not required
|
// @todo: allow clear if not required
|
||||||
export function EntityRelationalFormField({
|
export function EntityRelationalFormField({
|
||||||
@@ -25,7 +25,7 @@ export function EntityRelationalFormField({
|
|||||||
disabled,
|
disabled,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
}: {
|
}: {
|
||||||
fieldApi: FieldApi<any, any>;
|
fieldApi: TFieldApi;
|
||||||
field: RelationField;
|
field: RelationField;
|
||||||
data?: EntityData;
|
data?: EntityData;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { IconType } from "react-icons";
|
import type { IconType } from "react-icons";
|
||||||
import { TemplateMediaComponent, TemplateMediaMeta } from "./media";
|
import { TemplateMediaComponent, TemplateMediaMeta } from "./media";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
export type StepTemplate = {
|
export type StepTemplate = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -8,8 +9,6 @@ export type StepTemplate = {
|
|||||||
Icon: IconType;
|
Icon: IconType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Templates: [() => JSX.Element, StepTemplate][] = [
|
const Templates: [() => ReactNode, StepTemplate][] = [[TemplateMediaComponent, TemplateMediaMeta]];
|
||||||
[TemplateMediaComponent, TemplateMediaMeta],
|
|
||||||
];
|
|
||||||
|
|
||||||
export default Templates;
|
export default Templates;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ucFirst } from "core/utils";
|
import { ucFirst } from "core/utils";
|
||||||
import type { Entity, EntityData, EntityRelation, RepoQuery } from "data";
|
import type { Entity, EntityData, EntityRelation } from "data";
|
||||||
import { Fragment, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
import { TbDots } from "react-icons/tb";
|
import { TbDots } from "react-icons/tb";
|
||||||
import { useApiQuery, useEntityQuery } from "ui/client";
|
import { useApiQuery, useEntityQuery } from "ui/client";
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function StepCreate() {
|
|||||||
name: "",
|
name: "",
|
||||||
trigger: "manual",
|
trigger: "manual",
|
||||||
mode: "async",
|
mode: "async",
|
||||||
},
|
} as Static<typeof schema>,
|
||||||
mode: "onSubmit",
|
mode: "onSubmit",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import ModalTest from "../../routes/test/tests/modal-test";
|
|||||||
import QueryJsonFormTest from "../../routes/test/tests/query-jsonform";
|
import QueryJsonFormTest from "../../routes/test/tests/query-jsonform";
|
||||||
import DropdownTest from "./tests/dropdown-test";
|
import DropdownTest from "./tests/dropdown-test";
|
||||||
import DropzoneElementTest from "./tests/dropzone-element-test";
|
import DropzoneElementTest from "./tests/dropzone-element-test";
|
||||||
import EntityFieldsForm from "./tests/entity-fields-form";
|
|
||||||
import FlowsTest from "./tests/flows-test";
|
import FlowsTest from "./tests/flows-test";
|
||||||
import JsonSchemaForm3 from "./tests/json-schema-form3";
|
import JsonSchemaForm3 from "./tests/json-schema-form3";
|
||||||
import JsonFormTest from "./tests/jsonform-test";
|
import JsonFormTest from "./tests/jsonform-test";
|
||||||
@@ -27,9 +26,11 @@ import ReactFlowTest from "./tests/reactflow-test";
|
|||||||
import SchemaTest from "./tests/schema-test";
|
import SchemaTest from "./tests/schema-test";
|
||||||
import SortableTest from "./tests/sortable-test";
|
import SortableTest from "./tests/sortable-test";
|
||||||
import { SqlAiTest } from "./tests/sql-ai-test";
|
import { SqlAiTest } from "./tests/sql-ai-test";
|
||||||
|
import Themes from "./tests/themes";
|
||||||
|
|
||||||
const tests = {
|
const tests = {
|
||||||
DropdownTest,
|
DropdownTest,
|
||||||
|
Themes,
|
||||||
ModalTest,
|
ModalTest,
|
||||||
JsonFormTest,
|
JsonFormTest,
|
||||||
FlowFormTest,
|
FlowFormTest,
|
||||||
@@ -42,7 +43,6 @@ const tests = {
|
|||||||
SqlAiTest,
|
SqlAiTest,
|
||||||
SortableTest,
|
SortableTest,
|
||||||
ReactHookErrors,
|
ReactHookErrors,
|
||||||
EntityFieldsForm,
|
|
||||||
FlowsTest,
|
FlowsTest,
|
||||||
AppShellAccordionsTest,
|
AppShellAccordionsTest,
|
||||||
SwaggerTest,
|
SwaggerTest,
|
||||||
|
|||||||
@@ -1,296 +0,0 @@
|
|||||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
|
||||||
import { Select, Switch, Tabs, TextInput, Textarea, Tooltip } from "@mantine/core";
|
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
|
||||||
import { Type } from "@sinclair/typebox";
|
|
||||||
import { StringEnum, StringIdentifier, transformObject } from "core/utils";
|
|
||||||
import { FieldClassMap } from "data";
|
|
||||||
import { omit } from "lodash-es";
|
|
||||||
import {
|
|
||||||
type FieldArrayWithId,
|
|
||||||
type FieldValues,
|
|
||||||
type UseControllerProps,
|
|
||||||
type UseFormReturn,
|
|
||||||
useController,
|
|
||||||
useFieldArray,
|
|
||||||
useForm,
|
|
||||||
} from "react-hook-form";
|
|
||||||
import { TbChevronDown, TbChevronUp, TbGripVertical, TbTrash } from "react-icons/tb";
|
|
||||||
import { Button } from "../../../components/buttons/Button";
|
|
||||||
import { IconButton } from "../../../components/buttons/IconButton";
|
|
||||||
import { MantineSelect } from "../../../components/form/hook-form-mantine/MantineSelect";
|
|
||||||
|
|
||||||
const fieldSchemas = transformObject(omit(FieldClassMap, ["primary"]), (value) => value.schema);
|
|
||||||
const fieldSchema = Type.Union(
|
|
||||||
Object.entries(fieldSchemas).map(([type, schema]) =>
|
|
||||||
Type.Object(
|
|
||||||
{
|
|
||||||
type: Type.Const(type),
|
|
||||||
name: StringIdentifier,
|
|
||||||
config: Type.Optional(schema),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const schema = Type.Object({
|
|
||||||
fields: Type.Array(fieldSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
const fieldSchema2 = Type.Object({
|
|
||||||
type: StringEnum(Object.keys(fieldSchemas)),
|
|
||||||
name: StringIdentifier,
|
|
||||||
});
|
|
||||||
|
|
||||||
function specificFieldSchema(type: keyof typeof fieldSchemas) {
|
|
||||||
return Type.Omit(fieldSchemas[type], [
|
|
||||||
"label",
|
|
||||||
"description",
|
|
||||||
"required",
|
|
||||||
"fillable",
|
|
||||||
"hidden",
|
|
||||||
"virtual",
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function EntityFieldsForm() {
|
|
||||||
const {
|
|
||||||
control,
|
|
||||||
formState: { isValid, errors },
|
|
||||||
getValues,
|
|
||||||
handleSubmit,
|
|
||||||
watch,
|
|
||||||
register,
|
|
||||||
setValue,
|
|
||||||
} = useForm({
|
|
||||||
mode: "onTouched",
|
|
||||||
resolver: typeboxResolver(schema),
|
|
||||||
defaultValues: {
|
|
||||||
fields: [{ type: "text", name: "", config: {} }],
|
|
||||||
sort: { by: "-1", dir: "asc" },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const defaultType = Object.keys(fieldSchemas)[0];
|
|
||||||
const { fields, append, prepend, remove, swap, move, insert, update } = useFieldArray({
|
|
||||||
control, // control props comes from useForm (optional: if you are using FormProvider)
|
|
||||||
name: "fields", // unique name for your Field Array
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleAppend() {
|
|
||||||
append({ type: "text", name: "", config: {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-1 p-8">
|
|
||||||
{/*{fields.map((field, index) => (
|
|
||||||
<EntityField
|
|
||||||
key={field.id}
|
|
||||||
field={field}
|
|
||||||
index={index}
|
|
||||||
form={{ watch, register, setValue, getValues, control }}
|
|
||||||
defaultType={defaultType}
|
|
||||||
remove={remove}
|
|
||||||
/>
|
|
||||||
))}*/}
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<EntityFieldForm key={field.id} value={field} index={index} update={update} />
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Button className="justify-center" onClick={handleAppend}>
|
|
||||||
Add Field
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<pre>{JSON.stringify(watch(), null, 2)}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EntityFieldForm({ update, index, value }) {
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
handleSubmit,
|
|
||||||
control,
|
|
||||||
formState: { errors },
|
|
||||||
} = useForm({
|
|
||||||
mode: "onBlur",
|
|
||||||
resolver: typeboxResolver(
|
|
||||||
Type.Object({
|
|
||||||
type: StringEnum(Object.keys(fieldSchemas)),
|
|
||||||
name: Type.String({ minLength: 1, maxLength: 3 }),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
defaultValues: value,
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleUpdate({ id, ...data }) {
|
|
||||||
console.log("data", data);
|
|
||||||
update(index, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form>
|
|
||||||
<MantineSelect
|
|
||||||
control={control}
|
|
||||||
name="type"
|
|
||||||
data={[...Object.keys(fieldSchemas), "test"]}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Name"
|
|
||||||
placeholder="name"
|
|
||||||
classNames={{ root: "w-full" }}
|
|
||||||
{...register("name")}
|
|
||||||
error={errors.name?.message as any}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EntityFieldController({
|
|
||||||
name,
|
|
||||||
control,
|
|
||||||
defaultValue,
|
|
||||||
rules,
|
|
||||||
shouldUnregister,
|
|
||||||
}: UseControllerProps & {
|
|
||||||
index: number;
|
|
||||||
}) {
|
|
||||||
const {
|
|
||||||
field: { value, onChange: fieldOnChange, ...field },
|
|
||||||
fieldState,
|
|
||||||
} = useController({
|
|
||||||
name,
|
|
||||||
control,
|
|
||||||
defaultValue,
|
|
||||||
rules,
|
|
||||||
shouldUnregister,
|
|
||||||
});
|
|
||||||
|
|
||||||
return <div>field</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function EntityField({
|
|
||||||
field,
|
|
||||||
index,
|
|
||||||
form: { watch, register, setValue, getValues, control },
|
|
||||||
remove,
|
|
||||||
defaultType,
|
|
||||||
}: {
|
|
||||||
field: FieldArrayWithId;
|
|
||||||
index: number;
|
|
||||||
form: Pick<UseFormReturn<any>, "watch" | "register" | "setValue" | "getValues" | "control">;
|
|
||||||
remove: (index: number) => void;
|
|
||||||
defaultType: string;
|
|
||||||
}) {
|
|
||||||
const [opened, handlers] = useDisclosure(false);
|
|
||||||
const prefix = `fields.${index}` as const;
|
|
||||||
const name = watch(`${prefix}.name`);
|
|
||||||
const enabled = name?.length > 0;
|
|
||||||
const type = watch(`${prefix}.type`);
|
|
||||||
//const config = watch(`${prefix}.config`);
|
|
||||||
const selectFieldRegister = register(`${prefix}.type`);
|
|
||||||
//console.log("type", type, specificFieldSchema(type as any));
|
|
||||||
|
|
||||||
function handleDelete(index: number) {
|
|
||||||
return () => {
|
|
||||||
if (name.length === 0) {
|
|
||||||
remove(index);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.confirm(`Sure to delete "${name}"?`) && remove(index);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={field.id} className="flex flex-col border border-muted rounded">
|
|
||||||
<div className="flex flex-row gap-2 px-2 pt-1 pb-2">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<IconButton Icon={TbGripVertical} className="mt-1" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row flex-grow gap-1">
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
label="Type"
|
|
||||||
data={[...Object.keys(fieldSchemas), "test"]}
|
|
||||||
defaultValue={defaultType}
|
|
||||||
onBlur={selectFieldRegister.onBlur}
|
|
||||||
onChange={(value) => {
|
|
||||||
setValue(`${prefix}.type`, value as any);
|
|
||||||
setValue(`${prefix}.config`, {} as any);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<TextInput
|
|
||||||
label="Name"
|
|
||||||
placeholder="name"
|
|
||||||
classNames={{ root: "w-full" }}
|
|
||||||
{...register(`fields.${index}.name`)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-end ">
|
|
||||||
<div className="flex flex-row gap-1">
|
|
||||||
<Tooltip label="Specify a property name to see options." disabled={enabled}>
|
|
||||||
<Button
|
|
||||||
IconRight={opened ? TbChevronUp : TbChevronDown}
|
|
||||||
onClick={handlers.toggle}
|
|
||||||
variant={opened ? "default" : "ghost"}
|
|
||||||
disabled={!enabled}
|
|
||||||
>
|
|
||||||
Options
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
<IconButton Icon={TbTrash} onClick={handleDelete(index)} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/*{enabled && opened && (
|
|
||||||
<div className="flex flex-col border-t border-t-muted px-3 py-2">
|
|
||||||
<Tabs defaultValue="general">
|
|
||||||
<Tabs.List>
|
|
||||||
<Tabs.Tab value="general">General</Tabs.Tab>
|
|
||||||
<Tabs.Tab value="specific">Specific</Tabs.Tab>
|
|
||||||
<Tabs.Tab value="visibility">Visiblity</Tabs.Tab>
|
|
||||||
</Tabs.List>
|
|
||||||
<Tabs.Panel value="general">
|
|
||||||
<div className="flex flex-col gap-2 pt-3 pb-1" key={`${prefix}_${type}`}>
|
|
||||||
<Switch
|
|
||||||
label="Required"
|
|
||||||
{...register(`${prefix}.config.required` as any)}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Label"
|
|
||||||
placeholder="Label"
|
|
||||||
{...register(`${prefix}.config.label` as any)}
|
|
||||||
/>
|
|
||||||
<Textarea
|
|
||||||
label="Description"
|
|
||||||
placeholder="Description"
|
|
||||||
{...register(`${prefix}.config.description` as any)}
|
|
||||||
/>
|
|
||||||
<Switch label="Virtual" {...register(`${prefix}.config.virtual` as any)} />
|
|
||||||
</div>
|
|
||||||
</Tabs.Panel>
|
|
||||||
<Tabs.Panel value="specific">
|
|
||||||
<div className="flex flex-col gap-2 pt-3 pb-1">
|
|
||||||
<JsonSchemaForm
|
|
||||||
key={type}
|
|
||||||
schema={specificFieldSchema(type as any)}
|
|
||||||
uiSchema={dataFieldsUiSchema.config}
|
|
||||||
className="legacy hide-required-mark fieldset-alternative mute-root"
|
|
||||||
onChange={(value) => {
|
|
||||||
setValue(`${prefix}.config`, {
|
|
||||||
...getValues([`fields.${index}.config`])[0],
|
|
||||||
...value
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tabs.Panel>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
)}*/}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
48
app/src/ui/routes/test/tests/themes.tsx
Normal file
48
app/src/ui/routes/test/tests/themes.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Button } from "ui/components/buttons/Button";
|
||||||
|
import { Alert } from "ui/components/display/Alert";
|
||||||
|
import * as Formy from "ui/components/form/Formy";
|
||||||
|
|
||||||
|
export default function Themes() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col p-3 gap-4">
|
||||||
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<Button size="small">Small</Button>
|
||||||
|
<Button>Default</Button>
|
||||||
|
<Button variant="primary">Primary</Button>
|
||||||
|
<Button variant="ghost">Ghost</Button>
|
||||||
|
<Button variant="outline">Outline</Button>
|
||||||
|
<Button variant="red">Red</Button>
|
||||||
|
<Button variant="subtlered">Subtlered</Button>
|
||||||
|
<Button size="large">Large</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-1">
|
||||||
|
<Alert.Exception title="Exception">Alert.Exception</Alert.Exception>
|
||||||
|
<Alert.Info title="Info">Alert.Info</Alert.Info>
|
||||||
|
<Alert.Success title="Success">Alert.Success</Alert.Success>
|
||||||
|
<Alert.Warning title="Warning">Alert.Warning</Alert.Warning>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-3 items-start">
|
||||||
|
<Formy.Group>
|
||||||
|
<Formy.Label>Input</Formy.Label>
|
||||||
|
<Formy.Input placeholder="Input" />
|
||||||
|
</Formy.Group>
|
||||||
|
<Formy.Group>
|
||||||
|
<Formy.Label>Checkbox</Formy.Label>
|
||||||
|
<Formy.BooleanInput />
|
||||||
|
</Formy.Group>
|
||||||
|
<Formy.Group>
|
||||||
|
<Formy.Label>Switch</Formy.Label>
|
||||||
|
<Formy.Switch />
|
||||||
|
</Formy.Group>
|
||||||
|
<Formy.Group>
|
||||||
|
<Formy.Label>Select</Formy.Label>
|
||||||
|
<Formy.Select>
|
||||||
|
<option value="" />
|
||||||
|
<option value="1">Option 1</option>
|
||||||
|
<option value="2">Option 2</option>
|
||||||
|
</Formy.Select>
|
||||||
|
</Formy.Group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -88,7 +88,7 @@ input[disabled]::placeholder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor {
|
.cm-editor {
|
||||||
background: transparent;
|
/*background: transparent;*/
|
||||||
}
|
}
|
||||||
.cm-editor.cm-focused {
|
.cm-editor.cm-focused {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import react from "@vitejs/plugin-react";
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
import tsconfigPaths from "vite-tsconfig-paths";
|
||||||
import { devServerConfig } from "./src/adapter/vite/dev-server-config";
|
import { devServerConfig } from "./src/adapter/vite/dev-server-config";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -25,6 +26,7 @@ export default defineConfig({
|
|||||||
...devServerConfig,
|
...devServerConfig,
|
||||||
entry: "./vite.dev.ts",
|
entry: "./vite.dev.ts",
|
||||||
}),
|
}),
|
||||||
|
tailwindcss(),
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
manifest: true,
|
manifest: true,
|
||||||
|
|||||||
10
biome.json
10
biome.json
@@ -8,8 +8,7 @@
|
|||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"indentStyle": "space",
|
"indentStyle": "space"
|
||||||
"ignore": ["**/package.json"]
|
|
||||||
},
|
},
|
||||||
"javascript": {
|
"javascript": {
|
||||||
"formatter": {
|
"formatter": {
|
||||||
@@ -23,6 +22,13 @@
|
|||||||
"indentWidth": 3
|
"indentWidth": 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"json": {
|
||||||
|
"formatter": {
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 80,
|
||||||
|
"indentStyle": "space"
|
||||||
|
}
|
||||||
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
|
|||||||
Reference in New Issue
Block a user