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:
202
web/node_modules/bits-ui/dist/bits/date-range-field/date-range-field.svelte.js
generated
vendored
Normal file
202
web/node_modules/bits-ui/dist/bits/date-range-field/date-range-field.svelte.js
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
import { boxWith, onDestroyEffect, attachRef, DOMContext, } from "svelte-toolbelt";
|
||||
import { Context, watch } from "runed";
|
||||
import { DateFieldInputState, DateFieldRootState } from "../date-field/date-field.svelte.js";
|
||||
import { useId } from "../../internal/use-id.js";
|
||||
import { createBitsAttrs, boolToEmptyStrOrUndef } from "../../internal/attrs.js";
|
||||
import { createFormatter } from "../../internal/date-time/formatter.js";
|
||||
import { removeDescriptionElement } from "../../internal/date-time/field/helpers.js";
|
||||
import { isBefore } from "../../internal/date-time/utils.js";
|
||||
import { getFirstSegment } from "../../internal/date-time/field/segments.js";
|
||||
export const dateRangeFieldAttrs = createBitsAttrs({
|
||||
component: "date-range-field",
|
||||
parts: ["root", "label"],
|
||||
});
|
||||
export const DateRangeFieldRootContext = new Context("DateRangeField.Root");
|
||||
export class DateRangeFieldRootState {
|
||||
static create(opts) {
|
||||
return DateRangeFieldRootContext.set(new DateRangeFieldRootState(opts));
|
||||
}
|
||||
opts;
|
||||
startFieldState = undefined;
|
||||
endFieldState = undefined;
|
||||
descriptionId = useId();
|
||||
formatter;
|
||||
fieldNode = $state(null);
|
||||
labelNode = $state(null);
|
||||
descriptionNode = $state(null);
|
||||
startValueComplete = $derived.by(() => this.opts.startValue.current !== undefined);
|
||||
endValueComplete = $derived.by(() => this.opts.endValue.current !== undefined);
|
||||
rangeComplete = $derived(this.startValueComplete && this.endValueComplete);
|
||||
domContext;
|
||||
attachment;
|
||||
constructor(opts) {
|
||||
this.opts = opts;
|
||||
this.formatter = createFormatter({
|
||||
initialLocale: this.opts.locale.current,
|
||||
monthFormat: boxWith(() => "long"),
|
||||
yearFormat: boxWith(() => "numeric"),
|
||||
});
|
||||
this.domContext = new DOMContext(this.opts.ref);
|
||||
this.attachment = attachRef(this.opts.ref, (v) => (this.fieldNode = v));
|
||||
onDestroyEffect(() => {
|
||||
removeDescriptionElement(this.descriptionId, this.domContext.getDocument());
|
||||
});
|
||||
$effect(() => {
|
||||
if (this.formatter.getLocale() === this.opts.locale.current)
|
||||
return;
|
||||
this.formatter.setLocale(this.opts.locale.current);
|
||||
});
|
||||
/**
|
||||
* Synchronize the start and end values with the `value` in case
|
||||
* it is updated externally.
|
||||
*/
|
||||
watch(() => this.opts.value.current, (value) => {
|
||||
if (value.start && value.end) {
|
||||
this.opts.startValue.current = value.start;
|
||||
this.opts.endValue.current = value.end;
|
||||
}
|
||||
else if (value.start) {
|
||||
this.opts.startValue.current = value.start;
|
||||
this.opts.endValue.current = undefined;
|
||||
}
|
||||
else if (value.start === undefined && value.end === undefined) {
|
||||
this.opts.startValue.current = undefined;
|
||||
this.opts.endValue.current = undefined;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Synchronize the placeholder value with the current start value
|
||||
*/
|
||||
watch(() => this.opts.value.current, (value) => {
|
||||
const startValue = value.start;
|
||||
if (startValue && this.opts.placeholder.current !== startValue) {
|
||||
this.opts.placeholder.current = startValue;
|
||||
}
|
||||
});
|
||||
watch([() => this.opts.startValue.current, () => this.opts.endValue.current], ([startValue, endValue]) => {
|
||||
if (this.opts.value.current &&
|
||||
this.opts.value.current.start === startValue &&
|
||||
this.opts.value.current.end === endValue) {
|
||||
return;
|
||||
}
|
||||
if (startValue && endValue) {
|
||||
this.#updateValue((prev) => {
|
||||
if (prev.start === startValue && prev.end === endValue) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
start: startValue,
|
||||
end: endValue,
|
||||
};
|
||||
});
|
||||
}
|
||||
else if (this.opts.value.current &&
|
||||
this.opts.value.current.start &&
|
||||
this.opts.value.current.end) {
|
||||
this.opts.value.current.start = undefined;
|
||||
this.opts.value.current.end = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
validationStatus = $derived.by(() => {
|
||||
const value = this.opts.value.current;
|
||||
if (value === undefined)
|
||||
return false;
|
||||
if (value.start === undefined || value.end === undefined)
|
||||
return false;
|
||||
const msg = this.opts.validate.current?.({
|
||||
start: value.start,
|
||||
end: value.end,
|
||||
});
|
||||
if (msg) {
|
||||
return {
|
||||
reason: "custom",
|
||||
message: msg,
|
||||
};
|
||||
}
|
||||
const minValue = this.opts.minValue.current;
|
||||
if (minValue && value.start && isBefore(value.start, minValue)) {
|
||||
return {
|
||||
reason: "min",
|
||||
};
|
||||
}
|
||||
const maxValue = this.opts.maxValue.current;
|
||||
if ((maxValue && value.end && isBefore(maxValue, value.end)) ||
|
||||
(maxValue && value.start && isBefore(maxValue, value.start))) {
|
||||
return {
|
||||
reason: "max",
|
||||
};
|
||||
}
|
||||
return false;
|
||||
});
|
||||
isInvalid = $derived.by(() => {
|
||||
if (this.validationStatus === false)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
#updateValue(cb) {
|
||||
const value = this.opts.value.current;
|
||||
const newValue = cb(value);
|
||||
this.opts.value.current = newValue;
|
||||
}
|
||||
props = $derived.by(() => ({
|
||||
id: this.opts.id.current,
|
||||
role: "group",
|
||||
[dateRangeFieldAttrs.root]: "",
|
||||
"data-invalid": boolToEmptyStrOrUndef(this.isInvalid),
|
||||
...this.attachment,
|
||||
}));
|
||||
}
|
||||
export class DateRangeFieldLabelState {
|
||||
static create(opts) {
|
||||
return new DateRangeFieldLabelState(opts, DateRangeFieldRootContext.get());
|
||||
}
|
||||
opts;
|
||||
root;
|
||||
attachment;
|
||||
constructor(opts, root) {
|
||||
this.opts = opts;
|
||||
this.root = root;
|
||||
this.attachment = attachRef(this.opts.ref, (v) => (this.root.labelNode = v));
|
||||
}
|
||||
#onclick = () => {
|
||||
if (this.root.opts.disabled.current)
|
||||
return;
|
||||
const firstSegment = getFirstSegment(this.root.fieldNode);
|
||||
if (!firstSegment)
|
||||
return;
|
||||
firstSegment.focus();
|
||||
};
|
||||
props = $derived.by(() => ({
|
||||
id: this.opts.id.current,
|
||||
"data-invalid": boolToEmptyStrOrUndef(this.root.isInvalid),
|
||||
"data-disabled": boolToEmptyStrOrUndef(this.root.opts.disabled.current),
|
||||
[dateRangeFieldAttrs.label]: "",
|
||||
onclick: this.#onclick,
|
||||
...this.attachment,
|
||||
}));
|
||||
}
|
||||
export class DateRangeFieldInputState {
|
||||
static create(opts, type) {
|
||||
const root = DateRangeFieldRootContext.get();
|
||||
const fieldState = DateFieldRootState.create({
|
||||
value: type === "start" ? root.opts.startValue : root.opts.endValue,
|
||||
disabled: root.opts.disabled,
|
||||
readonly: root.opts.readonly,
|
||||
readonlySegments: root.opts.readonlySegments,
|
||||
validate: boxWith(() => undefined),
|
||||
minValue: root.opts.minValue,
|
||||
maxValue: root.opts.maxValue,
|
||||
hourCycle: root.opts.hourCycle,
|
||||
locale: root.opts.locale,
|
||||
hideTimeZone: root.opts.hideTimeZone,
|
||||
required: root.opts.required,
|
||||
granularity: root.opts.granularity,
|
||||
placeholder: root.opts.placeholder,
|
||||
onInvalid: root.opts.onInvalid,
|
||||
errorMessageId: root.opts.errorMessageId,
|
||||
isInvalidProp: boxWith(() => root.isInvalid),
|
||||
}, root);
|
||||
return new DateFieldInputState({ name: opts.name, id: opts.id, ref: opts.ref }, fieldState);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user