feat: Phase 1 — extract the client (web SPA + desktop) to dtoro/oikos-web
Problem: the hexagonal refactor churns the backend tree for nine more phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper, compose/web image) must move to its own repo first so doc/layout rewrites land once on a backend-only tree. Change: - New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/ (updateURL repointed to oikos-web releases), compose/, own CI (web + desktop jobs), own deploy script (CI-green gate, TOCTOU guard, version-tagged images, prune-to-3), own webhook receiver on :9798 + launchd unit, own compose project publishing the same 8091:80. - Cutover executed on mac-mini in order: oikos stack's web service stopped+removed, oikos-web project brought up on 8091; outer Caddy untouched (targets the published port) — serving + Authentik flow + /wails 404 quirk verified post-cutover. - Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install targets, the compose web service, oikos-web from deploy.sh's fallback prune list; wails + go-keyring dropped from go.mod, vendor synced. - README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now point at the new repo; mbse + mascot design docs carry a path note. Risk: production SPA serving depends on the new pipeline now; rollback is versioned-image re-up of the old web service from a pre-split checkout (port 8091). Desktop builds installed before the split still check dtoro/oikos releases — one manual reinstall, noted in the oikos-web release notes. Verification: go vet, make test (race), make generate-check, golangci (no new findings; baseline down 400→365); post-cutover curls — localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
This commit is contained in:
226
web/node_modules/svelte-sonner/dist/toast-state.svelte.js
generated
vendored
Normal file
226
web/node_modules/svelte-sonner/dist/toast-state.svelte.js
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
import { isBrowser } from './internal/helpers.js';
|
||||
import { sonnerContext } from './internal/ctx.js';
|
||||
import { untrack } from 'svelte';
|
||||
let toastsCounter = 0;
|
||||
class ToastState {
|
||||
toasts = $state([]);
|
||||
heights = $state([]);
|
||||
#findToastIdx = (id) => {
|
||||
const idx = this.toasts.findIndex((toast) => toast.id === id);
|
||||
if (idx === -1)
|
||||
return null;
|
||||
return idx;
|
||||
};
|
||||
addToast = (data) => {
|
||||
if (!isBrowser)
|
||||
return;
|
||||
this.toasts.unshift(data);
|
||||
};
|
||||
updateToast = ({ id, data, type, message }) => {
|
||||
const toastIdx = this.toasts.findIndex((toast) => toast.id === id);
|
||||
const toastToUpdate = this.toasts[toastIdx];
|
||||
this.toasts[toastIdx] = {
|
||||
...toastToUpdate,
|
||||
...data,
|
||||
id,
|
||||
title: message,
|
||||
type,
|
||||
updated: true
|
||||
};
|
||||
};
|
||||
create = (data) => {
|
||||
const { message, ...rest } = data;
|
||||
const id = typeof data?.id === 'number' || (data.id && data.id?.length > 0)
|
||||
? data.id
|
||||
: toastsCounter++;
|
||||
// Support deprecated `dismissable` as a fallback for backwards compatibility
|
||||
const dismissible = data.dismissible !== undefined
|
||||
? data.dismissible
|
||||
: data.dismissable !== undefined
|
||||
? data.dismissable
|
||||
: true;
|
||||
const type = data.type === undefined ? 'default' : data.type;
|
||||
untrack(() => {
|
||||
const alreadyExists = this.toasts.find((toast) => toast.id === id);
|
||||
if (alreadyExists) {
|
||||
this.updateToast({ id, data, type, message, dismissible });
|
||||
}
|
||||
else {
|
||||
this.addToast({ ...rest, id, title: message, dismissible, type });
|
||||
}
|
||||
});
|
||||
return id;
|
||||
};
|
||||
dismiss = (id) => {
|
||||
untrack(() => {
|
||||
if (id === undefined) {
|
||||
// we're dismissing all the toasts
|
||||
this.toasts = this.toasts.map((toast) => ({ ...toast, dismiss: true }));
|
||||
return;
|
||||
}
|
||||
// we're dismissing a specific toast
|
||||
const toastIdx = this.toasts.findIndex((toast) => toast.id === id);
|
||||
if (this.toasts[toastIdx]) {
|
||||
this.toasts[toastIdx] = { ...this.toasts[toastIdx], dismiss: true };
|
||||
}
|
||||
});
|
||||
return id;
|
||||
};
|
||||
remove = (id) => {
|
||||
if (id === undefined) {
|
||||
// remove all toasts
|
||||
this.toasts = [];
|
||||
return;
|
||||
}
|
||||
// remove a specific toast
|
||||
const toastIdx = this.#findToastIdx(id);
|
||||
if (toastIdx === null)
|
||||
return;
|
||||
this.toasts.splice(toastIdx, 1);
|
||||
return id;
|
||||
};
|
||||
message = (message, data) => {
|
||||
return this.create({ ...data, type: 'default', message });
|
||||
};
|
||||
error = (message, data) => {
|
||||
return this.create({ ...data, type: 'error', message });
|
||||
};
|
||||
success = (message, data) => {
|
||||
return this.create({ ...data, type: 'success', message });
|
||||
};
|
||||
info = (message, data) => {
|
||||
return this.create({ ...data, type: 'info', message });
|
||||
};
|
||||
warning = (message, data) => {
|
||||
return this.create({ ...data, type: 'warning', message });
|
||||
};
|
||||
loading = (message, data) => {
|
||||
return this.create({ ...data, type: 'loading', message });
|
||||
};
|
||||
promise = (promise, data) => {
|
||||
if (!data) {
|
||||
// Nothing to show
|
||||
return;
|
||||
}
|
||||
let id = undefined;
|
||||
if (data.loading !== undefined) {
|
||||
id = this.create({
|
||||
...data,
|
||||
promise,
|
||||
type: 'loading',
|
||||
message: typeof data.loading === 'string' ? data.loading : data.loading()
|
||||
});
|
||||
}
|
||||
const p = promise instanceof Promise ? promise : promise();
|
||||
let shouldDismiss = id !== undefined;
|
||||
p.then((response) => {
|
||||
if (typeof response === 'object' &&
|
||||
response &&
|
||||
'ok' in response &&
|
||||
typeof response.ok === 'boolean' &&
|
||||
!response.ok) {
|
||||
shouldDismiss = false;
|
||||
const message = constructPromiseErrorMessage(response);
|
||||
this.create({ id, type: 'error', message });
|
||||
}
|
||||
else if (data.success !== undefined) {
|
||||
shouldDismiss = false;
|
||||
const message = typeof data.success === 'function' ? data.success(response) : data.success;
|
||||
this.create({ id, type: 'success', message });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (data.error !== undefined) {
|
||||
shouldDismiss = false;
|
||||
const message = typeof data.error === 'function' ? data.error(error) : data.error;
|
||||
this.create({ id, type: 'error', message });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (shouldDismiss) {
|
||||
// Toast is still in load state (and will be indefinitely — dismiss it)
|
||||
this.dismiss(id);
|
||||
id = undefined;
|
||||
}
|
||||
data.finally?.();
|
||||
});
|
||||
return id;
|
||||
};
|
||||
custom = (component, data) => {
|
||||
const id = data?.id || toastsCounter++;
|
||||
this.create({ component, id, ...data });
|
||||
return id;
|
||||
};
|
||||
removeHeight = (id) => {
|
||||
this.heights = this.heights.filter((height) => height.toastId !== id);
|
||||
};
|
||||
setHeight = (data) => {
|
||||
const toastIdx = this.#findToastIdx(data.toastId);
|
||||
if (toastIdx === null) {
|
||||
this.heights.push(data);
|
||||
return;
|
||||
}
|
||||
this.heights[toastIdx] = data;
|
||||
};
|
||||
reset = () => {
|
||||
this.toasts = [];
|
||||
this.heights = [];
|
||||
};
|
||||
}
|
||||
function constructPromiseErrorMessage(response) {
|
||||
if (response && typeof response === 'object' && 'status' in response) {
|
||||
return `HTTP error! Status: ${response.status}`;
|
||||
}
|
||||
return `Error! ${response}`;
|
||||
}
|
||||
export const toastState = new ToastState();
|
||||
function toastFunction(message, data) {
|
||||
return toastState.create({
|
||||
message,
|
||||
...data
|
||||
});
|
||||
}
|
||||
export class SonnerState {
|
||||
/**
|
||||
* A derived state of the toasts that are not dismissed.
|
||||
*/
|
||||
#activeToasts = $derived(toastState.toasts.filter((toast) => !toast.dismiss));
|
||||
get toasts() {
|
||||
return this.#activeToasts;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A hook to get a reference to the sonner toast state.
|
||||
*
|
||||
* Returns a class instance a getter for the `toasts` array.
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <script lang="ts">
|
||||
* import { useSonner } from 'svelte-sonner';
|
||||
*
|
||||
* const sonner = useSonner();
|
||||
*
|
||||
* // Reactive access to the toasts
|
||||
* $effect(() => console.log(sonner.toasts))
|
||||
* </script>
|
||||
* ```
|
||||
*/
|
||||
export function useSonner() {
|
||||
return sonnerContext.get();
|
||||
}
|
||||
const basicToast = toastFunction;
|
||||
export const toast = Object.assign(basicToast, {
|
||||
success: toastState.success,
|
||||
info: toastState.info,
|
||||
warning: toastState.warning,
|
||||
error: toastState.error,
|
||||
custom: toastState.custom,
|
||||
message: toastState.message,
|
||||
promise: toastState.promise,
|
||||
dismiss: toastState.dismiss,
|
||||
loading: toastState.loading,
|
||||
getActiveToasts: () => {
|
||||
return toastState.toasts.filter((toast) => !toast.dismiss);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user