Skip to content

Commit 5a06ca6

Browse files
committedApr 23, 2024
Use dynamic context for defaultFormat
1 parent c49d4d2 commit 5a06ca6

15 files changed

+508
-347
lines changed
 

‎packages/base/card-api.gts

+47-47
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@ import { flatMap, merge, isEqual } from 'lodash';
55
import { TrackedWeakMap } from 'tracked-built-ins';
66
import { WatchedArray } from './watched-array';
77
import { BoxelInput, FieldContainer } from '@cardstack/boxel-ui/components';
8-
import { cn, eq, pick } from '@cardstack/boxel-ui/helpers';
8+
import { cn, and, eq, not, pick } from '@cardstack/boxel-ui/helpers';
99
import { on } from '@ember/modifier';
1010
import { startCase } from 'lodash';
11-
import { getBoxComponent, type BoxComponent } from './field-component';
11+
import {
12+
getBoxComponent,
13+
type BoxComponent,
14+
DefaultFormatConsumer,
15+
} from './field-component';
1216
import { getContainsManyComponent } from './contains-many-component';
1317
import { getLinksToEditor } from './links-to-editor';
1418
import { getLinksToManyComponent } from './links-to-many-component';
@@ -284,7 +288,7 @@ export interface Field<
284288
): Promise<any>;
285289
emptyValue(instance: BaseDef): any;
286290
validate(instance: BaseDef, value: any): void;
287-
component(model: Box<BaseDef>, defaultFormat: Format): BoxComponent;
291+
component(model: Box<BaseDef>): BoxComponent;
288292
getter(instance: BaseDef): BaseInstanceType<CardT>;
289293
queryableValue(value: any, stack: BaseDef[]): SearchT;
290294
queryMatcher(
@@ -578,27 +582,17 @@ class ContainsMany<FieldT extends FieldDefConstructor>
578582
);
579583
}
580584

581-
component(model: Box<BaseDef>, format: Format): BoxComponent {
585+
component(model: Box<BaseDef>): BoxComponent {
582586
let fieldName = this.name as keyof BaseDef;
583587
let arrayField = model.field(
584588
fieldName,
585589
useIndexBasedKey in this.card,
586590
) as unknown as Box<BaseDef[]>;
587591

588-
let renderFormat: Format | undefined = undefined;
589-
if (
590-
format === 'edit' &&
591-
'isFieldDef' in model.value.constructor &&
592-
model.value.constructor.isFieldDef
593-
) {
594-
renderFormat = 'atom';
595-
}
596-
597592
return getContainsManyComponent({
598593
model,
599594
arrayField,
600595
field: this,
601-
format: renderFormat ?? format,
602596
cardTypeFor,
603597
});
604598
}
@@ -750,8 +744,8 @@ class Contains<CardT extends FieldDefConstructor> implements Field<CardT, any> {
750744
);
751745
}
752746

753-
component(model: Box<BaseDef>, format: Format): BoxComponent {
754-
return fieldComponent(this, model, format);
747+
component(model: Box<BaseDef>): BoxComponent {
748+
return fieldComponent(this, model);
755749
}
756750
}
757751

@@ -1042,14 +1036,38 @@ class LinksTo<CardT extends CardDefConstructor> implements Field<CardT> {
10421036
return fieldInstance;
10431037
}
10441038

