Compare commits
20 Commits
e3ec0219b7
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| fcd2c0342f | |||
| c89329dada | |||
| e3d96b8818 | |||
| b72df41ec7 | |||
| 694a5295cc | |||
| 39bea5e7a9 | |||
| f8b8ecd88c | |||
| eb64dc8022 | |||
| 455b762eef | |||
| 47625259ba | |||
| 8b977b5e86 | |||
| 132e376b9f | |||
| c06daf4f7c | |||
| d06e585bb5 | |||
| 8af1ee8bd4 | |||
| 6e6ad0de05 | |||
| f51f9c9f81 | |||
| 115eecc6f3 | |||
| 567d7e3e13 | |||
| c6b30100bd |
+15
@@ -0,0 +1,15 @@
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
node_modules
|
||||
/.svelte-kit
|
||||
/build
|
||||
/.output
|
||||
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
@@ -8,34 +8,18 @@ export const nodes = [
|
||||
() => import('./nodes/4'),
|
||||
() => import('./nodes/5'),
|
||||
() => import('./nodes/6'),
|
||||
() => import('./nodes/7'),
|
||||
() => import('./nodes/8'),
|
||||
() => import('./nodes/9'),
|
||||
() => import('./nodes/10'),
|
||||
() => import('./nodes/11'),
|
||||
() => import('./nodes/12'),
|
||||
() => import('./nodes/13'),
|
||||
() => import('./nodes/14'),
|
||||
() => import('./nodes/15')
|
||||
() => import('./nodes/7')
|
||||
];
|
||||
|
||||
export const server_loads = [0];
|
||||
|
||||
export const dictionary = {
|
||||
"/": [~2],
|
||||
"/accounts": [~10],
|
||||
"/accounts/new": [11],
|
||||
"/accounts/[id]": [12],
|
||||
"/items": [~13],
|
||||
"/items/[id]": [~14],
|
||||
"/login": [~15],
|
||||
"/login": [~7],
|
||||
"/logout": [~6],
|
||||
"/receipts": [~3],
|
||||
"/receipts/new": [4],
|
||||
"/receipts/[id]": [5],
|
||||
"/transactions": [~7],
|
||||
"/transactions/new": [8],
|
||||
"/transactions/[id]": [9]
|
||||
"/receipts/[id]": [~5]
|
||||
};
|
||||
|
||||
export const hooks = {
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as component } from "../../../../src/routes/accounts/+page.svelte";
|
||||
export { default as component } from "../../../../src/routes/login/+page.svelte";
|
||||
@@ -1 +1 @@
|
||||
export { default as component } from "../../../../src/routes/accounts/new/+page.svelte";
|
||||
export { default as component } from "../../../../src/routes/login/+page.svelte";
|
||||
@@ -1 +1 @@
|
||||
export { default as component } from "../../../../src/routes/accounts/[id]/+page.svelte";
|
||||
export { default as component } from "../../../../src/routes/login/+page.svelte";
|
||||
@@ -1 +1 @@
|
||||
export { default as component } from "../../../../src/routes/items/+page.svelte";
|
||||
export { default as component } from "../../../../src/routes/login/+page.svelte";
|
||||
@@ -1 +1 @@
|
||||
export { default as component } from "../../../../src/routes/items/[id]/+page.svelte";
|
||||
export { default as component } from "../../../../src/routes/login/+page.svelte";
|
||||
@@ -1 +1 @@
|
||||
export { default as component } from "../../../../src/routes/transactions/+page.svelte";
|
||||
export { default as component } from "../../../../src/routes/login/+page.svelte";
|
||||
@@ -1 +1 @@
|
||||
export { default as component } from "../../../../src/routes/transactions/new/+page.svelte";
|
||||
export { default as component } from "../../../../src/routes/login/+page.svelte";
|
||||
@@ -1 +1 @@
|
||||
export { default as component } from "../../../../src/routes/transactions/[id]/+page.svelte";
|
||||
export { default as component } from "../../../../src/routes/login/+page.svelte";
|
||||
@@ -21,10 +21,10 @@ export const options = {
|
||||
service_worker: false,
|
||||
service_worker_options: undefined,
|
||||
templates: {
|
||||
app: ({ head, body, assets, nonce, env }) => "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"icon\" href=\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='4' fill='%231e293b'/><text x='16' y='22' font-size='18' font-family='system-ui' fill='%2394a3b8' text-anchor='middle'>S</text></svg>\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5, viewport-fit=cover\" />\n <meta name=\"theme-color\" content=\"#0f172a\" />\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\n <meta name=\"apple-mobile-web-app-title\" content=\"Stackq\" />\n <link rel=\"apple-touch-icon\" href=\"/icon.svg\" />\n <link rel=\"manifest\" href=\"/manifest.webmanifest\" />\n " + head + "\n </head>\n <body data-sveltekit-preload-data=\"hover\">\n <div style=\"display: contents\">" + body + "</div>\n </body>\n</html>\n",
|
||||
app: ({ head, body, assets, nonce, env }) => "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"icon\" href=\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='4' fill='%231e293b'/><text x='16' y='22' font-size='18' font-family='system-ui' fill='%2394a3b8' text-anchor='middle'>S</text></svg>\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5, viewport-fit=cover\" />\n <meta name=\"theme-color\" content=\"#0f172a\" />\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\n <meta name=\"apple-mobile-web-app-title\" content=\"Stackq\" />\n <link rel=\"apple-touch-icon\" href=\"/icon.svg\" />\n <link rel=\"manifest\" href=\"/manifest.webmanifest\" />\n " + head + "\n </head>\n <body data-sveltekit-preload-data=\"hover\">\n <div style=\"display: contents\">" + body + "</div>\n </body>\n</html>\n",
|
||||
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
|
||||
},
|
||||
version_hash: "16sw9ck"
|
||||
version_hash: "49o61d"
|
||||
};
|
||||
|
||||
export async function get_hooks() {
|
||||
|
||||
Vendored
+8
-5
@@ -27,20 +27,23 @@ export {};
|
||||
|
||||
declare module "$app/types" {
|
||||
export interface AppTypes {
|
||||
RouteId(): "/" | "/access-denied" | "/accounts" | "/accounts/new" | "/accounts/[id]" | "/api" | "/api/toggle-darkmode" | "/items" | "/items/[id]" | "/login" | "/logout" | "/receipts" | "/receipts/new" | "/receipts/[id]" | "/transactions" | "/transactions/new" | "/transactions/[id]";
|
||||
RouteId(): "/" | "/access-denied" | "/accounts" | "/accounts/new" | "/accounts/[id]" | "/api" | "/api/files" | "/api/files/[...path]" | "/api/toggle-darkmode" | "/items" | "/items/[id]" | "/login" | "/logout" | "/receipts" | "/receipts/new" | "/receipts/[id]" | "/transactions" | "/transactions/new" | "/transactions/[id]";
|
||||
RouteParams(): {
|
||||
"/accounts/[id]": { id: string };
|
||||
"/api/files/[...path]": { path: string };
|
||||
"/items/[id]": { id: string };
|
||||
"/receipts/[id]": { id: string };
|
||||
"/transactions/[id]": { id: string }
|
||||
};
|
||||
LayoutParams(): {
|
||||
"/": { id?: string };
|
||||
"/": { id?: string; path?: string };
|
||||
"/access-denied": Record<string, never>;
|
||||
"/accounts": { id?: string };
|
||||
"/accounts/new": Record<string, never>;
|
||||
"/accounts/[id]": { id: string };
|
||||
"/api": Record<string, never>;
|
||||
"/api": { path?: string };
|
||||
"/api/files": { path?: string };
|
||||
"/api/files/[...path]": { path: string };
|
||||
"/api/toggle-darkmode": Record<string, never>;
|
||||
"/items": { id?: string };
|
||||
"/items/[id]": { id: string };
|
||||
@@ -53,8 +56,8 @@ declare module "$app/types" {
|
||||
"/transactions/new": Record<string, never>;
|
||||
"/transactions/[id]": { id: string }
|
||||
};
|
||||
Pathname(): "/" | "/accounts" | "/accounts/new" | `/accounts/${string}` & {} | "/items" | `/items/${string}` & {} | "/login" | "/logout" | "/receipts" | "/receipts/new" | `/receipts/${string}` & {} | "/transactions" | "/transactions/new" | `/transactions/${string}` & {};
|
||||
Pathname(): "/" | `/api/files/${string}` & {} | "/login" | "/logout" | "/receipts" | "/receipts/new" | `/receipts/${string}` & {};
|
||||
ResolvedPathname(): `${"" | `/${string}`}${ReturnType<AppTypes['Pathname']>}`;
|
||||
Asset(): "/icon.svg" | string & {};
|
||||
Asset(): "/icon.svg" | "/manifest.webmanifest" | string & {};
|
||||
}
|
||||
}
|
||||
@@ -6,28 +6,8 @@
|
||||
"src/routes/+layout.ts",
|
||||
"src/routes/+layout.server.ts"
|
||||
],
|
||||
"/accounts": [
|
||||
"src/routes/accounts/+page.server.ts",
|
||||
"src/routes/+layout.ts",
|
||||
"src/routes/+layout.server.ts"
|
||||
],
|
||||
"/accounts/new": [
|
||||
"src/routes/+layout.ts",
|
||||
"src/routes/+layout.server.ts"
|
||||
],
|
||||
"/accounts/[id]": [
|
||||
"src/routes/+layout.ts",
|
||||
"src/routes/+layout.server.ts"
|
||||
],
|
||||
"/items": [
|
||||
"src/routes/items/+page.server.ts",
|
||||
"src/routes/+layout.ts",
|
||||
"src/routes/+layout.server.ts"
|
||||
],
|
||||
"/items/[id]": [
|
||||
"src/routes/items/[id]/+page.server.ts",
|
||||
"src/routes/+layout.ts",
|
||||
"src/routes/+layout.server.ts"
|
||||
"/api/files/[...path]": [
|
||||
"src/routes/api/files/[...path]/+server.ts"
|
||||
],
|
||||
"/login": [
|
||||
"src/routes/login/+page.server.ts",
|
||||
@@ -49,19 +29,7 @@
|
||||
"src/routes/+layout.server.ts"
|
||||
],
|
||||
"/receipts/[id]": [
|
||||
"src/routes/+layout.ts",
|
||||
"src/routes/+layout.server.ts"
|
||||
],
|
||||
"/transactions": [
|
||||
"src/routes/transactions/+page.server.ts",
|
||||
"src/routes/+layout.ts",
|
||||
"src/routes/+layout.server.ts"
|
||||
],
|
||||
"/transactions/new": [
|
||||
"src/routes/+layout.ts",
|
||||
"src/routes/+layout.server.ts"
|
||||
],
|
||||
"/transactions/[id]": [
|
||||
"src/routes/receipts/[id]/+page.server.ts",
|
||||
"src/routes/+layout.ts",
|
||||
"src/routes/+layout.server.ts"
|
||||
]
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends
|
||||
export type Snapshot<T = any> = Kit.Snapshot<T>;
|
||||
type PageServerParentData = EnsureDefined<LayoutServerData>;
|
||||
type PageParentData = EnsureDefined<LayoutData>;
|
||||
type LayoutRouteId = RouteId | "/" | "/receipts" | "/receipts/new" | "/receipts/[id]" | "/logout" | "/transactions" | "/transactions/new" | "/transactions/[id]" | "/accounts" | "/accounts/new" | "/accounts/[id]" | "/items" | "/items/[id]" | "/login" | null
|
||||
type LayoutRouteId = RouteId | "/" | "/receipts" | "/receipts/new" | "/receipts/[id]" | "/logout" | "/login" | null
|
||||
type LayoutParams = RouteParams & { id?: string }
|
||||
type LayoutServerParentData = EnsureDefined<{}>;
|
||||
type LayoutParentData = EnsureDefined<{}>;
|
||||
|
||||
+3
-16
@@ -11,21 +11,8 @@ type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Pa
|
||||
type EnsureDefined<T> = T extends null | undefined ? {} : T;
|
||||
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
|
||||
export type Snapshot<T = any> = Kit.Snapshot<T>;
|
||||
type PageServerParentData = EnsureDefined<import('../$types.js').LayoutServerData>;
|
||||
type PageParentData = EnsureDefined<import('../$types.js').LayoutData>;
|
||||
|
||||
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
|
||||
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
|
||||
type ExcludeActionFailure<T> = T extends Kit.ActionFailure<any> ? never : T extends void ? never : T;
|
||||
type ActionsSuccess<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: ExcludeActionFailure<Awaited<ReturnType<T[Key]>>>; }[keyof T];
|
||||
type ExtractActionFailure<T> = T extends Kit.ActionFailure<infer X> ? X extends void ? never : X : never;
|
||||
type ActionsFailure<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: Exclude<ExtractActionFailure<Awaited<ReturnType<T[Key]>>>, void>; }[keyof T];
|
||||
type ActionsExport = typeof import('./proxy+page.server.js').actions
|
||||
export type SubmitFunction = Kit.SubmitFunction<Expand<ActionsSuccess<ActionsExport>>, Expand<ActionsFailure<ActionsExport>>>
|
||||
export type ActionData = Expand<Kit.AwaitedActions<ActionsExport>> | null;
|
||||
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
|
||||
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
|
||||
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
|
||||
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
|
||||
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
|
||||
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;
|
||||
export type PageServerData = null;
|
||||
export type PageData = Expand<PageParentData>;
|
||||
export type PageProps = { params: RouteParams; data: PageData }
|
||||
+3
-16
@@ -11,22 +11,9 @@ type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Pa
|
||||
type EnsureDefined<T> = T extends null | undefined ? {} : T;
|
||||
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
|
||||
export type Snapshot<T = any> = Kit.Snapshot<T>;
|
||||
type PageServerParentData = EnsureDefined<import('../../$types.js').LayoutServerData>;
|
||||
type PageParentData = EnsureDefined<import('../../$types.js').LayoutData>;
|
||||
|
||||
export type EntryGenerator = () => Promise<Array<RouteParams>> | Array<RouteParams>;
|
||||
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
|
||||
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
|
||||
type ExcludeActionFailure<T> = T extends Kit.ActionFailure<any> ? never : T extends void ? never : T;
|
||||
type ActionsSuccess<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: ExcludeActionFailure<Awaited<ReturnType<T[Key]>>>; }[keyof T];
|
||||
type ExtractActionFailure<T> = T extends Kit.ActionFailure<infer X> ? X extends void ? never : X : never;
|
||||
type ActionsFailure<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: Exclude<ExtractActionFailure<Awaited<ReturnType<T[Key]>>>, void>; }[keyof T];
|
||||
type ActionsExport = typeof import('./proxy+page.server.js').actions
|
||||
export type SubmitFunction = Kit.SubmitFunction<Expand<ActionsSuccess<ActionsExport>>, Expand<ActionsFailure<ActionsExport>>>
|
||||
export type ActionData = Expand<Kit.AwaitedActions<ActionsExport>> | null;
|
||||
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
|
||||
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
|
||||
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
|
||||
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
|
||||
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
|
||||
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;
|
||||
export type PageServerData = null;
|
||||
export type PageData = Expand<PageParentData>;
|
||||
export type PageProps = { params: RouteParams; data: PageData }
|
||||
@@ -1,91 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { error, fail, redirect } from "@sveltejs/kit";
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
import type { StackqItem } from "$lib/types/items";
|
||||
|
||||
const COLLECTION = "Stackq_Items";
|
||||
|
||||
function parseNum(val: unknown): number | undefined {
|
||||
if (val === "" || val === null || val === undefined) return undefined;
|
||||
const n = Number(val);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
}
|
||||
|
||||
function parseBool(val: unknown): boolean {
|
||||
return val === "on" || val === "true" || val === true;
|
||||
}
|
||||
|
||||
export const load = async ({ params, locals }: Parameters<PageServerLoad>[0]) => {
|
||||
const id = params.id;
|
||||
if (!id) throw error(404, "Item not found");
|
||||
|
||||
try {
|
||||
const item = await locals.pb
|
||||
.collection(COLLECTION)
|
||||
.getOne<StackqItem>(id, { expand: "Parent" });
|
||||
const list = await locals.pb
|
||||
.collection(COLLECTION)
|
||||
.getFullList<StackqItem>({ sort: "-created", expand: "Parent" })
|
||||
.catch(() => [] as StackqItem[]);
|
||||
const itemsForParent = list.map((i) => ({ id: i.id, Item: i.Item, SKU: i.SKU }));
|
||||
return { item, itemsForParent };
|
||||
} catch {
|
||||
throw error(404, "Item not found");
|
||||
}
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
update: async ({ request, locals, params }: import('./$types').RequestEvent) => {
|
||||
const id = params.id;
|
||||
if (!id) return fail(400, { error: "Missing id", form: "update" });
|
||||
const form = await request.formData();
|
||||
const Item = (form.get("Item") as string)?.trim() ?? "";
|
||||
const SKU = (form.get("SKU") as string)?.trim() ?? "";
|
||||
const Description = (form.get("Description") as string)?.trim() ?? "";
|
||||
const UOM = (form.get("UOM") as string)?.trim() ?? "";
|
||||
const Cost = parseNum(form.get("Cost"));
|
||||
const Vendor = (form.get("Vendor") as string)?.trim() ?? "";
|
||||
const Catagory = (form.get("Catagory") as string)?.trim() ?? "";
|
||||
const Sub_Catagory = (form.get("Sub_Catagory") as string)?.trim() ?? "";
|
||||
const Has_Parent = parseBool(form.get("Has_Parent"));
|
||||
const Parent = (form.get("Parent") as string)?.trim() ?? "";
|
||||
const Stock = parseBool(form.get("Stock"));
|
||||
const Image = form.get("Image") as File | null;
|
||||
const imageFile = Image && Image.size > 0 ? Image : undefined;
|
||||
if (!Item) return fail(400, { error: "Item name is required", form: "update" });
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
Item,
|
||||
SKU,
|
||||
Description,
|
||||
UOM: UOM || undefined,
|
||||
Cost: Cost ?? 0,
|
||||
Vendor,
|
||||
Catagory: Catagory || undefined,
|
||||
Sub_Catagory: Sub_Catagory || undefined,
|
||||
Has_Parent: !!Has_Parent,
|
||||
Parent: Parent || null,
|
||||
Stock: !!Stock,
|
||||
};
|
||||
if (imageFile) body.Image = imageFile;
|
||||
await locals.pb.collection(COLLECTION).update(id, body);
|
||||
} catch (e: unknown) {
|
||||
const message = e && typeof e === "object" && "message" in e ? String((e as { message: string }).message) : "Update failed";
|
||||
return fail(500, { error: message, form: "update" });
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
delete: async ({ locals, params }: import('./$types').RequestEvent) => {
|
||||
const id = params.id;
|
||||
if (!id) return fail(400, { error: "Missing id", form: "delete" });
|
||||
try {
|
||||
await locals.pb.collection(COLLECTION).delete(id);
|
||||
} catch (e: unknown) {
|
||||
const message = e && typeof e === "object" && "message" in e ? String((e as { message: string }).message) : "Delete failed";
|
||||
return fail(500, { error: message, form: "delete" });
|
||||
}
|
||||
throw redirect(303, "/items");
|
||||
},
|
||||
};
|
||||
;null as any as Actions;
|
||||
@@ -1,134 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { fail } from "@sveltejs/kit";
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
import type { StackqItem } from "$lib/types/items";
|
||||
|
||||
const COLLECTION = "Stackq_Items";
|
||||
|
||||
function parseNum(val: unknown): number | undefined {
|
||||
if (val === "" || val === null || val === undefined) return undefined;
|
||||
const n = Number(val);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
}
|
||||
|
||||
function parseBool(val: unknown): boolean {
|
||||
return val === "on" || val === "true" || val === true;
|
||||
}
|
||||
|
||||
export const load = async ({ locals }: Parameters<PageServerLoad>[0]) => {
|
||||
const list = await locals.pb
|
||||
.collection(COLLECTION)
|
||||
.getFullList<StackqItem>({
|
||||
sort: "-created",
|
||||
expand: "Parent",
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(COLLECTION, err);
|
||||
return [] as StackqItem[];
|
||||
});
|
||||
|
||||
const itemsForParent = list.map((i) => ({ id: i.id, Item: i.Item, SKU: i.SKU }));
|
||||
|
||||
return { items: list, itemsForParent };
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
create: async ({ request, locals }: import('./$types').RequestEvent) => {
|
||||
const form = await request.formData();
|
||||
const Item = (form.get("Item") as string)?.trim() ?? "";
|
||||
const SKU = (form.get("SKU") as string)?.trim() ?? "";
|
||||
const Description = (form.get("Description") as string)?.trim() ?? "";
|
||||
const UOM = (form.get("UOM") as string)?.trim() ?? "";
|
||||
const Cost = parseNum(form.get("Cost"));
|
||||
const Vendor = (form.get("Vendor") as string)?.trim() ?? "";
|
||||
const Catagory = (form.get("Catagory") as string)?.trim() ?? "";
|
||||
const Sub_Catagory = (form.get("Sub_Catagory") as string)?.trim() ?? "";
|
||||
const Has_Parent = parseBool(form.get("Has_Parent"));
|
||||
const Parent = (form.get("Parent") as string)?.trim() ?? "";
|
||||
const Stock = parseBool(form.get("Stock"));
|
||||
const imageFileCreate = form.get("Image") as File | null;
|
||||
const hasImage = imageFileCreate && imageFileCreate.size > 0;
|
||||
|
||||
if (!Item) return fail(400, { error: "Item name is required", form: "create" });
|
||||
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
Item,
|
||||
SKU,
|
||||
Description,
|
||||
UOM: UOM || undefined,
|
||||
Cost: Cost ?? 0,
|
||||
Vendor,
|
||||
Catagory: Catagory || undefined,
|
||||
Sub_Catagory: Sub_Catagory || undefined,
|
||||
Has_Parent: !!Has_Parent,
|
||||
Parent: Parent || null,
|
||||
Stock: !!Stock,
|
||||
};
|
||||
if (hasImage) body.Image = imageFileCreate;
|
||||
await locals.pb.collection(COLLECTION).create(body);
|
||||
} catch (e: unknown) {
|
||||
const message = e && typeof e === "object" && "message" in e ? String((e as { message: string }).message) : "Create failed";
|
||||
return fail(500, { error: message, form: "create" });
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
update: async ({ request, locals }: import('./$types').RequestEvent) => {
|
||||
const form = await request.formData();
|
||||
const id = (form.get("id") as string)?.trim();
|
||||
if (!id) return fail(400, { error: "Missing id", form: "update" });
|
||||
|
||||
const Item = (form.get("Item") as string)?.trim() ?? "";
|
||||
const SKU = (form.get("SKU") as string)?.trim() ?? "";
|
||||
const Description = (form.get("Description") as string)?.trim() ?? "";
|
||||
const UOM = (form.get("UOM") as string)?.trim() ?? "";
|
||||
const Cost = parseNum(form.get("Cost"));
|
||||
const Vendor = (form.get("Vendor") as string)?.trim() ?? "";
|
||||
const Catagory = (form.get("Catagory") as string)?.trim() ?? "";
|
||||
const Sub_Catagory = (form.get("Sub_Catagory") as string)?.trim() ?? "";
|
||||
const Has_Parent = parseBool(form.get("Has_Parent"));
|
||||
const Parent = (form.get("Parent") as string)?.trim() ?? "";
|
||||
const Stock = parseBool(form.get("Stock"));
|
||||
const Image = form.get("Image") as File | null;
|
||||
const imageFile = Image && Image.size > 0 ? Image : undefined;
|
||||
|
||||
if (!Item) return fail(400, { error: "Item name is required", form: "update" });
|
||||
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
Item,
|
||||
SKU,
|
||||
Description,
|
||||
UOM: UOM || undefined,
|
||||
Cost: Cost ?? 0,
|
||||
Vendor,
|
||||
Catagory: Catagory || undefined,
|
||||
Sub_Catagory: Sub_Catagory || undefined,
|
||||
Has_Parent: !!Has_Parent,
|
||||
Parent: Parent || null,
|
||||
Stock: !!Stock,
|
||||
};
|
||||
if (imageFile) body.Image = imageFile;
|
||||
await locals.pb.collection(COLLECTION).update(id, body);
|
||||
} catch (e: unknown) {
|
||||
const message = e && typeof e === "object" && "message" in e ? String((e as { message: string }).message) : "Update failed";
|
||||
return fail(500, { error: message, form: "update" });
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
delete: async ({ request, locals }: import('./$types').RequestEvent) => {
|
||||
const form = await request.formData();
|
||||
const id = (form.get("id") as string)?.trim();
|
||||
if (!id) return fail(400, { error: "Missing id", form: "delete" });
|
||||
try {
|
||||
await locals.pb.collection(COLLECTION).delete(id);
|
||||
} catch (e: unknown) {
|
||||
const message = e && typeof e === "object" && "message" in e ? String((e as { message: string }).message) : "Delete failed";
|
||||
return fail(500, { error: message, form: "delete" });
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
};
|
||||
;null as any as Actions;
|
||||
@@ -1,17 +1,8 @@
|
||||
// @ts-nocheck
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
import type { StackqItem } from "$lib/types/items";
|
||||
|
||||
const COLLECTION = "Stackq_Items";
|
||||
|
||||
export const load = async ({ locals }: Parameters<PageServerLoad>[0]) => {
|
||||
const list = await locals.pb
|
||||
.collection(COLLECTION)
|
||||
.getFullList<StackqItem>({ sort: "-created", expand: "Parent" })
|
||||
.catch(() => [] as StackqItem[]);
|
||||
|
||||
const itemsForParent = list.map((i) => ({ id: i.id, Item: i.Item, SKU: i.SKU }));
|
||||
return { items: list, itemsForParent };
|
||||
export const load = async () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
@@ -31,4 +22,4 @@ export const actions = {
|
||||
}
|
||||
},
|
||||
};
|
||||
;null as any as Actions;
|
||||
;null as any as PageServerLoad;;null as any as Actions;
|
||||
+7
-1
@@ -16,7 +16,13 @@ type PageParentData = EnsureDefined<import('../$types.js').LayoutData>;
|
||||
|
||||
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
|
||||
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
|
||||
export type ActionData = unknown;
|
||||
type ExcludeActionFailure<T> = T extends Kit.ActionFailure<any> ? never : T extends void ? never : T;
|
||||
type ActionsSuccess<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: ExcludeActionFailure<Awaited<ReturnType<T[Key]>>>; }[keyof T];
|
||||
type ExtractActionFailure<T> = T extends Kit.ActionFailure<infer X> ? X extends void ? never : X : never;
|
||||
type ActionsFailure<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: Exclude<ExtractActionFailure<Awaited<ReturnType<T[Key]>>>, void>; }[keyof T];
|
||||
type ActionsExport = typeof import('./proxy+page.server.js').actions
|
||||
export type SubmitFunction = Kit.SubmitFunction<Expand<ActionsSuccess<ActionsExport>>, Expand<ActionsFailure<ActionsExport>>>
|
||||
export type ActionData = Expand<Kit.AwaitedActions<ActionsExport>> | null;
|
||||
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
|
||||
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
|
||||
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
|
||||
|
||||
+16
-3
@@ -11,9 +11,22 @@ type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Pa
|
||||
type EnsureDefined<T> = T extends null | undefined ? {} : T;
|
||||
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
|
||||
export type Snapshot<T = any> = Kit.Snapshot<T>;
|
||||
type PageServerParentData = EnsureDefined<import('../../$types.js').LayoutServerData>;
|
||||
type PageParentData = EnsureDefined<import('../../$types.js').LayoutData>;
|
||||
|
||||
export type EntryGenerator = () => Promise<Array<RouteParams>> | Array<RouteParams>;
|
||||
export type PageServerData = null;
|
||||
export type PageData = Expand<PageParentData>;
|
||||
export type PageProps = { params: RouteParams; data: PageData }
|
||||
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
|
||||
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
|
||||
type ExcludeActionFailure<T> = T extends Kit.ActionFailure<any> ? never : T extends void ? never : T;
|
||||
type ActionsSuccess<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: ExcludeActionFailure<Awaited<ReturnType<T[Key]>>>; }[keyof T];
|
||||
type ExtractActionFailure<T> = T extends Kit.ActionFailure<infer X> ? X extends void ? never : X : never;
|
||||
type ActionsFailure<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: Exclude<ExtractActionFailure<Awaited<ReturnType<T[Key]>>>, void>; }[keyof T];
|
||||
type ActionsExport = typeof import('./proxy+page.server.js').actions
|
||||
export type SubmitFunction = Kit.SubmitFunction<Expand<ActionsSuccess<ActionsExport>>, Expand<ActionsFailure<ActionsExport>>>
|
||||
export type ActionData = Expand<Kit.AwaitedActions<ActionsExport>> | null;
|
||||
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
|
||||
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
|
||||
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
|
||||
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
|
||||
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
|
||||
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;
|
||||
@@ -1,8 +1,108 @@
|
||||
// @ts-nocheck
|
||||
import type { PageServerLoad } from "./$types";
|
||||
import { fail } from "@sveltejs/kit";
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
import type { StackqReceipt } from "$lib/types/receipts";
|
||||
import { STATUS_OPTIONS } from "$lib/types/receipts";
|
||||
import { convertHeicFileToJpegIfNeeded } from "$lib/convert-heic-server";
|
||||
import { validateImageUpload } from "$lib/validate-image-upload";
|
||||
|
||||
export const load = async () => {
|
||||
// Placeholder: no PocketBase receipts collection yet. Return empty list.
|
||||
return { receipts: [] as { id: string }[] };
|
||||
const COLLECTION = "Stackq_Receipts";
|
||||
|
||||
const TYPE_OPTIONS = ["Purchase", "Gas"] as const;
|
||||
|
||||
export const load = async ({ locals }: Parameters<PageServerLoad>[0]) => {
|
||||
const user = locals.user as { id: string; Admin?: boolean } | null;
|
||||
if (!user?.id) return { receipts: [] as StackqReceipt[] };
|
||||
|
||||
const isAdmin = user.Admin === true;
|
||||
const list = await locals.pb
|
||||
.collection(COLLECTION)
|
||||
.getFullList<StackqReceipt>({
|
||||
sort: "-created",
|
||||
expand: "User",
|
||||
...(isAdmin ? {} : { filter: `User = "${user.id}"` }),
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(COLLECTION, err);
|
||||
return [] as StackqReceipt[];
|
||||
});
|
||||
|
||||
return { receipts: list, canEdit: isAdmin };
|
||||
};
|
||||
;null as any as PageServerLoad;
|
||||
|
||||
export const actions = {
|
||||
create: async ({ request, locals }: import('./$types').RequestEvent) => {
|
||||
const user = locals.user;
|
||||
if (!user?.id) return fail(401, { error: "You must be logged in to create a receipt." });
|
||||
|
||||
const form = await request.formData();
|
||||
const Type = (form.get("Type") as string)?.trim() || "Purchase";
|
||||
const Description = (form.get("Description") as string)?.trim() ?? "";
|
||||
if (!Description) {
|
||||
return fail(400, { error: "Description is required.", Type });
|
||||
}
|
||||
const imageFile = form.get("Image") as File | null;
|
||||
const hasImage = imageFile && imageFile.size > 0;
|
||||
if (hasImage) {
|
||||
const imgError = validateImageUpload(imageFile);
|
||||
if (imgError) return fail(400, { error: imgError, Type, Description });
|
||||
}
|
||||
|
||||
let imageToSave: File | null = hasImage ? imageFile : null;
|
||||
if (imageToSave) {
|
||||
try {
|
||||
imageToSave = await convertHeicFileToJpegIfNeeded(imageToSave);
|
||||
} catch {
|
||||
return fail(400, {
|
||||
error: "Could not convert HEIC image. Try saving as JPEG (iPhone: Settings → Camera → Formats) or use a different photo.",
|
||||
Type,
|
||||
Description,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
Status: "New",
|
||||
Type: TYPE_OPTIONS.includes(Type as (typeof TYPE_OPTIONS)[number]) ? Type : "Purchase",
|
||||
Description,
|
||||
User: user.id,
|
||||
};
|
||||
if (imageToSave) body.Image = imageToSave;
|
||||
|
||||
try {
|
||||
await locals.pb.collection(COLLECTION).create(body);
|
||||
} catch (err: unknown) {
|
||||
const message =
|
||||
err && typeof err === "object" && "message" in err
|
||||
? String((err as { message: string }).message)
|
||||
: "Failed to create receipt.";
|
||||
return fail(500, { error: message, Type, Description });
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
setStatus: async ({ request, locals }: import('./$types').RequestEvent) => {
|
||||
const user = locals.user as { id: string; Admin?: boolean } | null;
|
||||
if (!user?.id) return fail(401, { error: "Not logged in", setStatus: null });
|
||||
const form = await request.formData();
|
||||
const id = (form.get("id") as string)?.trim();
|
||||
const Status = (form.get("Status") as string)?.trim() || "New";
|
||||
if (!id) return fail(400, { error: "Missing receipt id", setStatus: null });
|
||||
if (!STATUS_OPTIONS.includes(Status as (typeof STATUS_OPTIONS)[number])) {
|
||||
return fail(400, { error: "Invalid status", setStatus: null });
|
||||
}
|
||||
const isAdmin = user.Admin === true;
|
||||
if (!isAdmin) return fail(403, { error: "Only admins can change status.", setStatus: null });
|
||||
try {
|
||||
await locals.pb.collection(COLLECTION).getOne<StackqReceipt>(id);
|
||||
await locals.pb.collection(COLLECTION).update(id, { Status });
|
||||
} catch (e: unknown) {
|
||||
const is404 = e && typeof e === "object" && "status" in e && (e as { status: number }).status === 404;
|
||||
if (is404) return fail(404, { error: "Receipt not found", setStatus: null });
|
||||
return fail(500, { error: "Failed to update status", setStatus: null });
|
||||
}
|
||||
return { success: true, setStatus: id };
|
||||
},
|
||||
};
|
||||
;null as any as Actions;
|
||||
+3
-10
@@ -11,15 +11,8 @@ type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Pa
|
||||
type EnsureDefined<T> = T extends null | undefined ? {} : T;
|
||||
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
|
||||
export type Snapshot<T = any> = Kit.Snapshot<T>;
|
||||
type PageServerParentData = EnsureDefined<import('../$types.js').LayoutServerData>;
|
||||
type PageParentData = EnsureDefined<import('../$types.js').LayoutData>;
|
||||
|
||||
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
|
||||
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
|
||||
export type ActionData = unknown;
|
||||
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
|
||||
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
|
||||
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
|
||||
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
|
||||
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
|
||||
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;
|
||||
export type PageServerData = null;
|
||||
export type PageData = Expand<PageParentData>;
|
||||
export type PageProps = { params: RouteParams; data: PageData }
|
||||
@@ -1,7 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import type { PageServerLoad } from "./$types";
|
||||
|
||||
export const load = async () => {
|
||||
return { transactions: [] as { id: string }[] };
|
||||
};
|
||||
;null as any as PageServerLoad;
|
||||
@@ -7,6 +7,8 @@
|
||||
"dependencies": {
|
||||
"barcode-detector-api-polyfill": "^1.0.20",
|
||||
"clsx": "^2.1.1",
|
||||
"heic-convert": "^2.1.0",
|
||||
"heic2any": "^0.0.4",
|
||||
"pocketbase": "^0.26.0",
|
||||
"tailwind-merge": "^3.0.0",
|
||||
},
|
||||
@@ -595,6 +597,12 @@
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"heic-convert": ["heic-convert@2.1.0", "", { "dependencies": { "heic-decode": "^2.0.0", "jpeg-js": "^0.4.4", "pngjs": "^6.0.0" } }, "sha512-1qDuRvEHifTVAj3pFIgkqGgJIr0M3X7cxEPjEp0oG4mo8GFjq99DpCo8Eg3kg17Cy0MTjxpFdoBHOatj7ZVKtg=="],
|
||||
|
||||
"heic-decode": ["heic-decode@2.1.0", "", { "dependencies": { "libheif-js": "^1.19.8" } }, "sha512-0fB3O3WMk38+PScbHLVp66jcNhsZ/ErtQ6u2lMYu/YxXgbBtl+oKOhGQHa4RpvE68k8IzbWkABzHnyAIjR758A=="],
|
||||
|
||||
"heic2any": ["heic2any@0.0.4", "", {}, "sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA=="],
|
||||
|
||||
"idb": ["idb@7.1.1", "", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="],
|
||||
|
||||
"import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
|
||||
@@ -667,6 +675,8 @@
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
@@ -687,6 +697,8 @@
|
||||
|
||||
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
||||
|
||||
"libheif-js": ["libheif-js@1.19.8", "", {}, "sha512-vQJWusIxO7wavpON1dusciL8Go9jsIQ+EUrckauFYAiSTjcmLAsuJh3SszLpvkwPci3JcL41ek2n+LUZGFpPIQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||
@@ -761,6 +773,8 @@
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
|
||||
|
||||
"pocketbase": ["pocketbase@0.26.8", "", {}, "sha512-aQ/ewvS7ncvAE8wxoW10iAZu6ElgbeFpBhKPnCfvRovNzm2gW8u/sQNPGN6vNgVEagz44kK//C61oKjfa+7Low=="],
|
||||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
||||
+7
-7
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Icon_default
|
||||
} from "./chunk-6EUZYYI5.js";
|
||||
} from "./chunk-V6DNLLKF.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import {
|
||||
check_target,
|
||||
hmr,
|
||||
@@ -9,12 +10,12 @@ import {
|
||||
snippet,
|
||||
spread_props,
|
||||
wrap_snippet
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import {
|
||||
append,
|
||||
comment
|
||||
} from "./chunk-57IKBECK.js";
|
||||
} from "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import {
|
||||
FILENAME,
|
||||
HMR,
|
||||
@@ -23,12 +24,11 @@ import {
|
||||
noop,
|
||||
pop,
|
||||
push
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/@lucide/svelte/dist/icons/check.svelte
|
||||
Check[FILENAME] = "node_modules/@lucide/svelte/dist/icons/check.svelte";
|
||||
|
||||
+7
-7
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Icon_default
|
||||
} from "./chunk-6EUZYYI5.js";
|
||||
} from "./chunk-V6DNLLKF.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import {
|
||||
check_target,
|
||||
hmr,
|
||||
@@ -9,12 +10,12 @@ import {
|
||||
snippet,
|
||||
spread_props,
|
||||
wrap_snippet
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import {
|
||||
append,
|
||||
comment
|
||||
} from "./chunk-57IKBECK.js";
|
||||
} from "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import {
|
||||
FILENAME,
|
||||
HMR,
|
||||
@@ -23,12 +24,11 @@ import {
|
||||
noop,
|
||||
pop,
|
||||
push
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/@lucide/svelte/dist/icons/chevron-right.svelte
|
||||
Chevron_right[FILENAME] = "node_modules/@lucide/svelte/dist/icons/chevron-right.svelte";
|
||||
|
||||
+7
-7
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Icon_default
|
||||
} from "./chunk-6EUZYYI5.js";
|
||||
} from "./chunk-V6DNLLKF.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import {
|
||||
check_target,
|
||||
hmr,
|
||||
@@ -9,12 +10,12 @@ import {
|
||||
snippet,
|
||||
spread_props,
|
||||
wrap_snippet
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import {
|
||||
append,
|
||||
comment
|
||||
} from "./chunk-57IKBECK.js";
|
||||
} from "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import {
|
||||
FILENAME,
|
||||
HMR,
|
||||
@@ -23,12 +24,11 @@ import {
|
||||
noop,
|
||||
pop,
|
||||
push
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/@lucide/svelte/dist/icons/circle.svelte
|
||||
Circle[FILENAME] = "node_modules/@lucide/svelte/dist/icons/circle.svelte";
|
||||
|
||||
+7
-7
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Icon_default
|
||||
} from "./chunk-6EUZYYI5.js";
|
||||
} from "./chunk-V6DNLLKF.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import {
|
||||
check_target,
|
||||
hmr,
|
||||
@@ -9,12 +10,12 @@ import {
|
||||
snippet,
|
||||
spread_props,
|
||||
wrap_snippet
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import {
|
||||
append,
|
||||
comment
|
||||
} from "./chunk-57IKBECK.js";
|
||||
} from "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import {
|
||||
FILENAME,
|
||||
HMR,
|
||||
@@ -23,12 +24,11 @@ import {
|
||||
noop,
|
||||
pop,
|
||||
push
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/@lucide/svelte/dist/icons/minus.svelte
|
||||
Minus[FILENAME] = "node_modules/@lucide/svelte/dist/icons/minus.svelte";
|
||||
|
||||
+7
-7
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Icon_default
|
||||
} from "./chunk-6EUZYYI5.js";
|
||||
} from "./chunk-V6DNLLKF.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import {
|
||||
check_target,
|
||||
hmr,
|
||||
@@ -9,12 +10,12 @@ import {
|
||||
snippet,
|
||||
spread_props,
|
||||
wrap_snippet
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import {
|
||||
append,
|
||||
comment
|
||||
} from "./chunk-57IKBECK.js";
|
||||
} from "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import {
|
||||
FILENAME,
|
||||
HMR,
|
||||
@@ -23,12 +24,11 @@ import {
|
||||
noop,
|
||||
pop,
|
||||
push
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/@lucide/svelte/dist/icons/x.svelte
|
||||
X[FILENAME] = "node_modules/@lucide/svelte/dist/icons/x.svelte";
|
||||
|
||||
+84
-60
@@ -1,229 +1,247 @@
|
||||
{
|
||||
"hash": "6372abc7",
|
||||
"hash": "e1b6cade",
|
||||
"configHash": "441370a6",
|
||||
"lockfileHash": "43c145e9",
|
||||
"browserHash": "583ba445",
|
||||
"lockfileHash": "9401e68b",
|
||||
"browserHash": "8eb13a4f",
|
||||
"optimized": {
|
||||
"svelte": {
|
||||
"src": "../../svelte/src/index-client.js",
|
||||
"file": "svelte.js",
|
||||
"fileHash": "b620c7b2",
|
||||
"fileHash": "07b115b9",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/animate": {
|
||||
"src": "../../svelte/src/animate/index.js",
|
||||
"file": "svelte_animate.js",
|
||||
"fileHash": "02f610c1",
|
||||
"fileHash": "c012b289",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/attachments": {
|
||||
"src": "../../svelte/src/attachments/index.js",
|
||||
"file": "svelte_attachments.js",
|
||||
"fileHash": "aa2fd54c",
|
||||
"fileHash": "6bded66c",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/easing": {
|
||||
"src": "../../svelte/src/easing/index.js",
|
||||
"file": "svelte_easing.js",
|
||||
"fileHash": "0f0d888b",
|
||||
"fileHash": "8c8820c0",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/internal": {
|
||||
"src": "../../svelte/src/internal/index.js",
|
||||
"file": "svelte_internal.js",
|
||||
"fileHash": "ae1c44b6",
|
||||
"fileHash": "adeb43af",
|
||||
"needsInterop": true
|
||||
},
|
||||
"svelte/internal/client": {
|
||||
"src": "../../svelte/src/internal/client/index.js",
|
||||
"file": "svelte_internal_client.js",
|
||||
"fileHash": "9548cc44",
|
||||
"fileHash": "c34b6da3",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/internal/disclose-version": {
|
||||
"src": "../../svelte/src/internal/disclose-version.js",
|
||||
"file": "svelte_internal_disclose-version.js",
|
||||
"fileHash": "523d7d1e",
|
||||
"fileHash": "9a06bc8d",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/internal/flags/async": {
|
||||
"src": "../../svelte/src/internal/flags/async.js",
|
||||
"file": "svelte_internal_flags_async.js",
|
||||
"fileHash": "69240c49",
|
||||
"fileHash": "61336ace",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/internal/flags/legacy": {
|
||||
"src": "../../svelte/src/internal/flags/legacy.js",
|
||||
"file": "svelte_internal_flags_legacy.js",
|
||||
"fileHash": "4a2d0680",
|
||||
"fileHash": "b5bf1ecd",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/internal/flags/tracing": {
|
||||
"src": "../../svelte/src/internal/flags/tracing.js",
|
||||
"file": "svelte_internal_flags_tracing.js",
|
||||
"fileHash": "65275630",
|
||||
"fileHash": "3fd00f44",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/legacy": {
|
||||
"src": "../../svelte/src/legacy/legacy-client.js",
|
||||
"file": "svelte_legacy.js",
|
||||
"fileHash": "753552cd",
|
||||
"fileHash": "3de6d8ec",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/motion": {
|
||||
"src": "../../svelte/src/motion/index.js",
|
||||
"file": "svelte_motion.js",
|
||||
"fileHash": "1405f1f5",
|
||||
"fileHash": "d56675c0",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/reactivity": {
|
||||
"src": "../../svelte/src/reactivity/index-client.js",
|
||||
"file": "svelte_reactivity.js",
|
||||
"fileHash": "a6ac2ac8",
|
||||
"fileHash": "699399e6",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/reactivity/window": {
|
||||
"src": "../../svelte/src/reactivity/window/index.js",
|
||||
"file": "svelte_reactivity_window.js",
|
||||
"fileHash": "25337be5",
|
||||
"fileHash": "a0847fd2",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/store": {
|
||||
"src": "../../svelte/src/store/index-client.js",
|
||||
"file": "svelte_store.js",
|
||||
"fileHash": "0bb2bc3e",
|
||||
"fileHash": "14d7ddb4",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/transition": {
|
||||
"src": "../../svelte/src/transition/index.js",
|
||||
"file": "svelte_transition.js",
|
||||
"fileHash": "0c13897c",
|
||||
"fileHash": "423bf8f4",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte/events": {
|
||||
"src": "../../svelte/src/events/index.js",
|
||||
"file": "svelte_events.js",
|
||||
"fileHash": "8e64c629",
|
||||
"fileHash": "078c4a8c",
|
||||
"needsInterop": false
|
||||
},
|
||||
"svelte > clsx": {
|
||||
"src": "../../clsx/dist/clsx.mjs",
|
||||
"file": "svelte___clsx.js",
|
||||
"fileHash": "a1e066f0",
|
||||
"fileHash": "1ee833c7",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@lucide/svelte/icons/check": {
|
||||
"src": "../../@lucide/svelte/dist/icons/check.js",
|
||||
"file": "@lucide_svelte_icons_check.js",
|
||||
"fileHash": "891b74d4",
|
||||
"fileHash": "0ac209ae",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@lucide/svelte/icons/chevron-right": {
|
||||
"src": "../../@lucide/svelte/dist/icons/chevron-right.js",
|
||||
"file": "@lucide_svelte_icons_chevron-right.js",
|
||||
"fileHash": "63648db9",
|
||||
"fileHash": "c09b9f4f",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@lucide/svelte/icons/circle": {
|
||||
"src": "../../@lucide/svelte/dist/icons/circle.js",
|
||||
"file": "@lucide_svelte_icons_circle.js",
|
||||
"fileHash": "11e4822b",
|
||||
"fileHash": "e48abf24",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@lucide/svelte/icons/minus": {
|
||||
"src": "../../@lucide/svelte/dist/icons/minus.js",
|
||||
"file": "@lucide_svelte_icons_minus.js",
|
||||
"fileHash": "30cf92e4",
|
||||
"fileHash": "cbdf943d",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@lucide/svelte/icons/x": {
|
||||
"src": "../../@lucide/svelte/dist/icons/x.js",
|
||||
"file": "@lucide_svelte_icons_x.js",
|
||||
"fileHash": "aa690a02",
|
||||
"fileHash": "32d222b0",
|
||||
"needsInterop": false
|
||||
},
|
||||
"barcode-detector-api-polyfill": {
|
||||
"src": "../../barcode-detector-api-polyfill/esm/index.js",
|
||||
"file": "barcode-detector-api-polyfill.js",
|
||||
"fileHash": "55497bad",
|
||||
"fileHash": "44de5b82",
|
||||
"needsInterop": false
|
||||
},
|
||||
"bits-ui": {
|
||||
"src": "../../bits-ui/dist/index.js",
|
||||
"file": "bits-ui.js",
|
||||
"fileHash": "1d3c3c46",
|
||||
"fileHash": "9adf7bd3",
|
||||
"needsInterop": false
|
||||
},
|
||||
"clsx": {
|
||||
"src": "../../clsx/dist/clsx.mjs",
|
||||
"file": "clsx.js",
|
||||
"fileHash": "b6a14292",
|
||||
"fileHash": "6547c390",
|
||||
"needsInterop": false
|
||||
},
|
||||
"devalue": {
|
||||
"src": "../../devalue/index.js",
|
||||
"file": "devalue.js",
|
||||
"fileHash": "2ff911fe",
|
||||
"fileHash": "6bd276fd",
|
||||
"needsInterop": false
|
||||
},
|
||||
"esm-env": {
|
||||
"src": "../../esm-env/index.js",
|
||||
"file": "esm-env.js",
|
||||
"fileHash": "5a410efb",
|
||||
"fileHash": "a16abac7",
|
||||
"needsInterop": false
|
||||
},
|
||||
"pocketbase": {
|
||||
"src": "../../pocketbase/dist/pocketbase.es.mjs",
|
||||
"file": "pocketbase.js",
|
||||
"fileHash": "9e68746a",
|
||||
"fileHash": "241d5081",
|
||||
"needsInterop": false
|
||||
},
|
||||
"tailwind-merge": {
|
||||
"src": "../../tailwind-merge/dist/bundle-mjs.mjs",
|
||||
"file": "tailwind-merge.js",
|
||||
"fileHash": "d8639500",
|
||||
"fileHash": "7d5f8442",
|
||||
"needsInterop": false
|
||||
},
|
||||
"tailwind-variants": {
|
||||
"src": "../../tailwind-variants/dist/index.js",
|
||||
"file": "tailwind-variants.js",
|
||||
"fileHash": "cf2013f5",
|
||||
"fileHash": "3821161a",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@lucide/svelte/icons/chevron-down": {
|
||||
"src": "../../@lucide/svelte/dist/icons/chevron-down.js",
|
||||
"file": "@lucide_svelte_icons_chevron-down.js",
|
||||
"fileHash": "48902207",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@lucide/svelte/icons/chevron-up": {
|
||||
"src": "../../@lucide/svelte/dist/icons/chevron-up.js",
|
||||
"file": "@lucide_svelte_icons_chevron-up.js",
|
||||
"fileHash": "b33cd26c",
|
||||
"needsInterop": false
|
||||
},
|
||||
"heic2any": {
|
||||
"src": "../../heic2any/dist/heic2any.js",
|
||||
"file": "heic2any.js",
|
||||
"fileHash": "4e438dea",
|
||||
"needsInterop": true
|
||||
},
|
||||
"@lucide/svelte/icons/loader-2": {
|
||||
"src": "../../@lucide/svelte/dist/icons/loader-2.js",
|
||||
"file": "@lucide_svelte_icons_loader-2.js",
|
||||
"fileHash": "7eba973c",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
"chunks": {
|
||||
"chunk-6EUZYYI5": {
|
||||
"file": "chunk-6EUZYYI5.js"
|
||||
"chunk-N4HEU72H": {
|
||||
"file": "chunk-N4HEU72H.js"
|
||||
},
|
||||
"chunk-HRPYXN4O": {
|
||||
"file": "chunk-HRPYXN4O.js"
|
||||
},
|
||||
"chunk-MZVN5SDE": {
|
||||
"file": "chunk-MZVN5SDE.js"
|
||||
},
|
||||
"chunk-EZEETTA4": {
|
||||
"file": "chunk-EZEETTA4.js"
|
||||
},
|
||||
"chunk-3TACVO2P": {
|
||||
"file": "chunk-3TACVO2P.js"
|
||||
"chunk-MPD7DSJG": {
|
||||
"file": "chunk-MPD7DSJG.js"
|
||||
},
|
||||
"chunk-7RQDXF5S": {
|
||||
"file": "chunk-7RQDXF5S.js"
|
||||
},
|
||||
"chunk-YERFD2CZ": {
|
||||
"file": "chunk-YERFD2CZ.js"
|
||||
"chunk-V6DNLLKF": {
|
||||
"file": "chunk-V6DNLLKF.js"
|
||||
},
|
||||
"chunk-EXK7SDUS": {
|
||||
"file": "chunk-EXK7SDUS.js"
|
||||
"chunk-2LGM3QYM": {
|
||||
"file": "chunk-2LGM3QYM.js"
|
||||
},
|
||||
"chunk-NTAG77ZX": {
|
||||
"file": "chunk-NTAG77ZX.js"
|
||||
},
|
||||
"chunk-MKTP2BFS": {
|
||||
"file": "chunk-MKTP2BFS.js"
|
||||
},
|
||||
"chunk-U7P2NEEE": {
|
||||
"file": "chunk-U7P2NEEE.js"
|
||||
},
|
||||
"chunk-57IKBECK": {
|
||||
"file": "chunk-57IKBECK.js"
|
||||
},
|
||||
"chunk-JC3VXQM7": {
|
||||
"file": "chunk-JC3VXQM7.js"
|
||||
"chunk-HZ6NBROY": {
|
||||
"file": "chunk-HZ6NBROY.js"
|
||||
},
|
||||
"chunk-XWATFG4W": {
|
||||
"file": "chunk-XWATFG4W.js"
|
||||
@@ -231,14 +249,20 @@
|
||||
"chunk-HNWPC2PS": {
|
||||
"file": "chunk-HNWPC2PS.js"
|
||||
},
|
||||
"chunk-2LGM3QYM": {
|
||||
"file": "chunk-2LGM3QYM.js"
|
||||
"chunk-MZVN5SDE": {
|
||||
"file": "chunk-MZVN5SDE.js"
|
||||
},
|
||||
"chunk-OHYQYV5R": {
|
||||
"file": "chunk-OHYQYV5R.js"
|
||||
},
|
||||
"chunk-X4VJQ2O3": {
|
||||
"file": "chunk-X4VJQ2O3.js"
|
||||
"chunk-HRPYXN4O": {
|
||||
"file": "chunk-HRPYXN4O.js"
|
||||
},
|
||||
"chunk-YERFD2CZ": {
|
||||
"file": "chunk-YERFD2CZ.js"
|
||||
},
|
||||
"chunk-LYA4E26T": {
|
||||
"file": "chunk-LYA4E26T.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
__publicField
|
||||
} from "./chunk-X4VJQ2O3.js";
|
||||
} from "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/ts-custom-error/dist/custom-error.mjs
|
||||
function fixProto(target, prototype) {
|
||||
|
||||
+11
-11
@@ -1,9 +1,9 @@
|
||||
import "./chunk-MZVN5SDE.js";
|
||||
import {
|
||||
SvelteMap
|
||||
} from "./chunk-EZEETTA4.js";
|
||||
import "./chunk-3TACVO2P.js";
|
||||
} from "./chunk-N4HEU72H.js";
|
||||
import "./chunk-MPD7DSJG.js";
|
||||
import "./chunk-7RQDXF5S.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import {
|
||||
add_locations,
|
||||
append_styles,
|
||||
@@ -38,10 +38,7 @@ import {
|
||||
validate_snippet_args,
|
||||
validate_void_dynamic_element,
|
||||
wrap_snippet
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
import {
|
||||
clsx
|
||||
} from "./chunk-U7P2NEEE.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import {
|
||||
append,
|
||||
comment,
|
||||
@@ -52,7 +49,10 @@ import {
|
||||
set_text,
|
||||
text,
|
||||
unmount
|
||||
} from "./chunk-57IKBECK.js";
|
||||
} from "./chunk-MKTP2BFS.js";
|
||||
import {
|
||||
clsx
|
||||
} from "./chunk-U7P2NEEE.js";
|
||||
import {
|
||||
FILENAME,
|
||||
HMR,
|
||||
@@ -88,12 +88,12 @@ import {
|
||||
user_derived,
|
||||
user_effect,
|
||||
user_pre_effect
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import {
|
||||
true_default
|
||||
} from "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import "./chunk-MZVN5SDE.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import {
|
||||
__export,
|
||||
@@ -103,7 +103,7 @@ import {
|
||||
__privateSet,
|
||||
__privateWrapper,
|
||||
__publicField
|
||||
} from "./chunk-X4VJQ2O3.js";
|
||||
} from "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/bits-ui/dist/bits/accordion/exports.js
|
||||
var exports_exports = {};
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
import {
|
||||
createSubscriber
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
import {
|
||||
__privateAdd,
|
||||
__privateGet,
|
||||
__privateSet
|
||||
} from "./chunk-X4VJQ2O3.js";
|
||||
|
||||
// node_modules/svelte/src/reactivity/reactive-value.js
|
||||
var _fn, _subscribe;
|
||||
var ReactiveValue = class {
|
||||
/**
|
||||
*
|
||||
* @param {() => T} fn
|
||||
* @param {(update: () => void) => void} onsubscribe
|
||||
*/
|
||||
constructor(fn, onsubscribe) {
|
||||
__privateAdd(this, _fn);
|
||||
__privateAdd(this, _subscribe);
|
||||
__privateSet(this, _fn, fn);
|
||||
__privateSet(this, _subscribe, createSubscriber(onsubscribe));
|
||||
}
|
||||
get current() {
|
||||
__privateGet(this, _subscribe).call(this);
|
||||
return __privateGet(this, _fn).call(this);
|
||||
}
|
||||
};
|
||||
_fn = new WeakMap();
|
||||
_subscribe = new WeakMap();
|
||||
|
||||
export {
|
||||
ReactiveValue
|
||||
};
|
||||
//# sourceMappingURL=chunk-3TACVO2P.js.map
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": ["../../svelte/src/reactivity/reactive-value.js"],
|
||||
"sourcesContent": ["import { createSubscriber } from './create-subscriber.js';\n\n/**\n * @template T\n */\nexport class ReactiveValue {\n\t#fn;\n\t#subscribe;\n\n\t/**\n\t *\n\t * @param {() => T} fn\n\t * @param {(update: () => void) => void} onsubscribe\n\t */\n\tconstructor(fn, onsubscribe) {\n\t\tthis.#fn = fn;\n\t\tthis.#subscribe = createSubscriber(onsubscribe);\n\t}\n\n\tget current() {\n\t\tthis.#subscribe();\n\t\treturn this.#fn();\n\t}\n}\n"],
|
||||
"mappings": ";;;;;;;;;;AAAA;AAKO,IAAM,gBAAN,MAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS1B,YAAY,IAAI,aAAa;AAR7B;AACA;AAQC,uBAAK,KAAM;AACX,uBAAK,YAAa,iBAAiB,WAAW;AAAA,EAC/C;AAAA,EAEA,IAAI,UAAU;AACb,uBAAK,YAAL;AACA,WAAO,mBAAK,KAAL;AAAA,EACR;AACD;AAjBC;AACA;",
|
||||
"names": []
|
||||
}
|
||||
-1017
File diff suppressed because it is too large
Load Diff
-7
File diff suppressed because one or more lines are too long
-188
@@ -1,188 +0,0 @@
|
||||
import {
|
||||
add_locations,
|
||||
attribute_effect,
|
||||
check_target,
|
||||
each,
|
||||
element,
|
||||
hmr,
|
||||
index,
|
||||
legacy_api,
|
||||
prop,
|
||||
rest_props,
|
||||
snippet,
|
||||
validate_dynamic_element_tag
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
import {
|
||||
append,
|
||||
comment,
|
||||
from_svg
|
||||
} from "./chunk-57IKBECK.js";
|
||||
import {
|
||||
FILENAME,
|
||||
HMR,
|
||||
add_svelte_meta,
|
||||
child,
|
||||
first_child,
|
||||
get,
|
||||
noop,
|
||||
pop,
|
||||
push,
|
||||
reset,
|
||||
sibling,
|
||||
to_array,
|
||||
user_derived
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
|
||||
// node_modules/@lucide/svelte/dist/defaultAttributes.js
|
||||
var defaultAttributes = {
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
width: 24,
|
||||
height: 24,
|
||||
viewBox: "0 0 24 24",
|
||||
fill: "none",
|
||||
stroke: "currentColor",
|
||||
"stroke-width": 2,
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
};
|
||||
var defaultAttributes_default = defaultAttributes;
|
||||
|
||||
// node_modules/@lucide/svelte/dist/Icon.svelte
|
||||
Icon[FILENAME] = "node_modules/@lucide/svelte/dist/Icon.svelte";
|
||||
var root = add_locations(from_svg(`<svg><!><!></svg>`), Icon[FILENAME], [[5, 0]]);
|
||||
function Icon($$anchor, $$props) {
|
||||
check_target(new.target);
|
||||
push($$props, true, Icon);
|
||||
const color = prop($$props, "color", 3, "currentColor"), size = prop($$props, "size", 3, 24), strokeWidth = prop($$props, "strokeWidth", 3, 2), absoluteStrokeWidth = prop($$props, "absoluteStrokeWidth", 3, false), iconNode = prop($$props, "iconNode", 19, () => []), props = rest_props(
|
||||
$$props,
|
||||
[
|
||||
"$$slots",
|
||||
"$$events",
|
||||
"$$legacy",
|
||||
"name",
|
||||
"color",
|
||||
"size",
|
||||
"strokeWidth",
|
||||
"absoluteStrokeWidth",
|
||||
"iconNode",
|
||||
"children"
|
||||
],
|
||||
"props"
|
||||
);
|
||||
var $$exports = { ...legacy_api() };
|
||||
var svg = root();
|
||||
attribute_effect(
|
||||
svg,
|
||||
($0) => ({
|
||||
...defaultAttributes_default,
|
||||
...props,
|
||||
width: size(),
|
||||
height: size(),
|
||||
stroke: color(),
|
||||
"stroke-width": $0,
|
||||
class: [
|
||||
"lucide-icon lucide",
|
||||
$$props.name && `lucide-${$$props.name}`,
|
||||
$$props.class
|
||||
]
|
||||
}),
|
||||
[
|
||||
() => absoluteStrokeWidth() ? Number(strokeWidth()) * 24 / Number(size()) : strokeWidth()
|
||||
]
|
||||
);
|
||||
var node = child(svg);
|
||||
add_svelte_meta(
|
||||
() => each(node, 17, iconNode, index, ($$anchor2, $$item) => {
|
||||
var $$array = user_derived(() => to_array(get($$item), 2));
|
||||
let tag = () => get($$array)[0];
|
||||
tag();
|
||||
let attrs = () => get($$array)[1];
|
||||
attrs();
|
||||
var fragment = comment();
|
||||
var node_1 = first_child(fragment);
|
||||
{
|
||||
validate_dynamic_element_tag(tag);
|
||||
element(
|
||||
node_1,
|
||||
tag,
|
||||
true,
|
||||
($$element, $$anchor3) => {
|
||||
attribute_effect($$element, () => ({ ...attrs() }));
|
||||
},
|
||||
void 0,
|
||||
[15, 4]
|
||||
);
|
||||
}
|
||||
append($$anchor2, fragment);
|
||||
}),
|
||||
"each",
|
||||
Icon,
|
||||
14,
|
||||
2
|
||||
);
|
||||
var node_2 = sibling(node);
|
||||
add_svelte_meta(() => snippet(node_2, () => $$props.children ?? noop), "render", Icon, 20, 2);
|
||||
reset(svg);
|
||||
append($$anchor, svg);
|
||||
return pop($$exports);
|
||||
}
|
||||
if (import.meta.hot) {
|
||||
Icon = hmr(Icon);
|
||||
import.meta.hot.accept((module) => {
|
||||
Icon[HMR].update(module.default);
|
||||
});
|
||||
}
|
||||
var Icon_default = Icon;
|
||||
|
||||
export {
|
||||
Icon_default
|
||||
};
|
||||
/*! Bundled license information:
|
||||
|
||||
@lucide/svelte/dist/defaultAttributes.js:
|
||||
(**
|
||||
* @license @lucide/svelte v0.561.0 - ISC
|
||||
*
|
||||
* ISC License
|
||||
*
|
||||
* Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* The MIT License (MIT) (for portions derived from Feather)
|
||||
*
|
||||
* Copyright (c) 2013-2023 Cole Bemis
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*)
|
||||
*/
|
||||
//# sourceMappingURL=chunk-6EUZYYI5.js.map
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": ["../../@lucide/svelte/dist/defaultAttributes.js", "../../@lucide/svelte/dist/Icon.svelte"],
|
||||
"sourcesContent": ["/**\n * @license @lucide/svelte v0.561.0 - ISC\n *\n * ISC License\n * \n * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.\n * \n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n * \n * ---\n * \n * The MIT License (MIT) (for portions derived from Feather)\n * \n * Copyright (c) 2013-2023 Cole Bemis\n * \n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n * \n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n * \n */\nconst defaultAttributes = {\n xmlns: 'http://www.w3.org/2000/svg',\n width: 24,\n height: 24,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n 'stroke-width': 2,\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n};\nexport default defaultAttributes;\n", "<script lang=\"ts\">import defaultAttributes from './defaultAttributes.js';\nconst { name, color = 'currentColor', size = 24, strokeWidth = 2, absoluteStrokeWidth = false, iconNode = [], children, ...props } = $props();\n</script>\n\n<svg\n {...defaultAttributes}\n {...props}\n width={size}\n height={size}\n stroke={color}\n stroke-width={absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth}\n class={['lucide-icon lucide', name && `lucide-${name}`, props.class]}\n>\n {#each iconNode as [tag, attrs]}\n <svelte:element\n this={tag}\n {...attrs}\n />\n {/each}\n {@render children?.()}\n</svg>\n"],
|
||||
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,IAAM,oBAAoB;AAAA,EACtB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,mBAAmB;AACvB;AACA,IAAO,4BAAQ;;;;;iCCvDf;;;QACc,QAAK,KAAA,SAAA,SAAA,GAAG,cAAc,GAAE,OAAI,KAAA,SAAA,QAAA,GAAG,EAAE,GAAE,cAAW,KAAA,SAAA,eAAA,GAAG,CAAC,GAAE,sBAAmB,KAAA,SAAA,uBAAA,GAAG,KAAK,GAAE,WAAQ,KAAA,SAAA,YAAA,IAAA,MAAA,CAAA,CAAA,GAAoB,QAAK;;;;;;;;;;;;;;;;;MAG/H,MAAA,KAAA;;IAAA;;SACK;SACA;aACG,KAAI;cACH,KAAI;cACJ,MAAK;;;QAEL;;gBAAsD;;;;YADhD,oBAAmB,IAAI,OAAO,YAAW,CAAA,IAAI,KAAM,OAAO,KAAI,CAAA,IAAI,YAAW;;;mBAN5F,GAAA;;yBASQ,UAAQ,OAAA,CAAAA,WAAA,WAAA;;UAAK,MAAI,MAAA,IAAA,OAAA,EAAA,CAAA;;UAAC,QAAM,MAAA,IAAA,OAAA,EAAA,CAAA;;;;;qCAErB,GAAG;;;UAAH;;;oDACF,MAAK,EAAA,EAAA;;;;;;;;;;;;;;;QAZd,GAAA;mBAAA,GAAA;;AAFO;;;;;;;;",
|
||||
"names": ["$$anchor"]
|
||||
}
|
||||
-4576
File diff suppressed because it is too large
Load Diff
-7
File diff suppressed because one or more lines are too long
-736
@@ -1,736 +0,0 @@
|
||||
import {
|
||||
ReactiveValue
|
||||
} from "./chunk-3TACVO2P.js";
|
||||
import {
|
||||
active_reaction,
|
||||
get,
|
||||
increment,
|
||||
label,
|
||||
on,
|
||||
set,
|
||||
set_active_reaction,
|
||||
source,
|
||||
state,
|
||||
tag,
|
||||
update_version,
|
||||
user_derived
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
import {
|
||||
true_default
|
||||
} from "./chunk-HNWPC2PS.js";
|
||||
import {
|
||||
__privateAdd,
|
||||
__privateGet,
|
||||
__privateMethod,
|
||||
__privateSet,
|
||||
__superGet
|
||||
} from "./chunk-X4VJQ2O3.js";
|
||||
|
||||
// node_modules/svelte/src/reactivity/date.js
|
||||
var inited = false;
|
||||
var _time, _deriveds, _reaction, _SvelteDate_instances, init_fn;
|
||||
var _SvelteDate = class _SvelteDate extends Date {
|
||||
/** @param {any[]} params */
|
||||
constructor(...params) {
|
||||
super(...params);
|
||||
__privateAdd(this, _SvelteDate_instances);
|
||||
__privateAdd(this, _time, state(super.getTime()));
|
||||
/** @type {Map<keyof Date, Source<unknown>>} */
|
||||
__privateAdd(this, _deriveds, /* @__PURE__ */ new Map());
|
||||
__privateAdd(this, _reaction, active_reaction);
|
||||
if (true_default) {
|
||||
tag(__privateGet(this, _time), "SvelteDate.#time");
|
||||
}
|
||||
if (!inited) __privateMethod(this, _SvelteDate_instances, init_fn).call(this);
|
||||
}
|
||||
};
|
||||
_time = new WeakMap();
|
||||
_deriveds = new WeakMap();
|
||||
_reaction = new WeakMap();
|
||||
_SvelteDate_instances = new WeakSet();
|
||||
init_fn = function() {
|
||||
inited = true;
|
||||
var proto = _SvelteDate.prototype;
|
||||
var date_proto = Date.prototype;
|
||||
var methods = (
|
||||
/** @type {Array<keyof Date & string>} */
|
||||
Object.getOwnPropertyNames(date_proto)
|
||||
);
|
||||
for (const method of methods) {
|
||||
if (method.startsWith("get") || method.startsWith("to") || method === "valueOf") {
|
||||
proto[method] = function(...args) {
|
||||
if (args.length > 0) {
|
||||
get(__privateGet(this, _time));
|
||||
return date_proto[method].apply(this, args);
|
||||
}
|
||||
var d = __privateGet(this, _deriveds).get(method);
|
||||
if (d === void 0) {
|
||||
const reaction = active_reaction;
|
||||
set_active_reaction(__privateGet(this, _reaction));
|
||||
d = user_derived(() => {
|
||||
get(__privateGet(this, _time));
|
||||
return date_proto[method].apply(this, args);
|
||||
});
|
||||
__privateGet(this, _deriveds).set(method, d);
|
||||
set_active_reaction(reaction);
|
||||
}
|
||||
return get(d);
|
||||
};
|
||||
}
|
||||
if (method.startsWith("set")) {
|
||||
proto[method] = function(...args) {
|
||||
var result = date_proto[method].apply(this, args);
|
||||
set(__privateGet(this, _time), date_proto.getTime.call(this));
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
var SvelteDate = _SvelteDate;
|
||||
|
||||
// node_modules/svelte/src/reactivity/set.js
|
||||
var read_methods = ["forEach", "isDisjointFrom", "isSubsetOf", "isSupersetOf"];
|
||||
var set_like_methods = ["difference", "intersection", "symmetricDifference", "union"];
|
||||
var inited2 = false;
|
||||
var _sources, _version, _size, _update_version, _SvelteSet_instances, source_fn, init_fn2;
|
||||
var _SvelteSet = class _SvelteSet extends Set {
|
||||
/**
|
||||
* @param {Iterable<T> | null | undefined} [value]
|
||||
*/
|
||||
constructor(value) {
|
||||
super();
|
||||
__privateAdd(this, _SvelteSet_instances);
|
||||
/** @type {Map<T, Source<boolean>>} */
|
||||
__privateAdd(this, _sources, /* @__PURE__ */ new Map());
|
||||
__privateAdd(this, _version, state(0));
|
||||
__privateAdd(this, _size, state(0));
|
||||
__privateAdd(this, _update_version, update_version || -1);
|
||||
if (true_default) {
|
||||
value = new Set(value);
|
||||
tag(__privateGet(this, _version), "SvelteSet version");
|
||||
tag(__privateGet(this, _size), "SvelteSet.size");
|
||||
}
|
||||
if (value) {
|
||||
for (var element of value) {
|
||||
super.add(element);
|
||||
}
|
||||
__privateGet(this, _size).v = super.size;
|
||||
}
|
||||
if (!inited2) __privateMethod(this, _SvelteSet_instances, init_fn2).call(this);
|
||||
}
|
||||
/** @param {T} value */
|
||||
has(value) {
|
||||
var has = super.has(value);
|
||||
var sources = __privateGet(this, _sources);
|
||||
var s = sources.get(value);
|
||||
if (s === void 0) {
|
||||
if (!has) {
|
||||
get(__privateGet(this, _version));
|
||||
return false;
|
||||
}
|
||||
s = __privateMethod(this, _SvelteSet_instances, source_fn).call(this, true);
|
||||
if (true_default) {
|
||||
tag(s, `SvelteSet has(${label(value)})`);
|
||||
}
|
||||
sources.set(value, s);
|
||||
}
|
||||
get(s);
|
||||
return has;
|
||||
}
|
||||
/** @param {T} value */
|
||||
add(value) {
|
||||
if (!super.has(value)) {
|
||||
super.add(value);
|
||||
set(__privateGet(this, _size), super.size);
|
||||
increment(__privateGet(this, _version));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/** @param {T} value */
|
||||
delete(value) {
|
||||
var deleted = super.delete(value);
|
||||
var sources = __privateGet(this, _sources);
|
||||
var s = sources.get(value);
|
||||
if (s !== void 0) {
|
||||
sources.delete(value);
|
||||
set(s, false);
|
||||
}
|
||||
if (deleted) {
|
||||
set(__privateGet(this, _size), super.size);
|
||||
increment(__privateGet(this, _version));
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
clear() {
|
||||
if (super.size === 0) {
|
||||
return;
|
||||
}
|
||||
super.clear();
|
||||
var sources = __privateGet(this, _sources);
|
||||
for (var s of sources.values()) {
|
||||
set(s, false);
|
||||
}
|
||||
sources.clear();
|
||||
set(__privateGet(this, _size), 0);
|
||||
increment(__privateGet(this, _version));
|
||||
}
|
||||
keys() {
|
||||
return this.values();
|
||||
}
|
||||
values() {
|
||||
get(__privateGet(this, _version));
|
||||
return super.values();
|
||||
}
|
||||
entries() {
|
||||
get(__privateGet(this, _version));
|
||||
return super.entries();
|
||||
}
|
||||
[Symbol.iterator]() {
|
||||
return this.keys();
|
||||
}
|
||||
get size() {
|
||||
return get(__privateGet(this, _size));
|
||||
}
|
||||
};
|
||||
_sources = new WeakMap();
|
||||
_version = new WeakMap();
|
||||
_size = new WeakMap();
|
||||
_update_version = new WeakMap();
|
||||
_SvelteSet_instances = new WeakSet();
|
||||
/**
|
||||
* If the source is being created inside the same reaction as the SvelteSet instance,
|
||||
* we use `state` so that it will not be a dependency of the reaction. Otherwise we
|
||||
* use `source` so it will be.
|
||||
*
|
||||
* @template T
|
||||
* @param {T} value
|
||||
* @returns {Source<T>}
|
||||
*/
|
||||
source_fn = function(value) {
|
||||
return update_version === __privateGet(this, _update_version) ? state(value) : source(value);
|
||||
};
|
||||
// We init as part of the first instance so that we can treeshake this class
|
||||
init_fn2 = function() {
|
||||
inited2 = true;
|
||||
var proto = _SvelteSet.prototype;
|
||||
var set_proto = Set.prototype;
|
||||
for (const method of read_methods) {
|
||||
proto[method] = function(...v) {
|
||||
get(__privateGet(this, _version));
|
||||
return set_proto[method].apply(this, v);
|
||||
};
|
||||
}
|
||||
for (const method of set_like_methods) {
|
||||
proto[method] = function(...v) {
|
||||
get(__privateGet(this, _version));
|
||||
var set2 = (
|
||||
/** @type {Set<T>} */
|
||||
set_proto[method].apply(this, v)
|
||||
);
|
||||
return new _SvelteSet(set2);
|
||||
};
|
||||
}
|
||||
};
|
||||
var SvelteSet = _SvelteSet;
|
||||
|
||||
// node_modules/svelte/src/reactivity/map.js
|
||||
var _sources2, _version2, _size2, _update_version2, _SvelteMap_instances, source_fn2, read_all_fn;
|
||||
var _SvelteMap = class _SvelteMap extends Map {
|
||||
/**
|
||||
* @param {Iterable<readonly [K, V]> | null | undefined} [value]
|
||||
*/
|
||||
constructor(value) {
|
||||
super();
|
||||
__privateAdd(this, _SvelteMap_instances);
|
||||
/** @type {Map<K, Source<number>>} */
|
||||
__privateAdd(this, _sources2, /* @__PURE__ */ new Map());
|
||||
__privateAdd(this, _version2, state(0));
|
||||
__privateAdd(this, _size2, state(0));
|
||||
__privateAdd(this, _update_version2, update_version || -1);
|
||||
if (true_default) {
|
||||
value = new Map(value);
|
||||
tag(__privateGet(this, _version2), "SvelteMap version");
|
||||
tag(__privateGet(this, _size2), "SvelteMap.size");
|
||||
}
|
||||
if (value) {
|
||||
for (var [key, v] of value) {
|
||||
super.set(key, v);
|
||||
}
|
||||
__privateGet(this, _size2).v = super.size;
|
||||
}
|
||||
}
|
||||
/** @param {K} key */
|
||||
has(key) {
|
||||
var sources = __privateGet(this, _sources2);
|
||||
var s = sources.get(key);
|
||||
if (s === void 0) {
|
||||
var ret = super.get(key);
|
||||
if (ret !== void 0) {
|
||||
s = __privateMethod(this, _SvelteMap_instances, source_fn2).call(this, 0);
|
||||
if (true_default) {
|
||||
tag(s, `SvelteMap get(${label(key)})`);
|
||||
}
|
||||
sources.set(key, s);
|
||||
} else {
|
||||
get(__privateGet(this, _version2));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
get(s);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* @param {(value: V, key: K, map: Map<K, V>) => void} callbackfn
|
||||
* @param {any} [this_arg]
|
||||
*/
|
||||
forEach(callbackfn, this_arg) {
|
||||
__privateMethod(this, _SvelteMap_instances, read_all_fn).call(this);
|
||||
super.forEach(callbackfn, this_arg);
|
||||
}
|
||||
/** @param {K} key */
|
||||
get(key) {
|
||||
var sources = __privateGet(this, _sources2);
|
||||
var s = sources.get(key);
|
||||
if (s === void 0) {
|
||||
var ret = super.get(key);
|
||||
if (ret !== void 0) {
|
||||
s = __privateMethod(this, _SvelteMap_instances, source_fn2).call(this, 0);
|
||||
if (true_default) {
|
||||
tag(s, `SvelteMap get(${label(key)})`);
|
||||
}
|
||||
sources.set(key, s);
|
||||
} else {
|
||||
get(__privateGet(this, _version2));
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
get(s);
|
||||
return super.get(key);
|
||||
}
|
||||
/**
|
||||
* @param {K} key
|
||||
* @param {V} value
|
||||
* */
|
||||
set(key, value) {
|
||||
var _a;
|
||||
var sources = __privateGet(this, _sources2);
|
||||
var s = sources.get(key);
|
||||
var prev_res = super.get(key);
|
||||
var res = super.set(key, value);
|
||||
var version = __privateGet(this, _version2);
|
||||
if (s === void 0) {
|
||||
s = __privateMethod(this, _SvelteMap_instances, source_fn2).call(this, 0);
|
||||
if (true_default) {
|
||||
tag(s, `SvelteMap get(${label(key)})`);
|
||||
}
|
||||
sources.set(key, s);
|
||||
set(__privateGet(this, _size2), super.size);
|
||||
increment(version);
|
||||
} else if (prev_res !== value) {
|
||||
increment(s);
|
||||
var v_reactions = version.reactions === null ? null : new Set(version.reactions);
|
||||
var needs_version_increase = v_reactions === null || !((_a = s.reactions) == null ? void 0 : _a.every(
|
||||
(r) => (
|
||||
/** @type {NonNullable<typeof v_reactions>} */
|
||||
v_reactions.has(r)
|
||||
)
|
||||
));
|
||||
if (needs_version_increase) {
|
||||
increment(version);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/** @param {K} key */
|
||||
delete(key) {
|
||||
var sources = __privateGet(this, _sources2);
|
||||
var s = sources.get(key);
|
||||
var res = super.delete(key);
|
||||
if (s !== void 0) {
|
||||
sources.delete(key);
|
||||
set(__privateGet(this, _size2), super.size);
|
||||
set(s, -1);
|
||||
increment(__privateGet(this, _version2));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
clear() {
|
||||
if (super.size === 0) {
|
||||
return;
|
||||
}
|
||||
super.clear();
|
||||
var sources = __privateGet(this, _sources2);
|
||||
set(__privateGet(this, _size2), 0);
|
||||
for (var s of sources.values()) {
|
||||
set(s, -1);
|
||||
}
|
||||
increment(__privateGet(this, _version2));
|
||||
sources.clear();
|
||||
}
|
||||
keys() {
|
||||
get(__privateGet(this, _version2));
|
||||
return super.keys();
|
||||
}
|
||||
values() {
|
||||
__privateMethod(this, _SvelteMap_instances, read_all_fn).call(this);
|
||||
return super.values();
|
||||
}
|
||||
entries() {
|
||||
__privateMethod(this, _SvelteMap_instances, read_all_fn).call(this);
|
||||
return super.entries();
|
||||
}
|
||||
[Symbol.iterator]() {
|
||||
return this.entries();
|
||||
}
|
||||
get size() {
|
||||
get(__privateGet(this, _size2));
|
||||
return super.size;
|
||||
}
|
||||
};
|
||||
_sources2 = new WeakMap();
|
||||
_version2 = new WeakMap();
|
||||
_size2 = new WeakMap();
|
||||
_update_version2 = new WeakMap();
|
||||
_SvelteMap_instances = new WeakSet();
|
||||
/**
|
||||
* If the source is being created inside the same reaction as the SvelteMap instance,
|
||||
* we use `state` so that it will not be a dependency of the reaction. Otherwise we
|
||||
* use `source` so it will be.
|
||||
*
|
||||
* @template T
|
||||
* @param {T} value
|
||||
* @returns {Source<T>}
|
||||
*/
|
||||
source_fn2 = function(value) {
|
||||
return update_version === __privateGet(this, _update_version2) ? state(value) : source(value);
|
||||
};
|
||||
read_all_fn = function() {
|
||||
get(__privateGet(this, _version2));
|
||||
var sources = __privateGet(this, _sources2);
|
||||
if (__privateGet(this, _size2).v !== sources.size) {
|
||||
for (var key of __superGet(_SvelteMap.prototype, this, "keys").call(this)) {
|
||||
if (!sources.has(key)) {
|
||||
var s = __privateMethod(this, _SvelteMap_instances, source_fn2).call(this, 0);
|
||||
if (true_default) {
|
||||
tag(s, `SvelteMap get(${label(key)})`);
|
||||
}
|
||||
sources.set(key, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
for ([, s] of __privateGet(this, _sources2)) {
|
||||
get(s);
|
||||
}
|
||||
};
|
||||
var SvelteMap = _SvelteMap;
|
||||
|
||||
// node_modules/svelte/src/reactivity/url-search-params.js
|
||||
var REPLACE = Symbol();
|
||||
var _version3, _url, _updating, _SvelteURLSearchParams_instances, update_url_fn;
|
||||
var SvelteURLSearchParams = class extends URLSearchParams {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
__privateAdd(this, _SvelteURLSearchParams_instances);
|
||||
__privateAdd(this, _version3, true_default ? tag(state(0), "SvelteURLSearchParams version") : state(0));
|
||||
__privateAdd(this, _url, get_current_url());
|
||||
__privateAdd(this, _updating, false);
|
||||
}
|
||||
/**
|
||||
* @param {URLSearchParams} params
|
||||
* @internal
|
||||
*/
|
||||
[REPLACE](params) {
|
||||
if (__privateGet(this, _updating)) return;
|
||||
__privateSet(this, _updating, true);
|
||||
for (const key of [...super.keys()]) {
|
||||
super.delete(key);
|
||||
}
|
||||
for (const [key, value] of params) {
|
||||
super.append(key, value);
|
||||
}
|
||||
increment(__privateGet(this, _version3));
|
||||
__privateSet(this, _updating, false);
|
||||
}
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @returns {void}
|
||||
*/
|
||||
append(name, value) {
|
||||
super.append(name, value);
|
||||
__privateMethod(this, _SvelteURLSearchParams_instances, update_url_fn).call(this);
|
||||
increment(__privateGet(this, _version3));
|
||||
}
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string=} value
|
||||
* @returns {void}
|
||||
*/
|
||||
delete(name, value) {
|
||||
var has_value = super.has(name, value);
|
||||
super.delete(name, value);
|
||||
if (has_value) {
|
||||
__privateMethod(this, _SvelteURLSearchParams_instances, update_url_fn).call(this);
|
||||
increment(__privateGet(this, _version3));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {string|null}
|
||||
*/
|
||||
get(name) {
|
||||
get(__privateGet(this, _version3));
|
||||
return super.get(name);
|
||||
}
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getAll(name) {
|
||||
get(__privateGet(this, _version3));
|
||||
return super.getAll(name);
|
||||
}
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string=} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(name, value) {
|
||||
get(__privateGet(this, _version3));
|
||||
return super.has(name, value);
|
||||
}
|
||||
keys() {
|
||||
get(__privateGet(this, _version3));
|
||||
return super.keys();
|
||||
}
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @returns {void}
|
||||
*/
|
||||
set(name, value) {
|
||||
var previous = super.getAll(name).join("");
|
||||
super.set(name, value);
|
||||
if (previous !== super.getAll(name).join("")) {
|
||||
__privateMethod(this, _SvelteURLSearchParams_instances, update_url_fn).call(this);
|
||||
increment(__privateGet(this, _version3));
|
||||
}
|
||||
}
|
||||
sort() {
|
||||
super.sort();
|
||||
__privateMethod(this, _SvelteURLSearchParams_instances, update_url_fn).call(this);
|
||||
increment(__privateGet(this, _version3));
|
||||
}
|
||||
toString() {
|
||||
get(__privateGet(this, _version3));
|
||||
return super.toString();
|
||||
}
|
||||
values() {
|
||||
get(__privateGet(this, _version3));
|
||||
return super.values();
|
||||
}
|
||||
entries() {
|
||||
get(__privateGet(this, _version3));
|
||||
return super.entries();
|
||||
}
|
||||
[Symbol.iterator]() {
|
||||
return this.entries();
|
||||
}
|
||||
get size() {
|
||||
get(__privateGet(this, _version3));
|
||||
return super.size;
|
||||
}
|
||||
};
|
||||
_version3 = new WeakMap();
|
||||
_url = new WeakMap();
|
||||
_updating = new WeakMap();
|
||||
_SvelteURLSearchParams_instances = new WeakSet();
|
||||
update_url_fn = function() {
|
||||
if (!__privateGet(this, _url) || __privateGet(this, _updating)) return;
|
||||
__privateSet(this, _updating, true);
|
||||
const search = this.toString();
|
||||
__privateGet(this, _url).search = search && `?${search}`;
|
||||
__privateSet(this, _updating, false);
|
||||
};
|
||||
|
||||
// node_modules/svelte/src/reactivity/url.js
|
||||
var current_url = null;
|
||||
function get_current_url() {
|
||||
return current_url;
|
||||
}
|
||||
var _protocol, _username, _password, _hostname, _port, _pathname, _hash, _search, _searchParams;
|
||||
var SvelteURL = class extends URL {
|
||||
/**
|
||||
* @param {string | URL} url
|
||||
* @param {string | URL} [base]
|
||||
*/
|
||||
constructor(url, base) {
|
||||
url = new URL(url, base);
|
||||
super(url);
|
||||
__privateAdd(this, _protocol, state(super.protocol));
|
||||
__privateAdd(this, _username, state(super.username));
|
||||
__privateAdd(this, _password, state(super.password));
|
||||
__privateAdd(this, _hostname, state(super.hostname));
|
||||
__privateAdd(this, _port, state(super.port));
|
||||
__privateAdd(this, _pathname, state(super.pathname));
|
||||
__privateAdd(this, _hash, state(super.hash));
|
||||
__privateAdd(this, _search, state(super.search));
|
||||
__privateAdd(this, _searchParams);
|
||||
if (true_default) {
|
||||
tag(__privateGet(this, _protocol), "SvelteURL.protocol");
|
||||
tag(__privateGet(this, _username), "SvelteURL.username");
|
||||
tag(__privateGet(this, _password), "SvelteURL.password");
|
||||
tag(__privateGet(this, _hostname), "SvelteURL.hostname");
|
||||
tag(__privateGet(this, _port), "SvelteURL.port");
|
||||
tag(__privateGet(this, _pathname), "SvelteURL.pathname");
|
||||
tag(__privateGet(this, _hash), "SvelteURL.hash");
|
||||
tag(__privateGet(this, _search), "SvelteURL.search");
|
||||
}
|
||||
current_url = this;
|
||||
__privateSet(this, _searchParams, new SvelteURLSearchParams(url.searchParams));
|
||||
current_url = null;
|
||||
}
|
||||
get hash() {
|
||||
return get(__privateGet(this, _hash));
|
||||
}
|
||||
set hash(value) {
|
||||
super.hash = value;
|
||||
set(__privateGet(this, _hash), super.hash);
|
||||
}
|
||||
get host() {
|
||||
get(__privateGet(this, _hostname));
|
||||
get(__privateGet(this, _port));
|
||||
return super.host;
|
||||
}
|
||||
set host(value) {
|
||||
super.host = value;
|
||||
set(__privateGet(this, _hostname), super.hostname);
|
||||
set(__privateGet(this, _port), super.port);
|
||||
}
|
||||
get hostname() {
|
||||
return get(__privateGet(this, _hostname));
|
||||
}
|
||||
set hostname(value) {
|
||||
super.hostname = value;
|
||||
set(__privateGet(this, _hostname), super.hostname);
|
||||
}
|
||||
get href() {
|
||||
get(__privateGet(this, _protocol));
|
||||
get(__privateGet(this, _username));
|
||||
get(__privateGet(this, _password));
|
||||
get(__privateGet(this, _hostname));
|
||||
get(__privateGet(this, _port));
|
||||
get(__privateGet(this, _pathname));
|
||||
get(__privateGet(this, _hash));
|
||||
get(__privateGet(this, _search));
|
||||
return super.href;
|
||||
}
|
||||
set href(value) {
|
||||
super.href = value;
|
||||
set(__privateGet(this, _protocol), super.protocol);
|
||||
set(__privateGet(this, _username), super.username);
|
||||
set(__privateGet(this, _password), super.password);
|
||||
set(__privateGet(this, _hostname), super.hostname);
|
||||
set(__privateGet(this, _port), super.port);
|
||||
set(__privateGet(this, _pathname), super.pathname);
|
||||
set(__privateGet(this, _hash), super.hash);
|
||||
set(__privateGet(this, _search), super.search);
|
||||
__privateGet(this, _searchParams)[REPLACE](super.searchParams);
|
||||
}
|
||||
get password() {
|
||||
return get(__privateGet(this, _password));
|
||||
}
|
||||
set password(value) {
|
||||
super.password = value;
|
||||
set(__privateGet(this, _password), super.password);
|
||||
}
|
||||
get pathname() {
|
||||
return get(__privateGet(this, _pathname));
|
||||
}
|
||||
set pathname(value) {
|
||||
super.pathname = value;
|
||||
set(__privateGet(this, _pathname), super.pathname);
|
||||
}
|
||||
get port() {
|
||||
return get(__privateGet(this, _port));
|
||||
}
|
||||
set port(value) {
|
||||
super.port = value;
|
||||
set(__privateGet(this, _port), super.port);
|
||||
}
|
||||
get protocol() {
|
||||
return get(__privateGet(this, _protocol));
|
||||
}
|
||||
set protocol(value) {
|
||||
super.protocol = value;
|
||||
set(__privateGet(this, _protocol), super.protocol);
|
||||
}
|
||||
get search() {
|
||||
return get(__privateGet(this, _search));
|
||||
}
|
||||
set search(value) {
|
||||
super.search = value;
|
||||
set(__privateGet(this, _search), value);
|
||||
__privateGet(this, _searchParams)[REPLACE](super.searchParams);
|
||||
}
|
||||
get username() {
|
||||
return get(__privateGet(this, _username));
|
||||
}
|
||||
set username(value) {
|
||||
super.username = value;
|
||||
set(__privateGet(this, _username), super.username);
|
||||
}
|
||||
get origin() {
|
||||
get(__privateGet(this, _protocol));
|
||||
get(__privateGet(this, _hostname));
|
||||
get(__privateGet(this, _port));
|
||||
return super.origin;
|
||||
}
|
||||
get searchParams() {
|
||||
return __privateGet(this, _searchParams);
|
||||
}
|
||||
toString() {
|
||||
return this.href;
|
||||
}
|
||||
toJSON() {
|
||||
return this.href;
|
||||
}
|
||||
};
|
||||
_protocol = new WeakMap();
|
||||
_username = new WeakMap();
|
||||
_password = new WeakMap();
|
||||
_hostname = new WeakMap();
|
||||
_port = new WeakMap();
|
||||
_pathname = new WeakMap();
|
||||
_hash = new WeakMap();
|
||||
_search = new WeakMap();
|
||||
_searchParams = new WeakMap();
|
||||
|
||||
// node_modules/svelte/src/reactivity/media-query.js
|
||||
var parenthesis_regex = /\(.+\)/;
|
||||
var non_parenthesized_keywords = /* @__PURE__ */ new Set(["all", "print", "screen", "and", "or", "not", "only"]);
|
||||
var MediaQuery = class extends ReactiveValue {
|
||||
/**
|
||||
* @param {string} query A media query string
|
||||
* @param {boolean} [fallback] Fallback value for the server
|
||||
*/
|
||||
constructor(query, fallback) {
|
||||
let final_query = parenthesis_regex.test(query) || // we need to use `some` here because technically this `window.matchMedia('random,screen')` still returns true
|
||||
query.split(/[\s,]+/).some((keyword) => non_parenthesized_keywords.has(keyword.trim())) ? query : `(${query})`;
|
||||
const q = window.matchMedia(final_query);
|
||||
super(
|
||||
() => q.matches,
|
||||
(update) => on(q, "change", update)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
SvelteDate,
|
||||
SvelteSet,
|
||||
SvelteMap,
|
||||
SvelteURLSearchParams,
|
||||
SvelteURL,
|
||||
MediaQuery
|
||||
};
|
||||
//# sourceMappingURL=chunk-EZEETTA4.js.map
|
||||
-7
File diff suppressed because one or more lines are too long
-4841
File diff suppressed because it is too large
Load Diff
-7
File diff suppressed because one or more lines are too long
-37
@@ -1,37 +0,0 @@
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __reflectGet = Reflect.get;
|
||||
var __typeError = (msg) => {
|
||||
throw TypeError(msg);
|
||||
};
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
||||
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
||||
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
||||
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
||||
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
||||
var __privateWrapper = (obj, member, setter, getter) => ({
|
||||
set _(value) {
|
||||
__privateSet(obj, member, value, setter);
|
||||
},
|
||||
get _() {
|
||||
return __privateGet(obj, member, getter);
|
||||
}
|
||||
});
|
||||
var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj);
|
||||
|
||||
export {
|
||||
__export,
|
||||
__publicField,
|
||||
__privateGet,
|
||||
__privateAdd,
|
||||
__privateSet,
|
||||
__privateMethod,
|
||||
__privateWrapper,
|
||||
__superGet
|
||||
};
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@ import {
|
||||
clsx,
|
||||
clsx_default
|
||||
} from "./chunk-U7P2NEEE.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
clsx,
|
||||
clsx_default as default
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/devalue/src/utils.js
|
||||
var escaped = {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import {
|
||||
false_default,
|
||||
true_default
|
||||
} from "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
true_default as BROWSER,
|
||||
true_default as DEV,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/pocketbase/dist/pocketbase.es.mjs
|
||||
var ClientResponseError = class _ClientResponseError extends Error {
|
||||
|
||||
+5
-5
@@ -7,13 +7,13 @@ import {
|
||||
hydratable,
|
||||
onDestroy,
|
||||
onMount
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import {
|
||||
hydrate,
|
||||
mount,
|
||||
unmount
|
||||
} from "./chunk-57IKBECK.js";
|
||||
} from "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import {
|
||||
createContext,
|
||||
flushSync,
|
||||
@@ -25,11 +25,11 @@ import {
|
||||
settled,
|
||||
tick,
|
||||
untrack
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
afterUpdate,
|
||||
beforeUpdate,
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import {
|
||||
clsx,
|
||||
clsx_default
|
||||
} from "./chunk-U7P2NEEE.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
clsx,
|
||||
clsx_default as default
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
cubicOut
|
||||
} from "./chunk-YERFD2CZ.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/svelte/src/animate/index.js
|
||||
function flip(node, { from, to }, params = {}) {
|
||||
|
||||
+4
-4
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
createAttachmentKey,
|
||||
fromAction
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import "./chunk-57IKBECK.js";
|
||||
import "./chunk-JC3VXQM7.js";
|
||||
import "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
createAttachmentKey,
|
||||
fromAction
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ import {
|
||||
sineInOut,
|
||||
sineOut
|
||||
} from "./chunk-YERFD2CZ.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
backIn,
|
||||
backInOut,
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import "./chunk-7RQDXF5S.js";
|
||||
import {
|
||||
on
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
on
|
||||
};
|
||||
|
||||
+5
-5
@@ -107,8 +107,7 @@ import {
|
||||
validate_store,
|
||||
validate_void_dynamic_element,
|
||||
wrap_snippet
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import {
|
||||
append,
|
||||
comment,
|
||||
@@ -126,7 +125,8 @@ import {
|
||||
text,
|
||||
trusted,
|
||||
with_script
|
||||
} from "./chunk-57IKBECK.js";
|
||||
} from "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import {
|
||||
$document,
|
||||
$window,
|
||||
@@ -200,11 +200,11 @@ import {
|
||||
user_effect,
|
||||
user_pre_effect,
|
||||
wait
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
CLASS,
|
||||
FILENAME,
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
import "./chunk-2LGM3QYM.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
enable_async_mode_flag
|
||||
} from "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/svelte/src/internal/flags/async.js
|
||||
enable_async_mode_flag();
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
import "./chunk-MZVN5SDE.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
enable_tracing_mode_flag
|
||||
} from "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/svelte/src/internal/flags/tracing.js
|
||||
enable_tracing_mode_flag();
|
||||
|
||||
+3
-3
@@ -12,12 +12,12 @@ import {
|
||||
stopImmediatePropagation,
|
||||
stopPropagation,
|
||||
trusted
|
||||
} from "./chunk-57IKBECK.js";
|
||||
import "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
asClassComponent,
|
||||
createBubbler,
|
||||
|
||||
+9
-9
@@ -1,18 +1,15 @@
|
||||
import {
|
||||
MediaQuery
|
||||
} from "./chunk-EZEETTA4.js";
|
||||
import "./chunk-3TACVO2P.js";
|
||||
} from "./chunk-N4HEU72H.js";
|
||||
import "./chunk-MPD7DSJG.js";
|
||||
import "./chunk-7RQDXF5S.js";
|
||||
import {
|
||||
linear
|
||||
} from "./chunk-YERFD2CZ.js";
|
||||
import {
|
||||
loop,
|
||||
raf,
|
||||
writable
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import "./chunk-57IKBECK.js";
|
||||
import {
|
||||
deferred,
|
||||
get,
|
||||
@@ -21,18 +18,21 @@ import {
|
||||
set,
|
||||
state,
|
||||
tag
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import {
|
||||
true_default
|
||||
} from "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import {
|
||||
linear
|
||||
} from "./chunk-YERFD2CZ.js";
|
||||
import {
|
||||
__privateAdd,
|
||||
__privateGet,
|
||||
__privateMethod,
|
||||
__privateSet
|
||||
} from "./chunk-X4VJQ2O3.js";
|
||||
} from "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/svelte/src/motion/utils.js
|
||||
function is_date(obj) {
|
||||
|
||||
+6
-6
@@ -5,19 +5,19 @@ import {
|
||||
SvelteSet,
|
||||
SvelteURL,
|
||||
SvelteURLSearchParams
|
||||
} from "./chunk-EZEETTA4.js";
|
||||
import "./chunk-3TACVO2P.js";
|
||||
} from "./chunk-N4HEU72H.js";
|
||||
import "./chunk-MPD7DSJG.js";
|
||||
import "./chunk-7RQDXF5S.js";
|
||||
import "./chunk-EXK7SDUS.js";
|
||||
import "./chunk-NTAG77ZX.js";
|
||||
import "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import "./chunk-57IKBECK.js";
|
||||
import {
|
||||
createSubscriber
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
MediaQuery,
|
||||
SvelteDate,
|
||||
|
||||
+5
-5
@@ -1,17 +1,17 @@
|
||||
import {
|
||||
ReactiveValue
|
||||
} from "./chunk-3TACVO2P.js";
|
||||
} from "./chunk-MPD7DSJG.js";
|
||||
import "./chunk-7RQDXF5S.js";
|
||||
import "./chunk-EXK7SDUS.js";
|
||||
import "./chunk-NTAG77ZX.js";
|
||||
import "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import "./chunk-57IKBECK.js";
|
||||
import {
|
||||
get,
|
||||
on,
|
||||
set,
|
||||
source,
|
||||
tag
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import {
|
||||
true_default
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
__privateAdd,
|
||||
__privateGet,
|
||||
__privateMethod
|
||||
} from "./chunk-X4VJQ2O3.js";
|
||||
} from "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/svelte/src/reactivity/window/index.js
|
||||
var scrollX = new ReactiveValue(
|
||||
|
||||
+4
-4
@@ -4,9 +4,9 @@ import {
|
||||
readable,
|
||||
readonly,
|
||||
writable
|
||||
} from "./chunk-EXK7SDUS.js";
|
||||
} from "./chunk-NTAG77ZX.js";
|
||||
import "./chunk-MKTP2BFS.js";
|
||||
import "./chunk-U7P2NEEE.js";
|
||||
import "./chunk-57IKBECK.js";
|
||||
import {
|
||||
active_effect,
|
||||
active_reaction,
|
||||
@@ -16,11 +16,11 @@ import {
|
||||
render_effect,
|
||||
set_active_effect,
|
||||
set_active_reaction
|
||||
} from "./chunk-JC3VXQM7.js";
|
||||
} from "./chunk-HZ6NBROY.js";
|
||||
import "./chunk-XWATFG4W.js";
|
||||
import "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-OHYQYV5R.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/svelte/src/store/index-client.js
|
||||
function toStore(get2, set) {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import {
|
||||
import {
|
||||
true_default
|
||||
} from "./chunk-HNWPC2PS.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/svelte/src/transition/index.js
|
||||
var linear = (x) => x;
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import {
|
||||
twMerge,
|
||||
validators
|
||||
} from "./chunk-HRPYXN4O.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
export {
|
||||
createTailwindMerge,
|
||||
extendTailwindMerge,
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import {
|
||||
extendTailwindMerge,
|
||||
twMerge
|
||||
} from "./chunk-HRPYXN4O.js";
|
||||
import "./chunk-X4VJQ2O3.js";
|
||||
import "./chunk-LYA4E26T.js";
|
||||
|
||||
// node_modules/tailwind-variants/dist/chunk-LQJYWU4O.js
|
||||
var SPACE_REGEX = /\s+/g;
|
||||
|
||||
+3
-1
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"dev": "vite dev --host 0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
@@ -30,6 +30,8 @@
|
||||
"dependencies": {
|
||||
"barcode-detector-api-polyfill": "^1.0.20",
|
||||
"clsx": "^2.1.1",
|
||||
"heic-convert": "^2.1.0",
|
||||
"heic2any": "^0.0.4",
|
||||
"pocketbase": "^0.26.0",
|
||||
"tailwind-merge": "^3.0.0"
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='4' fill='%231e293b'/><text x='16' y='22' font-size='18' font-family='system-ui' fill='%2394a3b8' text-anchor='middle'>S</text></svg>" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#0f172a" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Stackq" />
|
||||
|
||||
+47
-9
@@ -1,8 +1,9 @@
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
import PocketBase from "pocketbase";
|
||||
import type { Handle } from "@sveltejs/kit";
|
||||
import type { Handle, HandleServerError } from "@sveltejs/kit";
|
||||
import { POCKETBASE_BASE_URL } from "$lib/pocketbase";
|
||||
|
||||
const POCKETBASE_URL = "https://pocketbase.ccllc.pro";
|
||||
const BASE_SSO_URL = process.env.BASE_SSO_URL ?? "https://base.ccllc.pro";
|
||||
const LOGIN_PATH = "/login";
|
||||
const LOGOUT_PATH = "/logout";
|
||||
|
||||
@@ -15,7 +16,32 @@ function isPublicRoute(pathname: string): boolean {
|
||||
}
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
event.locals.pb = new PocketBase(POCKETBASE_URL);
|
||||
// SSO: if opened from Base with ?sso=token, exchange for session and redirect
|
||||
const ssoParam = event.url.searchParams.get("sso");
|
||||
if (ssoParam) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${BASE_SSO_URL}/api/sso/exchange?token=${encodeURIComponent(ssoParam)}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const auth = await res.json();
|
||||
const cookieVal = encodeURIComponent(
|
||||
JSON.stringify({ token: auth.token, model: auth.model })
|
||||
);
|
||||
const setCookie = `pb_auth=${cookieVal}; Path=/; SameSite=Lax; HttpOnly; Max-Age=604800`;
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: { Location: event.url.pathname, "Set-Cookie": setCookie },
|
||||
});
|
||||
} else {
|
||||
console.error("[SSO] Exchange failed:", res.status, await res.text().catch(() => ""));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[SSO] Exchange error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
event.locals.pb = new PocketBase(POCKETBASE_BASE_URL);
|
||||
event.locals.pb.authStore.loadFromCookie(event.request.headers.get("cookie") || "");
|
||||
|
||||
try {
|
||||
@@ -37,10 +63,22 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
throw redirect(303, loginUrl);
|
||||
}
|
||||
|
||||
const response = await resolve(event);
|
||||
response.headers.append(
|
||||
"set-cookie",
|
||||
event.locals.pb.authStore.exportToCookie({ httpOnly: false, sameSite: "Lax" })
|
||||
);
|
||||
return response;
|
||||
// Set PocketBase auth cookie via SvelteKit so we don't mutate the response (avoids 502)
|
||||
const { token, record } = event.locals.pb.authStore;
|
||||
if (token || record) {
|
||||
event.cookies.set("pb_auth", JSON.stringify({ token: token ?? "", model: record ?? null }), {
|
||||
path: "/",
|
||||
sameSite: "lax",
|
||||
maxAge: 604800,
|
||||
httpOnly: false,
|
||||
});
|
||||
}
|
||||
|
||||
return resolve(event);
|
||||
};
|
||||
|
||||
export const handleError: HandleServerError = ({ error, event }) => {
|
||||
console.error("[handleError]", event.url.pathname, error);
|
||||
const message = import.meta.env?.DEV ? String(error) : "Something went wrong.";
|
||||
return { message };
|
||||
};
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
onResult,
|
||||
title = "Scan barcode",
|
||||
}: {
|
||||
open?: boolean;
|
||||
onResult?: (value: string) => void;
|
||||
title?: string;
|
||||
} = $props();
|
||||
|
||||
let scanError = $state<string | null>(null);
|
||||
let searchInput = $state("");
|
||||
let videoEl = $state<HTMLVideoElement | undefined>(undefined);
|
||||
let stream = $state<MediaStream | null>(null);
|
||||
let scanRAF = $state(0);
|
||||
|
||||
function submitSearch() {
|
||||
const value = searchInput.trim();
|
||||
if (!value) return;
|
||||
stopScan();
|
||||
open = false;
|
||||
searchInput = "";
|
||||
onResult?.(value);
|
||||
}
|
||||
|
||||
async function ensureBarcodeDetector(): Promise<boolean> {
|
||||
if (typeof globalThis === "undefined" || "BarcodeDetector" in globalThis) return true;
|
||||
try {
|
||||
const mod = await import("barcode-detector-api-polyfill");
|
||||
(globalThis as unknown as { BarcodeDetector: typeof BarcodeDetector }).BarcodeDetector =
|
||||
mod.BarcodeDetector;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function startScan() {
|
||||
scanError = null;
|
||||
const hasDetector = await ensureBarcodeDetector();
|
||||
if (!hasDetector) {
|
||||
scanError = "Failed to load barcode scanner. Try again or use Chrome/Edge.";
|
||||
return;
|
||||
}
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: { facingMode: "environment", width: { ideal: 1280 }, height: { ideal: 720 } },
|
||||
});
|
||||
} catch (e) {
|
||||
scanError = e instanceof Error ? e.message : "Could not access camera.";
|
||||
}
|
||||
}
|
||||
|
||||
function stopScan() {
|
||||
if (scanRAF) {
|
||||
cancelAnimationFrame(scanRAF);
|
||||
scanRAF = 0;
|
||||
}
|
||||
if (stream) {
|
||||
stream.getTracks().forEach((t) => t.stop());
|
||||
stream = null;
|
||||
}
|
||||
if (videoEl?.srcObject) {
|
||||
videoEl.srcObject = null;
|
||||
}
|
||||
scanError = null;
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!open) {
|
||||
stopScan();
|
||||
searchInput = "";
|
||||
return;
|
||||
}
|
||||
startScan();
|
||||
return () => stopScan();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!open || !stream || !videoEl) return;
|
||||
videoEl.srcObject = stream;
|
||||
videoEl.play().catch(() => {});
|
||||
return () => {
|
||||
if (videoEl?.srcObject) videoEl.srcObject = null;
|
||||
};
|
||||
});
|
||||
|
||||
const SCAN_INTERVAL_MS = 300;
|
||||
|
||||
function scanLoop() {
|
||||
if (!videoEl || !("BarcodeDetector" in globalThis)) return;
|
||||
const detector = new (globalThis as unknown as { BarcodeDetector: typeof BarcodeDetector }).BarcodeDetector();
|
||||
let lastScan = 0;
|
||||
async function detect() {
|
||||
if (!open || !videoEl) return;
|
||||
if (videoEl.readyState < 2) {
|
||||
scanRAF = requestAnimationFrame(detect);
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
if (now - lastScan >= SCAN_INTERVAL_MS) {
|
||||
lastScan = now;
|
||||
try {
|
||||
const barcodes = await detector.detect(videoEl);
|
||||
if (barcodes.length > 0) {
|
||||
const value = barcodes[0].rawValue?.trim() ?? "";
|
||||
stopScan();
|
||||
open = false;
|
||||
onResult?.(value);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
scanRAF = requestAnimationFrame(detect);
|
||||
}
|
||||
detect();
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!open || !stream || !videoEl) return;
|
||||
scanLoop();
|
||||
return () => {
|
||||
if (scanRAF) cancelAnimationFrame(scanRAF);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content
|
||||
class="flex max-h-[100dvh] w-[100vw] max-w-none flex-col gap-4 rounded-none border-0 p-4 sm:max-h-[90dvh] sm:w-auto sm:max-w-md sm:rounded-lg sm:border"
|
||||
onclose={() => stopScan()}
|
||||
>
|
||||
<Dialog.Header class="shrink-0">
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
<Dialog.Description>Scan a barcode or type a code to look up an item.</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<div class="flex min-h-0 flex-1 flex-col gap-4">
|
||||
<div class="flex shrink-0 gap-2">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Type barcode or SKU to search…"
|
||||
bind:value={searchInput}
|
||||
onkeydown={(e) => e.key === "Enter" && submitSearch()}
|
||||
class="min-h-11 flex-1"
|
||||
/>
|
||||
<Button type="button" class="min-h-11 shrink-0" onclick={submitSearch} disabled={!searchInput.trim()}>
|
||||
Search
|
||||
</Button>
|
||||
</div>
|
||||
<p class="text-muted-foreground shrink-0 text-center text-xs">or use camera below</p>
|
||||
{#if scanError}
|
||||
<p class="text-destructive text-sm">{scanError}</p>
|
||||
{:else if stream}
|
||||
<video
|
||||
bind:this={videoEl}
|
||||
class="border-input max-h-[50vh] w-full flex-1 rounded-lg border object-cover sm:max-h-none"
|
||||
muted
|
||||
playsinline
|
||||
></video>
|
||||
{:else}
|
||||
<p class="text-muted-foreground text-sm">Starting camera…</p>
|
||||
{/if}
|
||||
<div class="flex shrink-0 gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
class="min-h-12 flex-1"
|
||||
onclick={() => { stopScan(); open = false; }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
import type { Snippet } from "svelte";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: CollapsiblePrimitive.ContentProps & HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Content
|
||||
bind:ref
|
||||
class={cn("overflow-hidden", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</CollapsiblePrimitive.Content>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
import type { Snippet } from "svelte";
|
||||
import type { HTMLButtonAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: CollapsiblePrimitive.TriggerProps & HTMLButtonAttributes<HTMLButtonElement> & { children?: Snippet } = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Trigger bind:ref class={cn("flex items-center gap-2", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</CollapsiblePrimitive.Trigger>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { Snippet } from "svelte";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: CollapsiblePrimitive.RootProps & HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Root bind:open class={cn(className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</CollapsiblePrimitive.Root>
|
||||
@@ -0,0 +1,5 @@
|
||||
import Root from "./collapsible.svelte";
|
||||
import Content from "./collapsible-content.svelte";
|
||||
import Trigger from "./collapsible-trigger.svelte";
|
||||
|
||||
export { Root, Content, Trigger, Root as Collapsible, Content as CollapsibleContent, Trigger as CollapsibleTrigger };
|
||||
@@ -1,34 +0,0 @@
|
||||
/** UOM options for Stackq_Items (single select). */
|
||||
export const UOM_OPTIONS = ["EA", "BX", "CA", "PK", "PLT", "LF"] as const;
|
||||
|
||||
/** Catagory options (single select in schema). */
|
||||
export const CATAGORY_OPTIONS = [
|
||||
"Framing",
|
||||
"Insulation",
|
||||
"Drywall",
|
||||
"T-Bar",
|
||||
"Painting",
|
||||
"Masking",
|
||||
"Misc",
|
||||
] as const;
|
||||
|
||||
/** Sub_Catagory options (single select in schema). */
|
||||
export const SUB_CATAGORY_OPTIONS = [
|
||||
"ACT",
|
||||
"Channel",
|
||||
"Fasteners",
|
||||
"Finishing Materials",
|
||||
"Adhesives",
|
||||
"Mud",
|
||||
"Corner Bead",
|
||||
"Sanding",
|
||||
"Tape",
|
||||
"Metal Studs",
|
||||
"Metal Tracks",
|
||||
"FRP",
|
||||
"Gyp",
|
||||
"Insulation",
|
||||
"Paint",
|
||||
"Weatherproofing",
|
||||
"Misc",
|
||||
] as const;
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Server-side HEIC conversion using heic-convert (Node). Use in form actions only.
|
||||
*/
|
||||
|
||||
export function isHeicFile(file: File): boolean {
|
||||
const name = file.name.toLowerCase();
|
||||
const type = (file.type ?? "").toLowerCase();
|
||||
return (
|
||||
name.endsWith(".heic") ||
|
||||
name.endsWith(".heif") ||
|
||||
type === "image/heic" ||
|
||||
type === "image/heif"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the file is HEIC/HEIF, convert to JPEG and return a new File. Otherwise return the original.
|
||||
*/
|
||||
export async function convertHeicFileToJpegIfNeeded(file: File): Promise<File> {
|
||||
if (!isHeicFile(file)) return file;
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const inputBuffer = Buffer.from(arrayBuffer);
|
||||
const convert = (await import("heic-convert")).default as (opts: {
|
||||
buffer: Buffer;
|
||||
format: string;
|
||||
quality?: number;
|
||||
}) => Promise<Buffer>;
|
||||
const outputBuffer = await convert({
|
||||
buffer: inputBuffer,
|
||||
format: "JPEG",
|
||||
quality: 0.92,
|
||||
});
|
||||
const baseName = file.name.replace(/\.[^.]+$/i, "") || "image";
|
||||
return new File([outputBuffer], `${baseName}.jpg`, {
|
||||
type: "image/jpeg",
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
function isHeifConsoleMessage(msg: string): boolean {
|
||||
return /HEIF|parse HEIF|Could not parse/i.test(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HEIC/HEIF blob to JPEG. Uses heic2any only when this function is called
|
||||
* (dynamic import), so the receipt page can load even if heic2any fails to resolve.
|
||||
* Suppresses the library's "Could not parse HEIF file" console output during conversion.
|
||||
*/
|
||||
export async function convertHeicBlobToJpeg(blob: Blob): Promise<Blob> {
|
||||
const originalError = console.error;
|
||||
const originalWarn = console.warn;
|
||||
console.error = (...args: unknown[]) => {
|
||||
const msg = args[0]?.toString?.() ?? "";
|
||||
if (isHeifConsoleMessage(msg)) return;
|
||||
originalError.apply(console, args);
|
||||
};
|
||||
console.warn = (...args: unknown[]) => {
|
||||
const msg = args[0]?.toString?.() ?? "";
|
||||
if (isHeifConsoleMessage(msg)) return;
|
||||
originalWarn.apply(console, args);
|
||||
};
|
||||
try {
|
||||
const mod = await import(/* @vite-ignore */ "heic2any");
|
||||
const heic2any = (mod as unknown as {
|
||||
default: (opts: { blob: Blob; toType: string; quality?: number }) => Promise<Blob | Blob[]>;
|
||||
}).default;
|
||||
const out = await heic2any({ blob, toType: "image/jpeg", quality: 0.92 });
|
||||
return Array.isArray(out) ? out[0] : out;
|
||||
} finally {
|
||||
console.error = originalError;
|
||||
console.warn = originalWarn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { convertHeicBlobToJpeg } from "./convert-heic";
|
||||
|
||||
export function isHeicFile(file: File): boolean {
|
||||
const name = file.name.toLowerCase();
|
||||
const type = (file.type ?? "").toLowerCase();
|
||||
return (
|
||||
name.endsWith(".heic") ||
|
||||
name.endsWith(".heif") ||
|
||||
type === "image/heic" ||
|
||||
type === "image/heif"
|
||||
);
|
||||
}
|
||||
|
||||
/** True if the file is an image we can process (image/* or HEIC by type/extension). */
|
||||
export function isAcceptableImageFile(file: File | null): boolean {
|
||||
if (!file) return false;
|
||||
const type = (file.type ?? "").toLowerCase();
|
||||
return type.startsWith("image/") || isHeicFile(file);
|
||||
}
|
||||
|
||||
function makeOutputFile(blob: Blob, baseName: string, ext: string): File {
|
||||
return new File([blob], `${baseName}.${ext}`, {
|
||||
type: blob.type,
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
function isSafari(): boolean {
|
||||
if (typeof navigator === "undefined") return false;
|
||||
const ua = navigator.userAgent;
|
||||
return ua.includes("Safari") && !ua.includes("Chrome");
|
||||
}
|
||||
|
||||
/** Load blob in Image, draw to canvas, encode as WebP or JPEG. Rejects if image fails to load or encode. */
|
||||
function tryCanvas(sourceBlob: Blob, baseName: string, quality: number): Promise<File> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = URL.createObjectURL(sourceBlob);
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = img.naturalWidth;
|
||||
canvas.height = img.naturalHeight;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) {
|
||||
reject(new Error("Could not get canvas context"));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
// Safari doesn't support WebP in toBlob; try WebP first, fall back to JPEG
|
||||
canvas.toBlob(
|
||||
(blob) => {
|
||||
if (blob && blob.type === "image/webp") {
|
||||
resolve(makeOutputFile(blob, baseName, "webp"));
|
||||
return;
|
||||
}
|
||||
canvas.toBlob(
|
||||
(jpegBlob) => {
|
||||
if (!jpegBlob) {
|
||||
reject(new Error("Failed to encode image"));
|
||||
return;
|
||||
}
|
||||
resolve(makeOutputFile(jpegBlob, baseName, "jpg"));
|
||||
},
|
||||
"image/jpeg",
|
||||
quality
|
||||
);
|
||||
},
|
||||
"image/webp",
|
||||
quality
|
||||
);
|
||||
};
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
reject(new Error("Failed to load image"));
|
||||
};
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an image File to WebP (or JPEG on Safari where WebP isn't supported).
|
||||
* HEIC: Safari uses native Image() first, then heic2any fallback; other browsers use heic2any first, then Image() fallback.
|
||||
*/
|
||||
export async function convertImageToWebP(file: File, quality = 0.85): Promise<File> {
|
||||
const baseName = file.name.replace(/\.[^.]+$/i, "") || "image";
|
||||
|
||||
if (isHeicFile(file)) {
|
||||
const safari = isSafari();
|
||||
if (safari) {
|
||||
// Safari can decode HEIC in Image(); heic2any is unreliable on Safari (e.g. transparent output)
|
||||
try {
|
||||
return await tryCanvas(file, baseName, quality);
|
||||
} catch {
|
||||
try {
|
||||
const jpegBlob = await convertHeicBlobToJpeg(file);
|
||||
return await tryCanvas(jpegBlob, baseName, quality);
|
||||
} catch (e) {
|
||||
throw e instanceof Error ? e : new Error("Failed to convert HEIC");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Non-Safari: Image() usually can't decode HEIC; use heic2any first
|
||||
try {
|
||||
const jpegBlob = await convertHeicBlobToJpeg(file);
|
||||
return await tryCanvas(jpegBlob, baseName, quality);
|
||||
} catch {
|
||||
try {
|
||||
return await tryCanvas(file, baseName, quality);
|
||||
} catch (e) {
|
||||
throw e instanceof Error ? e : new Error("Failed to convert HEIC");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await tryCanvas(file, baseName, quality);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Parse form values. Used by receipts server actions.
|
||||
*/
|
||||
|
||||
export function parseNum(val: unknown): number | undefined {
|
||||
if (val === "" || val === null || val === undefined) return undefined;
|
||||
const n = Number(val);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
}
|
||||
|
||||
/** Like parseNum but return a number (default when empty/invalid). */
|
||||
export function parseNumWithDefault(val: unknown, defaultVal: number): number {
|
||||
const n = parseNum(val);
|
||||
return n ?? defaultVal;
|
||||
}
|
||||
|
||||
export function parseBool(val: unknown): boolean {
|
||||
return val === "on" || val === "true" || val === true;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/** PocketBase Stackq_Items collection record (and expanded relation). */
|
||||
export interface StackqItem {
|
||||
id: string;
|
||||
Item: string;
|
||||
SKU: string;
|
||||
Description: string;
|
||||
UOM: string;
|
||||
Cost: number;
|
||||
Vendor: string;
|
||||
Catagory: string | string[];
|
||||
Sub_Catagory: string | string[];
|
||||
Has_Parent: boolean;
|
||||
Parent: string; // relation id
|
||||
Stock: boolean;
|
||||
Image?: string; // file filename
|
||||
created: string;
|
||||
updated: string;
|
||||
expand?: {
|
||||
Parent?: { id: string; Item?: string; SKU?: string };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/** Receipt status values for Stackq_Receipts */
|
||||
export const STATUS_OPTIONS = ["New", "Data Entered", "Reviewed", "Consolidated"] as const;
|
||||
export type ReceiptStatus = (typeof STATUS_OPTIONS)[number];
|
||||
|
||||
/** Line item stored in receipt Data.lineItems */
|
||||
export interface ReceiptLineItem {
|
||||
item: string;
|
||||
description: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
}
|
||||
|
||||
/** JSON shape stored in Stackq_Receipts.Data */
|
||||
export interface ReceiptDataShape {
|
||||
lineItems?: ReceiptLineItem[];
|
||||
tax?: number;
|
||||
/** Total printed on receipt (for validation) */
|
||||
receiptTotal?: number;
|
||||
}
|
||||
|
||||
/** PocketBase Stackq_Receipts collection record (and expanded relation). */
|
||||
export interface StackqReceipt {
|
||||
id: string;
|
||||
Status: string;
|
||||
Type: string;
|
||||
Description?: string;
|
||||
User: string;
|
||||
Image?: string;
|
||||
Data?: ReceiptDataShape | Record<string, unknown>;
|
||||
created: string;
|
||||
updated: string;
|
||||
expand?: {
|
||||
User?: { id: string; email?: string; name?: string };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/** Allowed MIME types for receipt/item image uploads (server-side check). */
|
||||
const ALLOWED_IMAGE_TYPES = new Set([
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/heic",
|
||||
"image/heif",
|
||||
]);
|
||||
|
||||
/** Max size 10MB. */
|
||||
const MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* Validates an image file for upload. Returns an error message if invalid, or null if valid/no file.
|
||||
* Use in form actions before sending the file to PocketBase.
|
||||
*/
|
||||
export function validateImageUpload(file: File | null): string | null {
|
||||
if (!file || file.size === 0) return null;
|
||||
const type = (file.type ?? "").toLowerCase();
|
||||
const name = file.name.toLowerCase();
|
||||
const isHeic =
|
||||
type === "image/heic" ||
|
||||
type === "image/heif" ||
|
||||
name.endsWith(".heic") ||
|
||||
name.endsWith(".heif");
|
||||
if (!isHeic && !ALLOWED_IMAGE_TYPES.has(type)) {
|
||||
return "Invalid image type. Use JPEG, PNG, WebP, or HEIC.";
|
||||
}
|
||||
if (file.size > MAX_IMAGE_BYTES) {
|
||||
return `Image must be under ${MAX_IMAGE_BYTES / 1024 / 1024}MB.`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { enhance } from '$app/forms';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
@@ -11,6 +12,7 @@
|
||||
|
||||
let { data, children }: { data: { user: { id: string; email?: string; name?: string; darkmode: boolean } | null }; children?: import('svelte').Snippet } = $props();
|
||||
|
||||
const isReceiptDetailFull = $derived(!!$page?.url?.pathname?.match(/^\/receipts\/[^/]+$/));
|
||||
let darkmode = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
@@ -38,7 +40,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="app-shell">
|
||||
<div class="app-shell" class:receipt-detail-full={isReceiptDetailFull}>
|
||||
{#if data.user}
|
||||
<header
|
||||
class="fixed right-0 top-0 z-50 flex items-center gap-2 p-2 pr-[max(0.5rem,env(safe-area-inset-right))] pt-[max(0.5rem,env(safe-area-inset-top))]"
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
import type { StackqItem } from "$lib/types/items";
|
||||
|
||||
const COLLECTION = "Stackq_Items";
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
const list = await locals.pb
|
||||
.collection(COLLECTION)
|
||||
.getFullList<StackqItem>({ sort: "-created", expand: "Parent" })
|
||||
.catch(() => [] as StackqItem[]);
|
||||
|
||||
const itemsForParent = list.map((i) => ({ id: i.id, Item: i.Item, SKU: i.SKU }));
|
||||
return { items: list, itemsForParent };
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
|
||||
+34
-38
@@ -1,43 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import ReceiptForm from "./receipts/ReceiptForm.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
let { data }: { data: { items: { id: string; SKU?: string }[]; itemsForParent: { id: string; Item: string; SKU: string }[] } } = $props();
|
||||
let receiptDialogOpen = $state(false);
|
||||
|
||||
function onReceiptSuccess() {
|
||||
receiptDialogOpen = false;
|
||||
goto("/receipts");
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if browser}
|
||||
{#await import("./HomeWithDialog.svelte")}
|
||||
<main class="flex min-h-0 flex-1 flex-col items-center justify-center gap-6 p-6">
|
||||
<p class="text-muted-foreground">Loading…</p>
|
||||
</main>
|
||||
{:then { default: HomeWithDialog }}
|
||||
<HomeWithDialog data={data} />
|
||||
{/await}
|
||||
{:else}
|
||||
<main class="flex min-h-0 flex-1 flex-col items-center justify-center gap-6 p-6">
|
||||
<h1 class="text-2xl font-semibold">Stackq</h1>
|
||||
<div class="flex w-full max-w-xs flex-col gap-3">
|
||||
<a href="/items" class="block">
|
||||
<Button class="h-12 w-full min-h-12">Scan</Button>
|
||||
</a>
|
||||
<a href="/transactions" class="block">
|
||||
<Button class="h-12 min-h-12 w-full" variant="outline">New Transaction</Button>
|
||||
</a>
|
||||
<a href="/items" class="block">
|
||||
<Button class="h-12 min-h-12 w-full" variant="outline">New Item</Button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-muted-foreground w-full max-w-xs border-t pt-4 text-center text-sm">Go to</div>
|
||||
<div class="flex w-full max-w-xs flex-col gap-2">
|
||||
<a href="/items" class="block">
|
||||
<Button class="h-12 min-h-12 w-full" variant="outline">Items</Button>
|
||||
</a>
|
||||
<a href="/transactions" class="block">
|
||||
<Button class="h-12 min-h-12 w-full" variant="outline">Transactions</Button>
|
||||
</a>
|
||||
<a href="/accounts" class="block">
|
||||
<Button class="h-12 min-h-12 w-full" variant="outline">Accounts</Button>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
{/if}
|
||||
<main class="flex min-h-0 flex-1 flex-col items-center justify-center gap-6 p-6">
|
||||
<h1 class="text-2xl font-semibold">Stackq</h1>
|
||||
<div class="flex w-full max-w-xs flex-col gap-3">
|
||||
<Button class="h-12 min-h-12 w-full" variant="outline" onclick={() => (receiptDialogOpen = true)}>
|
||||
New receipt
|
||||
</Button>
|
||||
<a href="/receipts" class="block">
|
||||
<Button class="h-12 min-h-12 w-full" variant="outline">Receipts</Button>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Dialog.Root bind:open={receiptDialogOpen}>
|
||||
<Dialog.Content class="flex min-h-0 max-h-[90dvh] w-[calc(100vw-2rem)] max-w-[500px] flex-col overflow-hidden rounded-lg sm:w-full">
|
||||
<Dialog.Header class="shrink-0">
|
||||
<Dialog.Title>New receipt</Dialog.Title>
|
||||
<Dialog.Description>Scan or attach a receipt image. You can add details later.</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<ReceiptForm
|
||||
action="/receipts?/create"
|
||||
submitLabel="Create"
|
||||
onSuccess={onReceiptSuccess}
|
||||
/>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import ItemForm from "./items/ItemForm.svelte";
|
||||
import BarcodeScanDialog from "$lib/components/BarcodeScanDialog.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
let {
|
||||
data,
|
||||
}: {
|
||||
data: {
|
||||
items: { id: string; SKU?: string }[];
|
||||
itemsForParent: { id: string; Item: string; SKU: string }[];
|
||||
};
|
||||
} = $props();
|
||||
|
||||
let dialogOpen = $state(false);
|
||||
let scanDialogOpen = $state(false);
|
||||
const items = $derived(data?.items ?? []);
|
||||
const itemsForParent = $derived(data?.itemsForParent ?? []);
|
||||
|
||||
function onItemSuccess() {
|
||||
dialogOpen = false;
|
||||
goto("/items");
|
||||
}
|
||||
|
||||
function onScanResult(value: string) {
|
||||
const sku = value.trim();
|
||||
const found = items.find((i) => (i.SKU ?? "").trim() === sku);
|
||||
if (found) goto("/items/" + found.id);
|
||||
else goto("/items?q=" + encodeURIComponent(sku));
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="flex min-h-0 flex-1 flex-col items-center justify-center gap-4 p-4 sm:gap-6 sm:p-6">
|
||||
<h1 class="w-full max-w-xs shrink-0 text-center text-2xl font-semibold">Stackq</h1>
|
||||
|
||||
<div class="flex w-full max-w-xs shrink-0 flex-col gap-3">
|
||||
<Button class="h-16 min-h-16 w-full" onclick={() => (scanDialogOpen = true)}>Scan</Button>
|
||||
<a href="/transactions/new" class="block">
|
||||
<Button class="h-16 min-h-16 w-full" variant="outline">New transaction</Button>
|
||||
</a>
|
||||
<a href="/receipts/new" class="block">
|
||||
<Button class="h-16 min-h-16 w-full" variant="outline">New receipt</Button>
|
||||
</a>
|
||||
<Button class="h-16 min-h-16 w-full" variant="outline" onclick={() => (dialogOpen = true)}>New Item</Button>
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-xs shrink-0 border-t pt-4"></div>
|
||||
|
||||
<div class="flex w-full max-w-xs shrink-0 flex-col gap-2">
|
||||
<a href="/items" class="block">
|
||||
<Button class="h-16 min-h-16 w-full" variant="outline">Items</Button>
|
||||
</a>
|
||||
<a href="/transactions" class="block">
|
||||
<Button class="h-16 min-h-16 w-full" variant="outline">Transactions</Button>
|
||||
</a>
|
||||
<a href="/receipts" class="block">
|
||||
<Button class="h-16 min-h-16 w-full" variant="outline">Receipts</Button>
|
||||
</a>
|
||||
<a href="/accounts" class="block">
|
||||
<Button class="h-16 min-h-16 w-full" variant="outline">Accounts</Button>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Dialog.Root bind:open={dialogOpen}>
|
||||
<Dialog.Content class="max-h-[90dvh] w-[calc(100vw-2rem)] max-w-[500px] overflow-y-auto rounded-lg sm:w-full">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>New item</Dialog.Title>
|
||||
<Dialog.Description>Add a new Stackq item.</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<ItemForm
|
||||
itemsForParent={itemsForParent}
|
||||
action="/items?/create"
|
||||
submitLabel="Create"
|
||||
onSuccess={onItemSuccess}
|
||||
/>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
<BarcodeScanDialog bind:open={scanDialogOpen} onResult={onScanResult} title="Scan to look up item" />
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { PageServerLoad } from "./$types";
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return { accounts: [] as { id: string }[] };
|
||||
};
|
||||
@@ -1,75 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Card, CardContent } from "$lib/components/ui/card";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
|
||||
let { data }: { data: { accounts: { id: string }[] } } = $props();
|
||||
let searchQuery = $state("");
|
||||
|
||||
const accounts = $derived(data?.accounts ?? []);
|
||||
const filteredAccounts = $derived(
|
||||
(() => {
|
||||
const q = searchQuery.trim().toLowerCase();
|
||||
if (!q) return accounts;
|
||||
return accounts.filter((a) => a.id.toLowerCase().includes(q));
|
||||
})()
|
||||
);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Accounts – Stackq</title>
|
||||
</svelte:head>
|
||||
|
||||
<main class="container mx-auto flex flex-col gap-6 px-4 py-6 sm:px-6 sm:py-8">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<a href="/" class="text-muted-foreground hover:text-foreground shrink-0 text-sm" title="Back to main">Back</a>
|
||||
<h1 class="text-xl font-semibold truncate sm:text-2xl">Accounts</h1>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:gap-2">
|
||||
<div class="w-full sm:w-64 sm:min-w-0">
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search accounts…"
|
||||
bind:value={searchQuery}
|
||||
class="h-11 w-full sm:h-9"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-muted-foreground text-sm shrink-0">
|
||||
{filteredAccounts.length}{filteredAccounts.length !== accounts.length ? ` of ${accounts.length}` : ""} accounts
|
||||
</span>
|
||||
<a href="/accounts/new" class="block shrink-0">
|
||||
<Button class="w-full sm:w-auto min-h-11">New account</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if filteredAccounts.length === 0}
|
||||
<Card>
|
||||
<CardContent class="flex flex-col items-center justify-center gap-4 py-12">
|
||||
<p class="text-muted-foreground text-center text-sm">
|
||||
{accounts.length === 0 ? "No accounts yet." : "No accounts match your search."}
|
||||
</p>
|
||||
<p class="text-muted-foreground text-center text-xs">
|
||||
{accounts.length === 0 ? "Create an account to get started." : "Try a different search."}
|
||||
</p>
|
||||
<a href="/accounts/new">
|
||||
<Button variant="outline">New account</Button>
|
||||
</a>
|
||||
<a href="/">
|
||||
<Button variant="ghost">Back to home</Button>
|
||||
</a>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each filteredAccounts as account (account.id)}
|
||||
<Card>
|
||||
<CardContent class="p-4">
|
||||
<a href="/accounts/{account.id}" class="font-medium hover:underline">Account {account.id}</a>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
@@ -1,26 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Card, CardContent } from "$lib/components/ui/card";
|
||||
|
||||
let { params }: { params: { id: string } } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Account – Stackq</title>
|
||||
</svelte:head>
|
||||
|
||||
<main class="container mx-auto flex flex-col gap-6 px-4 py-6 sm:px-6 sm:py-8">
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="/accounts" class="text-muted-foreground hover:text-foreground shrink-0 text-sm" title="Back to accounts">Back</a>
|
||||
<h1 class="text-xl font-semibold sm:text-2xl">Account</h1>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent class="flex flex-col gap-4 py-8">
|
||||
<p class="text-muted-foreground text-sm">Account {params.id}</p>
|
||||
<a href="/accounts">
|
||||
<Button variant="outline">Back to accounts</Button>
|
||||
</a>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
@@ -1,29 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Card, CardContent } from "$lib/components/ui/card";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>New account – Stackq</title>
|
||||
</svelte:head>
|
||||
|
||||
<main class="container mx-auto flex flex-col gap-6 px-4 py-6 sm:px-6 sm:py-8">
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="/accounts" class="text-muted-foreground hover:text-foreground shrink-0 text-sm" title="Back to accounts">Back</a>
|
||||
<h1 class="text-xl font-semibold sm:text-2xl">New account</h1>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent class="flex flex-col gap-4 py-8">
|
||||
<p class="text-muted-foreground text-center text-sm">Account creation will be available here.</p>
|
||||
<div class="flex justify-center gap-2">
|
||||
<a href="/accounts">
|
||||
<Button variant="outline">Back to accounts</Button>
|
||||
</a>
|
||||
<a href="/">
|
||||
<Button variant="ghost">Home</Button>
|
||||
</a>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
@@ -0,0 +1,69 @@
|
||||
import { POCKETBASE_BASE_URL } from "$lib/pocketbase";
|
||||
import type { RequestHandler } from "./$types";
|
||||
|
||||
/** Reject path traversal, leading slash, null bytes, and unreasonably long paths. */
|
||||
function isSafeFilePath(path: string): boolean {
|
||||
if (path.length === 0 || path.length > 512) return false;
|
||||
if (path.includes("..") || path.startsWith("/") || path.includes("\0")) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function isHeicPath(path: string): boolean {
|
||||
const lower = path.toLowerCase();
|
||||
return lower.endsWith(".heic") || lower.endsWith(".heif");
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async ({ params, request }) => {
|
||||
const path = params.path;
|
||||
if (!path || !isSafeFilePath(path)) return new Response(null, { status: 404 });
|
||||
|
||||
const url = `${POCKETBASE_BASE_URL}/api/files/${path}`;
|
||||
const cookie = request.headers.get("cookie") ?? "";
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: { cookie },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return new Response(null, { status: res.status });
|
||||
}
|
||||
|
||||
const contentType = res.headers.get("content-type") ?? "application/octet-stream";
|
||||
const body = res.body;
|
||||
if (!body) return new Response(null, { status: 502 });
|
||||
|
||||
if (isHeicPath(path)) {
|
||||
try {
|
||||
const arrayBuffer = await res.arrayBuffer();
|
||||
const inputBuffer = Buffer.from(arrayBuffer);
|
||||
const convert = (await import("heic-convert")).default as (opts: {
|
||||
buffer: Buffer;
|
||||
format: string;
|
||||
quality?: number;
|
||||
}) => Promise<Buffer>;
|
||||
const outputBuffer = await convert({
|
||||
buffer: inputBuffer,
|
||||
format: "JPEG",
|
||||
quality: 0.92,
|
||||
});
|
||||
return new Response(outputBuffer, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "image/jpeg",
|
||||
"cache-control": res.headers.get("cache-control") ?? "private, max-age=3600",
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[api/files] HEIC conversion failed:", path, err);
|
||||
return new Response(null, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": contentType,
|
||||
"cache-control": res.headers.get("cache-control") ?? "private, max-age=3600",
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,132 +0,0 @@
|
||||
import { fail } from "@sveltejs/kit";
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
import type { StackqItem } from "$lib/types/items";
|
||||
|
||||
const COLLECTION = "Stackq_Items";
|
||||
|
||||
function parseNum(val: unknown): number | undefined {
|
||||
if (val === "" || val === null || val === undefined) return undefined;
|
||||
const n = Number(val);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
}
|
||||
|
||||
function parseBool(val: unknown): boolean {
|
||||
return val === "on" || val === "true" || val === true;
|
||||
}
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
const list = await locals.pb
|
||||
.collection(COLLECTION)
|
||||
.getFullList<StackqItem>({
|
||||
sort: "-created",
|
||||
expand: "Parent",
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(COLLECTION, err);
|
||||
return [] as StackqItem[];
|
||||
});
|
||||
|
||||
const itemsForParent = list.map((i) => ({ id: i.id, Item: i.Item, SKU: i.SKU }));
|
||||
|
||||
return { items: list, itemsForParent };
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
create: async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
const Item = (form.get("Item") as string)?.trim() ?? "";
|
||||
const SKU = (form.get("SKU") as string)?.trim() ?? "";
|
||||
const Description = (form.get("Description") as string)?.trim() ?? "";
|
||||
const UOM = (form.get("UOM") as string)?.trim() ?? "";
|
||||
const Cost = parseNum(form.get("Cost"));
|
||||
const Vendor = (form.get("Vendor") as string)?.trim() ?? "";
|
||||
const Catagory = (form.get("Catagory") as string)?.trim() ?? "";
|
||||
const Sub_Catagory = (form.get("Sub_Catagory") as string)?.trim() ?? "";
|
||||
const Has_Parent = parseBool(form.get("Has_Parent"));
|
||||
const Parent = (form.get("Parent") as string)?.trim() ?? "";
|
||||
const Stock = parseBool(form.get("Stock"));
|
||||
const imageFileCreate = form.get("Image") as File | null;
|
||||
const hasImage = imageFileCreate && imageFileCreate.size > 0;
|
||||
|
||||
if (!Item) return fail(400, { error: "Item name is required", form: "create" });
|
||||
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
Item,
|
||||
SKU,
|
||||
Description,
|
||||
UOM: UOM || undefined,
|
||||
Cost: Cost ?? 0,
|
||||
Vendor,
|
||||
Catagory: Catagory || undefined,
|
||||
Sub_Catagory: Sub_Catagory || undefined,
|
||||
Has_Parent: !!Has_Parent,
|
||||
Parent: Parent || null,
|
||||
Stock: !!Stock,
|
||||
};
|
||||
if (hasImage) body.Image = imageFileCreate;
|
||||
await locals.pb.collection(COLLECTION).create(body);
|
||||
} catch (e: unknown) {
|
||||
const message = e && typeof e === "object" && "message" in e ? String((e as { message: string }).message) : "Create failed";
|
||||
return fail(500, { error: message, form: "create" });
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
update: async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
const id = (form.get("id") as string)?.trim();
|
||||
if (!id) return fail(400, { error: "Missing id", form: "update" });
|
||||
|
||||
const Item = (form.get("Item") as string)?.trim() ?? "";
|
||||
const SKU = (form.get("SKU") as string)?.trim() ?? "";
|
||||
const Description = (form.get("Description") as string)?.trim() ?? "";
|
||||
const UOM = (form.get("UOM") as string)?.trim() ?? "";
|
||||
const Cost = parseNum(form.get("Cost"));
|
||||
const Vendor = (form.get("Vendor") as string)?.trim() ?? "";
|
||||
const Catagory = (form.get("Catagory") as string)?.trim() ?? "";
|
||||
const Sub_Catagory = (form.get("Sub_Catagory") as string)?.trim() ?? "";
|
||||
const Has_Parent = parseBool(form.get("Has_Parent"));
|
||||
const Parent = (form.get("Parent") as string)?.trim() ?? "";
|
||||
const Stock = parseBool(form.get("Stock"));
|
||||
const Image = form.get("Image") as File | null;
|
||||
const imageFile = Image && Image.size > 0 ? Image : undefined;
|
||||
|
||||
if (!Item) return fail(400, { error: "Item name is required", form: "update" });
|
||||
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
Item,
|
||||
SKU,
|
||||
Description,
|
||||
UOM: UOM || undefined,
|
||||
Cost: Cost ?? 0,
|
||||
Vendor,
|
||||
Catagory: Catagory || undefined,
|
||||
Sub_Catagory: Sub_Catagory || undefined,
|
||||
Has_Parent: !!Has_Parent,
|
||||
Parent: Parent || null,
|
||||
Stock: !!Stock,
|
||||
};
|
||||
if (imageFile) body.Image = imageFile;
|
||||
await locals.pb.collection(COLLECTION).update(id, body);
|
||||
} catch (e: unknown) {
|
||||
const message = e && typeof e === "object" && "message" in e ? String((e as { message: string }).message) : "Update failed";
|
||||
return fail(500, { error: message, form: "update" });
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
delete: async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
const id = (form.get("id") as string)?.trim();
|
||||
if (!id) return fail(400, { error: "Missing id", form: "delete" });
|
||||
try {
|
||||
await locals.pb.collection(COLLECTION).delete(id);
|
||||
} catch (e: unknown) {
|
||||
const message = e && typeof e === "object" && "message" in e ? String((e as { message: string }).message) : "Delete failed";
|
||||
return fail(500, { error: message, form: "delete" });
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
import { page } from "$app/stores";
|
||||
import { Badge } from "$lib/components/ui/badge";
|
||||
import { Card, CardContent } from "$lib/components/ui/card";
|
||||
import type { StackqItem } from "$lib/types/items";
|
||||
|
||||
let { data }: { data: { items: StackqItem[]; itemsForParent: { id: string; Item: string; SKU: string }[] } } = $props();
|
||||
|
||||
const searchFromUrl = $derived($page.url.searchParams.get("q") ?? "");
|
||||
|
||||
function asList(val: string | string[] | undefined): string[] {
|
||||
if (val == null) return [];
|
||||
return Array.isArray(val) ? val : [val];
|
||||
}
|
||||
|
||||
function formatDate(iso: string) {
|
||||
if (!iso) return "—";
|
||||
try {
|
||||
return new Date(iso).toLocaleDateString(undefined, {
|
||||
dateStyle: "short",
|
||||
timeStyle: "short",
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
|
||||
const items = $derived(data?.items ?? []);
|
||||
const PB_BASE = "https://pocketbase.ccllc.pro";
|
||||
const COLLECTION = "Stackq_Items";
|
||||
function imageUrl(item: StackqItem): string | null {
|
||||
if (!item.Image) return null;
|
||||
return `${PB_BASE}/api/files/${COLLECTION}/${item.id}/${item.Image}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Items – Stackq</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if browser}
|
||||
{#await import("./ItemsCrud.svelte")}
|
||||
<main class="container mx-auto flex flex-col gap-6 px-4 py-8">
|
||||
<p class="text-muted-foreground">Loading…</p>
|
||||
</main>
|
||||
{:then { default: ItemsCrud }}
|
||||
<ItemsCrud data={data} searchFromUrl={searchFromUrl} />
|
||||
{/await}
|
||||
{:else}
|
||||
<main class="container mx-auto flex flex-col gap-6 px-4 py-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl font-semibold">Stackq Items</h1>
|
||||
<span class="text-muted-foreground text-sm">{items.length} items</span>
|
||||
</div>
|
||||
{#if items.length === 0}
|
||||
<Card>
|
||||
<CardContent class="flex h-32 items-center justify-center">
|
||||
<p class="text-muted-foreground text-sm">No items.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each items as item (item.id)}
|
||||
<Card class="overflow-hidden">
|
||||
<CardContent class="flex flex-row items-center gap-3 p-3">
|
||||
<div class="size-12 shrink-0 overflow-hidden rounded border">
|
||||
{#if imageUrl(item)}
|
||||
<img src={imageUrl(item)!} alt="" class="size-full object-cover" loading="lazy" />
|
||||
{:else}
|
||||
<span class="text-muted-foreground flex size-full items-center justify-center text-xs">—</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<a href="/items/{item.id}" class="font-medium hover:underline">{item.Item ?? "—"}</a>
|
||||
<span class="text-muted-foreground font-mono text-xs"> · {item.SKU ?? "—"}</span>
|
||||
{#if item.Description}
|
||||
<span class="text-muted-foreground ml-1 truncate text-xs">· {item.Description}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex shrink-0 items-center gap-2 text-xs">
|
||||
<Badge variant="outline">{item.UOM ?? "—"}</Badge>
|
||||
<span class="tabular-nums">{item.Cost != null ? Number(item.Cost).toLocaleString() : "—"}</span>
|
||||
{#if item.Stock}<Badge variant="default" class="text-xs">Stock</Badge>{/if}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
{/if}
|
||||
@@ -1,322 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { enhance } from "$app/forms";
|
||||
import { POCKETBASE_BASE_URL } from "$lib/pocketbase";
|
||||
import { UOM_OPTIONS, CATAGORY_OPTIONS, SUB_CATAGORY_OPTIONS } from "$lib/constants/items";
|
||||
import type { StackqItem } from "$lib/types/items";
|
||||
|
||||
const COLLECTION = "Stackq_Items";
|
||||
function itemImageUrl(recordId: string, filename: string): string {
|
||||
return `${POCKETBASE_BASE_URL}/api/files/${COLLECTION}/${recordId}/${filename}`;
|
||||
}
|
||||
|
||||
let {
|
||||
item = null,
|
||||
itemsForParent = [],
|
||||
action,
|
||||
submitLabel,
|
||||
error,
|
||||
onSuccess,
|
||||
}: {
|
||||
item?: StackqItem | null;
|
||||
itemsForParent?: { id: string; Item: string; SKU: string }[];
|
||||
action: string;
|
||||
submitLabel: string;
|
||||
error?: string | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const parentOptions = $derived(
|
||||
item ? itemsForParent.filter((p) => p.id !== item!.id) : itemsForParent
|
||||
);
|
||||
const defaultItem = $derived(item?.Item ?? "");
|
||||
const defaultSku = $derived(item?.SKU ?? "");
|
||||
const defaultDesc = $derived(item?.Description ?? "");
|
||||
const defaultUom = $derived(item?.UOM ?? "");
|
||||
const defaultCost = $derived(item?.Cost != null ? String(item.Cost) : "");
|
||||
const defaultVendor = $derived(item?.Vendor ?? "");
|
||||
const defaultCat = $derived(Array.isArray(item?.Catagory) ? item!.Catagory[0] : item?.Catagory ?? "");
|
||||
const defaultSub = $derived(Array.isArray(item?.Sub_Catagory) ? item!.Sub_Catagory[0] : item?.Sub_Catagory ?? "");
|
||||
const defaultParent = $derived(item?.Parent ?? "");
|
||||
const defaultHasParent = $derived(!!item?.Has_Parent);
|
||||
const defaultStock = $derived(!!item?.Stock);
|
||||
|
||||
let skuValue = $state("");
|
||||
let showScanner = $state(false);
|
||||
let scanError = $state<string | null>(null);
|
||||
let videoEl = $state<HTMLVideoElement | undefined>(undefined);
|
||||
let stream: MediaStream | null = null;
|
||||
let scanRAF = 0;
|
||||
|
||||
$effect(() => {
|
||||
skuValue = defaultSku;
|
||||
});
|
||||
|
||||
async function ensureBarcodeDetector(): Promise<boolean> {
|
||||
if (typeof globalThis === "undefined" || "BarcodeDetector" in globalThis) return true;
|
||||
try {
|
||||
const mod = await import("barcode-detector-api-polyfill");
|
||||
(globalThis as unknown as { BarcodeDetector: typeof BarcodeDetector }).BarcodeDetector =
|
||||
mod.BarcodeDetector;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function startScan() {
|
||||
scanError = null;
|
||||
const hasDetector = await ensureBarcodeDetector();
|
||||
if (!hasDetector) {
|
||||
scanError = "Failed to load barcode scanner. Try again or use Chrome/Edge.";
|
||||
showScanner = true;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: { facingMode: "environment", width: { ideal: 1280 }, height: { ideal: 720 } },
|
||||
});
|
||||
showScanner = true;
|
||||
} catch (e) {
|
||||
scanError = e instanceof Error ? e.message : "Could not access camera.";
|
||||
showScanner = true;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!showScanner || !stream || !videoEl) return;
|
||||
videoEl.srcObject = stream;
|
||||
videoEl.play().then(() => scanLoop()).catch(() => {});
|
||||
return () => {
|
||||
if (videoEl.srcObject) videoEl.srcObject = null;
|
||||
};
|
||||
});
|
||||
|
||||
const SCAN_INTERVAL_MS = 300;
|
||||
|
||||
function scanLoop() {
|
||||
if (!showScanner || !videoEl || !("BarcodeDetector" in globalThis)) return;
|
||||
const detector = new (globalThis as unknown as { BarcodeDetector: typeof BarcodeDetector }).BarcodeDetector();
|
||||
let lastScan = 0;
|
||||
async function detect() {
|
||||
if (!showScanner || !videoEl) return;
|
||||
if (videoEl.readyState < 2) {
|
||||
scanRAF = requestAnimationFrame(detect);
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
if (now - lastScan >= SCAN_INTERVAL_MS) {
|
||||
lastScan = now;
|
||||
try {
|
||||
const barcodes = await detector.detect(videoEl);
|
||||
if (barcodes.length > 0) {
|
||||
skuValue = barcodes[0].rawValue;
|
||||
stopScan();
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// ignore frame errors
|
||||
}
|
||||
}
|
||||
scanRAF = requestAnimationFrame(detect);
|
||||
}
|
||||
detect();
|
||||
}
|
||||
|
||||
function stopScan() {
|
||||
showScanner = false;
|
||||
scanError = null;
|
||||
if (scanRAF) {
|
||||
cancelAnimationFrame(scanRAF);
|
||||
scanRAF = 0;
|
||||
}
|
||||
if (stream) {
|
||||
stream.getTracks().forEach((t) => t.stop());
|
||||
stream = null;
|
||||
}
|
||||
if (videoEl && videoEl.srcObject) {
|
||||
videoEl.srcObject = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form
|
||||
method="POST"
|
||||
action={action}
|
||||
enctype="multipart/form-data"
|
||||
use:enhance={() => async ({ update, result }) => {
|
||||
await update();
|
||||
if (result.type === "success" && result.data?.success) {
|
||||
onSuccess?.();
|
||||
}
|
||||
}}
|
||||
class="flex flex-col gap-4"
|
||||
>
|
||||
{#if isEdit}
|
||||
<input type="hidden" name="id" value={item!.id} />
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="Item">Item</Label>
|
||||
<Input id="Item" name="Item" value={defaultItem} required />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="SKU">SKU</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input id="SKU" name="SKU" bind:value={skuValue} class="flex-1" />
|
||||
<Button type="button" variant="outline" onclick={startScan} title="Scan barcode">Scan</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if showScanner}
|
||||
<div
|
||||
class="bg-background/95 fixed inset-0 z-50 flex flex-col items-center justify-center gap-4 p-4 backdrop-blur-sm"
|
||||
role="dialog"
|
||||
aria-label="Scan barcode"
|
||||
>
|
||||
<div class="flex w-full max-w-sm flex-col gap-2">
|
||||
{#if scanError}
|
||||
<p class="text-destructive text-sm">{scanError}</p>
|
||||
{:else}
|
||||
<video
|
||||
bind:this={videoEl}
|
||||
class="border-input w-full rounded-lg border"
|
||||
muted
|
||||
playsinline
|
||||
></video>
|
||||
{/if}
|
||||
<div class="flex gap-2">
|
||||
<Button type="button" variant="outline" class="flex-1" onclick={stopScan}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="Description">Description</Label>
|
||||
<Input id="Description" name="Description" value={defaultDesc} />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="Image">Image</Label>
|
||||
{#if isEdit && item?.Image}
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<img
|
||||
src={itemImageUrl(item!.id, item!.Image)}
|
||||
alt=""
|
||||
class="size-20 rounded border object-cover"
|
||||
/>
|
||||
<span class="text-muted-foreground text-sm">Replace:</span>
|
||||
</div>
|
||||
{/if}
|
||||
<Input id="Image" name="Image" type="file" accept="image/*" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="UOM">UOM</Label>
|
||||
<select
|
||||
id="UOM"
|
||||
name="UOM"
|
||||
class="border-input bg-background flex h-9 w-full rounded-md border px-3 py-1 text-sm shadow-xs"
|
||||
>
|
||||
<option value="">—</option>
|
||||
{#each UOM_OPTIONS as o}
|
||||
<option value={o} selected={defaultUom === o}>{o}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="Cost">Cost</Label>
|
||||
<Input id="Cost" name="Cost" type="number" step="any" value={defaultCost} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="Vendor">Vendor</Label>
|
||||
<Input id="Vendor" name="Vendor" value={defaultVendor} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="Catagory">Catagory</Label>
|
||||
<select
|
||||
id="Catagory"
|
||||
name="Catagory"
|
||||
class="border-input bg-background flex h-9 w-full rounded-md border px-3 py-1 text-sm shadow-xs"
|
||||
>
|
||||
<option value="">—</option>
|
||||
{#each CATAGORY_OPTIONS as o}
|
||||
<option value={o} selected={defaultCat === o}>{o}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="Sub_Catagory">Sub_Catagory</Label>
|
||||
<select
|
||||
id="Sub_Catagory"
|
||||
name="Sub_Catagory"
|
||||
class="border-input bg-background flex h-9 w-full rounded-md border px-3 py-1 text-sm shadow-xs"
|
||||
>
|
||||
<option value="">—</option>
|
||||
{#each SUB_CATAGORY_OPTIONS as o}
|
||||
<option value={o} selected={defaultSub === o}>{o}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="Parent">Parent</Label>
|
||||
<select
|
||||
id="Parent"
|
||||
name="Parent"
|
||||
class="border-input bg-background flex h-9 w-full rounded-md border px-3 py-1 text-sm shadow-xs"
|
||||
>
|
||||
<option value="">—</option>
|
||||
{#each parentOptions as p}
|
||||
<option value={p.id} selected={defaultParent === p.id}>
|
||||
{p.Item || p.SKU || p.id}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-6">
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="Has_Parent"
|
||||
value="on"
|
||||
checked={defaultHasParent}
|
||||
class="border-input size-4 rounded"
|
||||
/>
|
||||
Has Parent
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="Stock"
|
||||
value="on"
|
||||
checked={defaultStock}
|
||||
class="border-input size-4 rounded"
|
||||
/>
|
||||
Stock
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<p class="text-destructive text-sm">{error}</p>
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button type="submit">{submitLabel}</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,250 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Badge } from "$lib/components/ui/badge";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Card, CardContent } from "$lib/components/ui/card";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { enhance } from "$app/forms";
|
||||
import { goto } from "$app/navigation";
|
||||
import BarcodeScanDialog from "$lib/components/BarcodeScanDialog.svelte";
|
||||
import ItemForm from "./ItemForm.svelte";
|
||||
import type { StackqItem } from "$lib/types/items";
|
||||
|
||||
let {
|
||||
data,
|
||||
searchFromUrl = "",
|
||||
}: {
|
||||
data: {
|
||||
items: StackqItem[];
|
||||
itemsForParent: { id: string; Item: string; SKU: string }[];
|
||||
};
|
||||
searchFromUrl?: string;
|
||||
} = $props();
|
||||
|
||||
let dialogOpen = $state(false);
|
||||
let scanDialogOpen = $state(false);
|
||||
let editingItem = $state<StackqItem | null>(null);
|
||||
let formWhich = $state<"create" | "update">("create");
|
||||
let searchQuery = $state(searchFromUrl);
|
||||
|
||||
const items = $derived(data?.items ?? []);
|
||||
const itemsForParent = $derived(data?.itemsForParent ?? []);
|
||||
|
||||
const filteredItems = $derived(
|
||||
(() => {
|
||||
const q = searchQuery.trim().toLowerCase();
|
||||
if (!q) return items;
|
||||
return items.filter((i) => {
|
||||
const item = (i.Item ?? "").toLowerCase();
|
||||
const sku = (i.SKU ?? "").toLowerCase();
|
||||
const desc = (i.Description ?? "").toLowerCase();
|
||||
const vendor = (i.Vendor ?? "").toLowerCase();
|
||||
const cat = Array.isArray(i.Catagory) ? i.Catagory.join(" ").toLowerCase() : (i.Catagory ?? "").toLowerCase();
|
||||
const sub = Array.isArray(i.Sub_Catagory) ? i.Sub_Catagory.join(" ").toLowerCase() : (i.Sub_Catagory ?? "").toLowerCase();
|
||||
return item.includes(q) || sku.includes(q) || desc.includes(q) || vendor.includes(q) || cat.includes(q) || sub.includes(q);
|
||||
});
|
||||
})()
|
||||
);
|
||||
|
||||
const PB_BASE = "https://pocketbase.ccllc.pro";
|
||||
const COLLECTION = "Stackq_Items";
|
||||
function imageUrl(item: StackqItem): string | null {
|
||||
if (!item.Image) return null;
|
||||
return `${PB_BASE}/api/files/${COLLECTION}/${item.id}/${item.Image}`;
|
||||
}
|
||||
|
||||
function asList(val: string | string[] | undefined): string[] {
|
||||
if (val == null) return [];
|
||||
return Array.isArray(val) ? val : [val];
|
||||
}
|
||||
|
||||
function formatDate(iso: string) {
|
||||
if (!iso) return "—";
|
||||
try {
|
||||
return new Date(iso).toLocaleDateString(undefined, {
|
||||
dateStyle: "short",
|
||||
timeStyle: "short",
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
editingItem = null;
|
||||
formWhich = "create";
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function openEdit(item: StackqItem) {
|
||||
editingItem = item;
|
||||
formWhich = "update";
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function onFormSuccess() {
|
||||
dialogOpen = false;
|
||||
editingItem = null;
|
||||
}
|
||||
|
||||
function onScanResult(value: string) {
|
||||
const sku = value.trim();
|
||||
const found = items.find((i) => (i.SKU ?? "").trim() === sku);
|
||||
if (found) goto("/items/" + found.id);
|
||||
else goto("/items?q=" + encodeURIComponent(sku));
|
||||
}
|
||||
|
||||
const formError = $derived(
|
||||
data?.form === "create" || data?.form === "update" ? (data?.error as string | undefined) ?? null : null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (formError && (data?.form === "create" || data?.form === "update")) {
|
||||
dialogOpen = true;
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (searchFromUrl) searchQuery = searchFromUrl;
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Items – Stackq</title>
|
||||
</svelte:head>
|
||||
|
||||
<main class="container mx-auto flex flex-col gap-4 px-4 py-6 sm:gap-6 sm:px-6 sm:py-8 md:max-w-full">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<a href="/" class="text-muted-foreground hover:text-foreground shrink-0 text-sm" title="Back to main">Back</a>
|
||||
<h1 class="text-xl font-semibold truncate sm:text-2xl">Stackq Items</h1>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:gap-2">
|
||||
<div class="w-full sm:w-64 md:w-80 sm:min-w-0">
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search items, SKU…"
|
||||
bind:value={searchQuery}
|
||||
class="h-11 w-full sm:h-9"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-muted-foreground text-sm shrink-0">
|
||||
{filteredItems.length}{filteredItems.length !== items.length ? ` of ${items.length}` : ""} items
|
||||
</span>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" onclick={() => (scanDialogOpen = true)} title="Scan barcode to look up item" class="min-h-11 flex-1 sm:flex-none">
|
||||
Scan
|
||||
</Button>
|
||||
<Button onclick={openCreate} class="min-h-11 flex-1 sm:flex-none">
|
||||
New item
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if filteredItems.length === 0}
|
||||
<Card>
|
||||
<CardContent class="flex h-32 items-center justify-center">
|
||||
<p class="text-muted-foreground text-center text-sm">
|
||||
{items.length === 0
|
||||
? "No items. Create one or check PocketBase collection name and auth."
|
||||
: "No items match your search."}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each filteredItems as item (item.id)}
|
||||
<Card class="overflow-hidden">
|
||||
<CardContent class="flex flex-row items-center gap-3 p-3 md:gap-4 md:px-4 md:py-3">
|
||||
<div class="size-12 shrink-0 overflow-hidden rounded border">
|
||||
{#if imageUrl(item)}
|
||||
<a
|
||||
href={imageUrl(item)!}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="block size-full"
|
||||
>
|
||||
<img
|
||||
src={imageUrl(item)!}
|
||||
alt=""
|
||||
class="size-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
{:else}
|
||||
<span class="text-muted-foreground flex size-full items-center justify-center text-xs">—</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<a href="/items/{item.id}" class="font-medium hover:underline">{item.Item ?? "—"}</a>
|
||||
<span class="text-muted-foreground font-mono text-xs"> · {item.SKU ?? "—"}</span>
|
||||
{#if item.Description}
|
||||
<span class="text-muted-foreground ml-1 truncate text-xs">· {item.Description}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex shrink-0 items-center gap-2 text-xs">
|
||||
<Badge variant="outline">{item.UOM ?? "—"}</Badge>
|
||||
<span class="tabular-nums">{item.Cost != null ? Number(item.Cost).toLocaleString() : "—"}</span>
|
||||
{#if item.Stock}<Badge variant="default" class="text-xs">Stock</Badge>{/if}
|
||||
</div>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger
|
||||
class="border-0 bg-transparent hover:bg-muted inline-flex min-h-9 min-w-9 shrink-0 items-center justify-center rounded-md text-sm"
|
||||
>
|
||||
Actions
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Item onclick={() => goto("/items/" + item.id)}>View</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => openEdit(item)}>Edit</DropdownMenu.Item>
|
||||
<form
|
||||
id="delete-form-{item.id}"
|
||||
method="POST"
|
||||
action="?/delete"
|
||||
use:enhance
|
||||
class="hidden"
|
||||
>
|
||||
<input type="hidden" name="id" value={item.id} />
|
||||
</form>
|
||||
<DropdownMenu.Item
|
||||
class="text-destructive focus:text-destructive cursor-pointer"
|
||||
onclick={() => {
|
||||
if (confirm("Delete this item? This cannot be undone.")) {
|
||||
document.getElementById("delete-form-" + item.id)?.requestSubmit();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Dialog.Root bind:open={dialogOpen}>
|
||||
<Dialog.Content class="max-h-[90dvh] w-[calc(100vw-2rem)] max-w-[500px] overflow-y-auto rounded-lg sm:w-full">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{editingItem ? "Edit item" : "New item"}</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
{editingItem ? "Update the item below." : "Add a new Stackq item."}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<ItemForm
|
||||
item={editingItem}
|
||||
itemsForParent={itemsForParent}
|
||||
action={editingItem ? "?/update" : "?/create"}
|
||||
submitLabel={editingItem ? "Update" : "Create"}
|
||||
error={formError ?? null}
|
||||
onSuccess={onFormSuccess}
|
||||
/>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
<BarcodeScanDialog bind:open={scanDialogOpen} onResult={onScanResult} title="Scan to look up item" />
|
||||
</main>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user