Skip to content

Commit f84f393

Browse files
committed
save 2
1 parent c397379 commit f84f393

15 files changed

Lines changed: 386 additions & 291 deletions

File tree

packages/admin-vanilla/src/app.inline.css

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -411,17 +411,18 @@ body:has(.modal-shell[open]) {
411411
position: fixed;
412412
inset: 0;
413413
z-index: 1000;
414-
display: grid;
415414
justify-items: center;
416415
align-items: start;
417416
padding: 28px;
418417
background: var(--color-overlay-modal);
419418
backdrop-filter: blur(10px);
420-
overflow: hidden;
419+
overflow: clip;
420+
display: flex;
421+
flex-direction: column;
421422
}
422423

423424
.modal-card {
424-
width: min(1180px, 100%);
425+
width: 100%;
425426
max-height: calc(100vh - 56px);
426427
display: flex;
427428
flex-direction: column;
@@ -452,8 +453,8 @@ footer.modal-header {
452453
font-size: 2rem;
453454
}
454455

455-
.modal-header > div {
456-
min-width: 0;
456+
.modal-title {
457+
flex: 1;
457458
}
458459

459460
.close-button {
@@ -511,8 +512,8 @@ footer.modal-header {
511512
}
512513

513514
.modal-layout {
514-
display: grid;
515-
grid-template-columns: 260px minmax(0, 1fr);
515+
display: flex;
516+
align-items: flex-start;
516517
gap: 22px;
517518
padding: 24px;
518519
flex: 1 1 auto;
@@ -571,6 +572,9 @@ footer.modal-header {
571572
}
572573

573574
.modal-sidebar {
575+
flex: 0 0 260px;
576+
width: 260px;
577+
max-width: 100%;
574578
position: sticky;
575579
top: 0;
576580
align-self: start;
@@ -580,7 +584,6 @@ footer.modal-header {
580584
border-radius: 20px;
581585
background: var(--color-surface-sidebar);
582586
overflow: hidden;
583-
word-break: break-all;
584587
}
585588

586589
.sidebar-section {
@@ -721,6 +724,52 @@ footer.modal-header {
721724
border-bottom: 0;
722725
}
723726

727+
.prefix-bag-sidebar {
728+
margin: 16px 0 0;
729+
display: grid;
730+
grid-template-columns: minmax(0, 1fr);
731+
gap: 10px;
732+
}
733+
734+
.prefix-bag-sidebar-term {
735+
font-size: 0.76rem;
736+
letter-spacing: 0.1em;
737+
color: var(--color-accent-meta);
738+
text-transform: none;
739+
margin: 0;
740+
}
741+
742+
.prefix-bag-sidebar-prefix {
743+
font-size: 0.88rem;
744+
}
745+
746+
.prefix-bag-sidebar-value {
747+
margin: 4px 0 0 10px;
748+
color: var(--color-text-strong);
749+
line-height: 1.45;
750+
padding-bottom: 10px;
751+
border-bottom: 1px solid var(--color-border-soft);
752+
}
753+
754+
.prefix-bag-sidebar-value:last-child {
755+
padding-bottom: 0;
756+
border-bottom: 0;
757+
}
758+
759+
.prefix-bag-sidebar-pill {
760+
display: inline-flex;
761+
align-items: center;
762+
margin-left: -8px;
763+
padding: 4px 8px;
764+
border-radius: 999px;
765+
background: var(--color-surface-pill);
766+
color: var(--color-accent-pill-text);
767+
font-weight: 600;
768+
font-size: 0.84rem;
769+
line-height: 1.2;
770+
vertical-align: middle;
771+
}
772+
724773
.summary-card ul {
725774
margin-bottom: 0;
726775
padding-left: 18px;
@@ -735,6 +784,8 @@ footer.modal-header {
735784
}
736785

737786
.modal-main {
787+
flex: 1 1 0;
788+
min-width: 0;
738789
gap: 22px;
739790
}
740791

@@ -1192,6 +1243,7 @@ footer.modal-header {
11921243
.value-ordered-list li {
11931244
padding-bottom: 10px;
11941245
border-bottom: 1px solid var(--color-border-soft);
1246+
11951247
}
11961248

11971249
.timeline-list li:last-child,
@@ -1329,12 +1381,21 @@ table.value-table td {
13291381

13301382
.hero-panel,
13311383
.section-header,
1332-
.modal-layout,
13331384
.modal-section-header,
13341385
.section-field-grid {
13351386
grid-template-columns: 1fr;
13361387
}
13371388

1389+
.modal-layout {
1390+
flex-direction: column;
1391+
}
1392+
1393+
.modal-sidebar {
1394+
flex: 0 0 auto;
1395+
width: 100%;
1396+
position: relative;
1397+
}
1398+
13381399
.composite-fields {
13391400
grid-template-columns: 1fr;
13401401
}
@@ -1347,7 +1408,7 @@ table.value-table td {
13471408
}
13481409

13491410
.field-index {
1350-
position: static;
1411+
position: relative;
13511412
}
13521413

13531414
.row-editor-row,

packages/admin-vanilla/src/app.tsx

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ interface SidebarSectionProps {
120120

121121
interface ToggleFieldProps {
122122
field: FieldDefinition;
123-
value: unknown;
124-
onDraftChange: DraftChangeHandler;
123+
value: boolean;
124+
onDraftChange: DraftChangeHandler<boolean>;
125125
headerOnly?: boolean;
126126
}
127127

@@ -324,8 +324,10 @@ function formatStorageErrorForDisplay(storageError: string): string {
324324

325325

326326
function formatFieldValue(value: any): string {
327-
if (typeof value === "string")
328-
return value && value.trim() ? value : "—";
327+
if (typeof value === "string"
328+
|| value instanceof IdString
329+
|| value instanceof KeyString)
330+
return value.trim() || "—";
329331
if (Array.isArray(value)) {
330332
if (!value.length) return "—";
331333
if (typeof value[0] === "string")
@@ -462,18 +464,22 @@ function renderMetadataTableField(ctx: ReadonlyFieldContext<readonly string[]>)
462464
function renderTableField(ctx: ReadonlyFieldContext) {
463465
const { field, value, itemsByTab } = ctx;
464466
definitely<readonly string[]>(value);
467+
return renderLinesList(value, field.key, itemsByTab)
468+
}
469+
470+
function renderLinesList(value: readonly string[], key: string, itemsByTab?: AdminRecordStore) {
465471
const missingCheck =
466472
itemsByTab ?
467-
field.key === "effectiveReadonlyBags" ? new Set(Array.from(itemsByTab.availableBagNames, e => e.toString())) :
468-
field.key === "effectivePluginSet" ? itemsByTab.availablePluginNames :
473+
key === "effectiveReadonlyBags" ? new Set(Array.from(itemsByTab.availableBagNames, e => e.toString())) :
474+
key === "effectivePluginSet" ? itemsByTab.availablePluginNames :
469475
null : null;
470476
const lines = value.map(line => ({ line, missing: missingCheck && !missingCheck.has(line), }));
471477
return <ul class="value-list">{lines.map(({ line, missing }) => <li>
472-
{line}
478+
{line.split("/").map((e, i, a) => <>{e}{(i !== a.length - 1) ? "/" : ""}<wbr/></>)}
473479
{missing ? <span class="missing-marker" aria-label="Missing dependency" title="Missing dependency"><MaterialSymbol icon={warningIcon} /></span> : null}
474480
</li>)}</ul>;
475-
}
476481

482+
}
477483
function renderCalloutField(ctx: ReadonlyFieldContext) {
478484
definitely<string>(ctx.value);
479485
return <div class="field-callout"><p>{formatFieldValue(ctx.value)}</p></div>;
@@ -727,6 +733,7 @@ class PermissionTableFieldHandler extends FieldTypeHandler<readonly PermissionRo
727733
}
728734
}
729735

736+
const defaultPrefixPill = <div class="prefix-bag-sidebar-pill">default</div>;
730737
class PrefixTableFieldHandler extends FieldTypeHandler<WritablePrefixRow[]> {
731738
constructor() {
732739
super();
@@ -736,6 +743,15 @@ class PrefixTableFieldHandler extends FieldTypeHandler<WritablePrefixRow[]> {
736743
return [];
737744
}
738745

746+
public override renderSidebar(ctx: ReadonlyFieldContext<WritablePrefixRow[]>): JSX.Node {
747+
return <dl class="prefix-bag-sidebar">
748+
{ctx.value.map(e => <>
749+
<dt class="prefix-bag-sidebar-term">{e.prefix ? <span class="prefix-bag-sidebar-prefix">"{e.prefix}"</span> : defaultPrefixPill}</dt>
750+
<dd class="prefix-bag-sidebar-value">{e.bagName.toString()}</dd>
751+
</>)}
752+
</dl>
753+
}
754+
739755
public override renderEditor(ctx: FieldEditorContext) {
740756
const { field, value, disabled, modalState, itemsByTab, inputId, onDraftChange, onPendingRowsChange } = ctx;
741757
const forDisplay = field.mode === "server";
@@ -820,7 +836,7 @@ class PrefixTableFieldHandler extends FieldTypeHandler<WritablePrefixRow[]> {
820836
return <table class="value-table">
821837
{displayedMappingRows.map((row) => (
822838
<tr>
823-
<td>{row.prefix ? <code>{'"' + row.prefix + '"'}</code> : <span class="pill-value pill-value-small">default</span>}</td>
839+
<td>{row.prefix ? <code>{'"' + row.prefix + '"'}</code> : defaultPrefixPill}</td>
824840
<td>{row.bagName.toString()}</td>
825841
</tr>
826842
))}
@@ -953,7 +969,7 @@ class ValueListFieldHandler extends FieldTypeHandler<string[]> {
953969
const lines = typeof ctx.value === "string"
954970
? lineListCodec.parse(ctx.value)
955971
: ctx.value as readonly string[];
956-
return <ul class="value-list">{lines.map((line) => <li>{line}</li>)}</ul>;
972+
return renderLinesList(lines, ctx.field.key, ctx.itemsByTab);
957973
}
958974

959975
public override renderEditor(ctx: FieldEditorContext) {
@@ -1064,6 +1080,7 @@ class FallbackFieldHandler extends StringFieldTypeHandler {
10641080
}
10651081
}
10661082

1083+
10671084
const textInputFieldHandler = new TextInputFieldHandler("text");
10681085
const numberInputFieldHandler = new TextInputFieldHandler("number");
10691086
const textareaFieldHandler = new TextareaFieldHandler(4);
@@ -1116,7 +1133,7 @@ function renderFieldEditor(input: FieldEditorInput) {
11161133
}
11171134

11181135
@customElement("mws-field-block")
1119-
class FieldBlockElement extends JSXElement {
1136+
class FieldBlockElement<T> extends JSXElement {
11201137
useLightDOM: boolean = true;
11211138

11221139
@state() accessor props!: FieldBlockProps;
@@ -1136,6 +1153,7 @@ class FieldBlockElement extends JSXElement {
11361153
) : null} */}
11371154

11381155
{useToggleEditor ? (
1156+
definitely<boolean>(value),
11391157
<ToggleFieldElement field={field} value={value} onDraftChange={this.props.onDraftChange} />
11401158
) : (
11411159
<div class="field-editor">
@@ -1196,7 +1214,7 @@ class ToggleFieldElement extends JSXElement {
11961214

11971215
protected render() {
11981216
const { field, value, onDraftChange, headerOnly } = this.props;
1199-
const checked = value === "enabled";
1217+
const checked = value === true;
12001218

12011219
return (
12021220
<div class="toggle-field-row">
@@ -1215,7 +1233,7 @@ class ToggleFieldElement extends JSXElement {
12151233
ref={(element) => {
12161234
if (element.checked !== checked) element.checked = checked;
12171235
}}
1218-
onchange={(event) => onDraftChange(field.key, (event.currentTarget as HTMLInputElement).checked ? "enabled" : "disabled")}
1236+
onchange={(event) => onDraftChange(field.key, (event.currentTarget as HTMLInputElement).checked)}
12191237
/>
12201238
<span class={checked ? "header-switch-track is-checked" : "header-switch-track"} aria-hidden="true">
12211239
<span class="header-switch-thumb"></span>
@@ -1262,14 +1280,14 @@ class RecordModalElement extends JSXElement {
12621280
}}>
12631281
<section class="modal-card" role="dialog" aria-modal="true" aria-label={`${selectedTab.label} details`}>
12641282
<header class="modal-header">
1265-
<div>
1283+
<div class="modal-title">
12661284
<p class="eyebrow">{selectedTab.eyebrow}</p>
12671285
<h3>{isModalLoading ? `Loading ${selectedTab.label.slice(0, -1).toLowerCase()}...` : modalState.mode === "create" ? `New ${selectedTab.label.slice(0, -1)}` : getPrimaryValue(selectedTab, modalState.draft)}</h3>
12681286
<p>{isModalLoading ? "Fetching record details from async storage before rendering the form." : selectedTab.description}</p>
12691287
</div>
1270-
<button class="close-button" type="button" onclick={onClose} aria-label="Close details">
1288+
<div class="close-button" onclick={onClose} aria-label="Close details">
12711289
<MaterialSymbol icon={closeIcon} />
1272-
</button>
1290+
</div>
12731291
</header>
12741292

12751293
{isModalLoading ? (

packages/admin-vanilla/src/definition/store.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ export class InMemoryAdminStorage implements AdminStorage {
88
private deriveItems: (data: DataStore) => AdminRecordStore
99
) {
1010
}
11-
11+
jsonReviver = (key: any, val: any) => {
12+
if (typeof val === "string" && val.startsWith(IdString.prefix))
13+
return new IdString(val.slice(IdString.prefix.length));
14+
if (typeof val === "string" && val.startsWith(KeyString.prefix))
15+
return new KeyString(val.slice(KeyString.prefix.length));
16+
return val;
17+
};
1218
public async loadAll(): Promise<AdminRecordStore> {
1319
const data = await (await fetch(pathPrefix + "/admin/load")).text();
1420

15-
this.data = JSON.parse(data, (key, val) => {
16-
if (typeof val === "string" && val.startsWith("IdString____"))
17-
return new IdString(val.slice("IdString____".length));
18-
if (typeof val === "string" && val.startsWith("KeyString____"))
19-
return new KeyString(val.slice("KeyString____".length));
20-
return val;
21-
});
21+
this.data = JSON.parse(data, this.jsonReviver);
2222
return this.deriveItems(this.data);
2323
}
2424
private wait(ms: number): Promise<void> {
@@ -42,10 +42,12 @@ export class InMemoryAdminStorage implements AdminStorage {
4242
body: JSON.stringify(prunedRecord),
4343
});
4444

45+
const text = await response.text();
46+
4547
if (response.status !== 200) {
46-
throw new Error(await response.text());
48+
throw new Error(text);
4749
} else {
48-
const storedRecord = await response.json();
50+
const storedRecord = JSON.parse(text, this.jsonReviver);
4951
console.log(storedRecord, prunedRecord);
5052
if (id) {
5153
if (storedRecord.id !== id) location.reload();

0 commit comments

Comments
 (0)