1045-
component(model: Box<CardDef>, format: Format): BoxComponent {
1046-
if (format === 'edit' && !this.computeVia) {
1047-
let innerModel = model.field(
1048-
this.name as keyof BaseDef,
1049-
) as unknown as Box<CardDef | null>;
1050-
return getLinksToEditor(innerModel, this);
1051-
}
1052-
return fieldComponent(this, model, format);
1039+
component(model: Box<CardDef>): BoxComponent {
1040+
let isComputed = !!this.computeVia;
1041+
let fieldName = this.name as keyof CardDef;
1042+
let linksToField = this;
1043+
let getInnerModel = () => {
1044+
let innerModel = model.field(fieldName);
1045+
return innerModel as unknown as Box<CardDef | null>;
1046+
};
1047+
return class LinksToComponent extends GlimmerComponent<{
1048+
Args: { Named: { format?: Format; displayContainer?: boolean } };
1049+
Blocks: {};
1050+
}> {
1051+
<template>
1052+
<DefaultFormatConsumer as |defaultFormat|>
1053+
{{#if (and (eq defaultFormat 'edit') (not isComputed))}}
1054+
{{#let
1055+
(getLinksToEditor (getInnerModel) linksToField)
1056+
as |LinksToEditor|
1057+
}}
1058+
<LinksToEditor />
1059+
{{/let}}
1060+
{{else}}
1061+
{{#let (fieldComponent linksToField model) as |FieldComponent|}}
1062+
<FieldComponent
1063+
@format={{@format}}
1064+
@displayContainer={{@displayContainer}}
1065+
/>
1066+
{{/let}}
1067+
{{/if}}
1068+
</DefaultFormatConsumer>
1069+
</template>
1070+
};
10531071
}
10541072
}
10551073

@@ -1424,28 +1442,16 @@ class LinksToMany<FieldT extends CardDefConstructor>
14241442
return fieldInstances;
14251443
}
14261444

1427-
component(model: Box<CardDef>, format: Format): BoxComponent {
1445+
component(model: Box<CardDef>): BoxComponent {
14281446
let fieldName = this.name as keyof BaseDef;
14291447
let arrayField = model.field(
14301448
fieldName,
14311449
useIndexBasedKey in this.card,
14321450
) as unknown as Box<CardDef[]>;
1433-
let renderFormat: Format | undefined = undefined;
1434-
if (
1435-
format === 'edit' &&
1436-
'isFieldDef' in model.value.constructor &&
1437-
model.value.constructor.isFieldDef
1438-
) {
1439-
renderFormat = 'atom';
1440-
}
1441-
if (format === 'edit' && this.computeVia) {
1442-
renderFormat = 'embedded';
1443-
}
14441451
return getLinksToManyComponent({
14451452
model,
14461453
arrayField,
14471454
field: this,
1448-
format: renderFormat ?? format,
14491455
cardTypeFor,
14501456
});
14511457
}
@@ -1454,7 +1460,6 @@ class LinksToMany<FieldT extends CardDefConstructor>
14541460
function fieldComponent(
14551461
field: Field<typeof BaseDef>,
14561462
model: Box<BaseDef>,
1457-
defaultFormat: Format,
14581463
): BoxComponent {
14591464
let fieldName = field.name as keyof BaseDef;
14601465
let card: typeof BaseDef;
@@ -1465,7 +1470,7 @@ function fieldComponent(
14651470
(model.value[fieldName]?.constructor as typeof BaseDef) ?? field.card;
14661471
}
14671472
let innerModel = model.field(fieldName) as unknown as Box<BaseDef>;
1468-
return getBoxComponent(card, innerModel, field, defaultFormat);
1473+
return getBoxComponent(card, innerModel, field);
14691474
}
14701475

14711476
// our decorators are implemented by Babel, not TypeScript, so they have a
@@ -1643,8 +1648,8 @@ export class BaseDef {
16431648
return _createFromSerialized(this, data, doc, relativeTo, identityContext);
16441649
}
16451650

1646-
static getComponent(card: BaseDef, format: Format, field?: Field) {
1647-
return getComponent(card, format, field);
1651+
static getComponent(card: BaseDef, field?: Field) {
1652+
return getComponent(card, field);
16481653
}
16491654

16501655
static assignInitialFieldValue(
@@ -2712,17 +2717,12 @@ export type SignatureFor<CardT extends BaseDefConstructor> = {
27122717
};
27132718
};
27142719

2715-
export function getComponent(
2716-
model: BaseDef,
2717-
format: Format,
2718-
field?: Field,
2719-
): BoxComponent {
2720+
export function getComponent(model: BaseDef, field?: Field): BoxComponent {
27202721
let box = Box.create(model);
27212722
let boxComponent = getBoxComponent(
27222723
model.constructor as BaseDefConstructor,
27232724
box,
27242725
field,
2725-
format,
27262726
);
27272727
return boxComponent;
27282728
}

‎packages/base/contains-many-component.gts

+94-20
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,22 @@ import {
1010
type FieldDef,
1111
type BaseDef,
1212
} from './card-api';
13-
import { getBoxComponent, getPluralViewComponent } from './field-component';
13+
import { eq } from '@cardstack/boxel-ui/helpers';
14+
import {
15+
type BoxComponentSignature,
16+
getBoxComponent,
17+
DefaultFormatConsumer,
18+
DefaultFormatProvider,
19+
} from './field-component';
1420
import { AddButton, IconButton } from '@cardstack/boxel-ui/components';
1521
import { getPlural } from '@cardstack/runtime-common';
1622
import { IconTrash } from '@cardstack/boxel-ui/icons';
23+
import { TemplateOnlyComponent } from '@ember/component/template-only';
1724

18-
interface Signature {
25+
interface ContainsManyEditorSignature {
1926
Args: {
2027
model: Box<FieldDef>;
2128
arrayField: Box<FieldDef[]>;
22-
format: Format;
2329
field: Field<typeof FieldDef>;
2430
cardTypeFor(
2531
field: Field<typeof BaseDef>,
@@ -28,7 +34,7 @@ interface Signature {
2834
};
2935
}
3036

31-
class ContainsManyEditor extends GlimmerComponent<Signature> {
37+
class ContainsManyEditor extends GlimmerComponent<ContainsManyEditorSignature> {
3238
<template>
3339
<div data-test-contains-many={{@field.name}}>
3440
{{#if @arrayField.children.length}}
@@ -37,11 +43,11 @@ class ContainsManyEditor extends GlimmerComponent<Signature> {
3743
<li class='editor' data-test-item={{i}}>
3844
{{#let
3945
(getBoxComponent
40-
(@cardTypeFor @field boxedElement) @format boxedElement @field
46+
(@cardTypeFor @field boxedElement) boxedElement @field
4147
)
4248
as |Item|
4349
}}
44-
<Item @format={{@format}} />
50+
<Item />
4551
{{/let}}
4652
<div class='remove-button-container'>
4753
<IconButton
@@ -118,33 +124,101 @@ class ContainsManyEditor extends GlimmerComponent<Signature> {
118124
};
119125
}
120126

127+
function adjustFormat(model: Box<FieldDef>, defaultFormat: Format) {
128+
if (
129+
defaultFormat === 'edit' &&
130+
'isFieldDef' in model.value.constructor &&
131+
model.value.constructor.isFieldDef
132+
) {
133+
return 'atom';
134+
}
135+
return defaultFormat;
136+
}
137+
121138
export function getContainsManyComponent({
122139
model,
123140
arrayField,
124-
format,
125141
field,
126142
cardTypeFor,
127143
}: {
128144
model: Box<FieldDef>;
129145
arrayField: Box<FieldDef[]>;
130-
format: Format;
131146
field: Field<typeof FieldDef>;
132147
cardTypeFor(
133148
field: Field<typeof BaseDef>,
134149
boxedElement: Box<BaseDef>,
135150
): typeof BaseDef;
136151
}): BoxComponent {
137-
if (format === 'edit') {
138-
return <template>
139-
<ContainsManyEditor
140-
@model={{model}}
141-
@arrayField={{arrayField}}
142-
@field={{field}}
143-
@format={{format}}
144-
@cardTypeFor={{cardTypeFor}}
145-
/>
152+
let getComponents = () =>
153+
arrayField.children.map((child) =>
154+
getBoxComponent(cardTypeFor(field, child), child, field),
155+
); // Wrap the the components in a function so that the template is reactive to changes in the model (this is essentially a helper)
156+
let containsManyComponent: TemplateOnlyComponent<BoxComponentSignature> =
157+
<template>
158+
<DefaultFormatConsumer as |defaultFormat|>
159+
{{#let (adjustFormat model defaultFormat) as |defaultFormat|}}
160+
<DefaultFormatProvider @value={{defaultFormat}}>
161+
{{#if (eq defaultFormat 'edit')}}
162+
<ContainsManyEditor
163+
@model={{model}}
164+
@arrayField={{arrayField}}
165+
@field={{field}}
166+
@cardTypeFor={{cardTypeFor}}
167+
/>
168+
{{else}}
169+
{{#let (if @format @format defaultFormat) as |format|}}
170+
<div
171+
class='plural-field containsMany-field
172+
{{format}}-format
173+
{{unless arrayField.children.length "empty"}}'
174+
data-test-plural-view={{field.fieldType}}
175+
data-test-plural-view-format={{format}}
176+
>
177+
<DefaultFormatProvider @value={{format}}>
178+
{{#each (getComponents) as |Item i|}}
179+
<div data-test-plural-view-item={{i}}>
180+
<Item />
181+
</div>
182+
{{/each}}
183+
</DefaultFormatProvider>
184+
</div>
185+
{{/let}}
186+
{{/if}}
187+
</DefaultFormatProvider>
188+
{{/let}}
189+
</DefaultFormatConsumer>
190+
<style>
191+
.containsMany-field.atom-format {
192+
padding: var(--boxel-sp-sm);
193+
background-color: var(--boxel-100);
194+
border: none !important;
195+
border-radius: var(--boxel-border-radius);
196+
}
197+
</style>
146198
</template>;
147-
} else {
148-
return getPluralViewComponent(arrayField, field, format, cardTypeFor);
149-
}
199+
return new Proxy(containsManyComponent, {
200+
get(target, property, received) {
201+
// proxying the bare minimum of an Array in order to render within a
202+
// template. add more getters as necessary...
203+
let components = getComponents();
204+
205+
if (property === Symbol.iterator) {
206+
return components[Symbol.iterator];
207+
}
208+
if (property === 'length') {
209+
return components.length;
210+
}
211+
if (typeof property === 'string' && property.match(/\d+/)) {
212+
return components[parseInt(property)];
213+
}
214+
return Reflect.get(target, property, received);
215+
},
216+
getPrototypeOf() {
217+
// This is necessary for Ember to be able to locate the template associated
218+
// with a proxied component. Our Proxy object won't be in the template WeakMap,
219+
// but we can pretend our Proxy object inherits from the true component, and
220+
// Ember's template lookup respects inheritance.
221+
return containsManyComponent;
222+
},
223+
});
150224
}

0 commit comments

Comments
 (0)