Skip to content

Commit 3fffd87

Browse files
committedApr 23, 2024
Use dynamic context for defaultFormat
1 parent c49d4d2 commit 3fffd87

15 files changed

+480
-276
lines changed
 

‎packages/base/card-api.gts

+43-46
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ 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';
1111
import { getBoxComponent, type BoxComponent } from './field-component';
@@ -50,6 +50,7 @@ import {
5050
} from '@cardstack/runtime-common';
5151
import type { ComponentLike } from '@glint/template';
5252
import { initSharedState } from './shared-state';
53+
import ContextConsumer from 'ember-provide-consume-context/components/context-consumer';
5354

5455
export { primitive, isField, type BoxComponent };
5556
export const serialize = Symbol.for('cardstack-serialize');
@@ -284,7 +285,7 @@ export interface Field<
284285
): Promise<any>;
285286
emptyValue(instance: BaseDef): any;
286287
validate(instance: BaseDef, value: any): void;
287-
component(model: Box<BaseDef>, defaultFormat: Format): BoxComponent;
288+
component(model: Box<BaseDef>): BoxComponent;
288289
getter(instance: BaseDef): BaseInstanceType<CardT>;
289290
queryableValue(value: any, stack: BaseDef[]): SearchT;
290291
queryMatcher(
@@ -578,27 +579,17 @@ class ContainsMany<FieldT extends FieldDefConstructor>
578579
);
579580
}
580581

581-
component(model: Box<BaseDef>, format: Format): BoxComponent {
582+
component(model: Box<BaseDef>): BoxComponent {
582583
let fieldName = this.name as keyof BaseDef;
583584
let arrayField = model.field(
584585
fieldName,
585586
useIndexBasedKey in this.card,
586587
) as unknown as Box<BaseDef[]>;
587588

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-
597589
return getContainsManyComponent({
598590
model,
599591
arrayField,
600592
field: this,
601-
format: renderFormat ?? format,
602593
cardTypeFor,
603594
});
604595
}
@@ -750,8 +741,8 @@ class Contains<CardT extends FieldDefConstructor> implements Field<CardT, any> {
750741
);
751742
}
752743

753-
component(model: Box<BaseDef>, format: Format): BoxComponent {
754-
return fieldComponent(this, model, format);
744+
component(model: Box<BaseDef>): BoxComponent {
745+
return fieldComponent(this, model);
755746
}
756747
}
757748

@@ -1042,14 +1033,38 @@ class LinksTo<CardT extends CardDefConstructor> implements Field<CardT> {
10421033
return fieldInstance;
10431034
}
10441035

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);
1036+
component(model: Box<CardDef>): BoxComponent {
1037+
let isComputed = !!this.computeVia;
1038+
let fieldName = this.name as keyof CardDef;
1039+
let linksToField = this;
1040+
let getInnerModel = () => {
1041+
let innerModel = model.field(fieldName);
1042+
return innerModel as unknown as Box<CardDef | null>;
1043+
};
1044+
return class LinksToComponent extends GlimmerComponent<{
1045+
Args: { Named: { format?: Format; displayContainer?: boolean } };
1046+
Blocks: {};
1047+
}> {
1048+
<template>
1049+
<ContextConsumer @key='default-format' as |defaultFormat|>
1050+
{{#if (and (eq defaultFormat 'edit') (not isComputed))}}
1051+
{{#let
1052+
(getLinksToEditor (getInnerModel) linksToField)
1053+
as |LinksToEditor|
1054+
}}
1055+
<LinksToEditor />
1056+
{{/let}}
1057+
{{else}}
1058+
{{#let (fieldComponent linksToField model) as |FieldComponent|}}
1059+
<FieldComponent
1060+
@format={{@format}}
1061+
@displayContainer={{@displayContainer}}
1062+
/>
1063+
{{/let}}
1064+
{{/if}}
1065+
</ContextConsumer>
1066+
</template>
1067+
};
10531068
}
10541069
}
10551070

@@ -1424,28 +1439,16 @@ class LinksToMany<FieldT extends CardDefConstructor>
14241439
return fieldInstances;
14251440
}
14261441

1427-
component(model: Box<CardDef>, format: Format): BoxComponent {
1442+
component(model: Box<CardDef>): BoxComponent {
14281443
let fieldName = this.name as keyof BaseDef;
14291444
let arrayField = model.field(
14301445
fieldName,
14311446
useIndexBasedKey in this.card,
14321447
) 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-
}
14441448
return getLinksToManyComponent({
14451449
model,
14461450
arrayField,
14471451
field: this,
1448-
format: renderFormat ?? format,
14491452
cardTypeFor,
14501453
});
14511454
}
@@ -1454,7 +1457,6 @@ class LinksToMany<FieldT extends CardDefConstructor>
14541457
function fieldComponent(
14551458
field: Field<typeof BaseDef>,
14561459
model: Box<BaseDef>,
1457-
defaultFormat: Format,
14581460
): BoxComponent {
14591461
let fieldName = field.name as keyof BaseDef;
14601462
let card: typeof BaseDef;
@@ -1465,7 +1467,7 @@ function fieldComponent(
14651467
(model.value[fieldName]?.constructor as typeof BaseDef) ?? field.card;
14661468
}
14671469
let innerModel = model.field(fieldName) as unknown as Box<BaseDef>;
1468-
return getBoxComponent(card, innerModel, field, defaultFormat);
1470+
return getBoxComponent(card, innerModel, field);
14691471
}
14701472

14711473
// our decorators are implemented by Babel, not TypeScript, so they have a
@@ -1643,8 +1645,8 @@ export class BaseDef {
16431645
return _createFromSerialized(this, data, doc, relativeTo, identityContext);
16441646
}
16451647

1646-
static getComponent(card: BaseDef, format: Format, field?: Field) {
1647-
return getComponent(card, format, field);
1648+
static getComponent(card: BaseDef, field?: Field) {
1649+
return getComponent(card, field);
16481650
}
16491651

16501652
static assignInitialFieldValue(
@@ -2712,17 +2714,12 @@ export type SignatureFor<CardT extends BaseDefConstructor> = {
27122714
};
27132715
};
27142716

2715-
export function getComponent(
2716-
model: BaseDef,
2717-
format: Format,
2718-
field?: Field,
2719-
): BoxComponent {
2717+
export function getComponent(model: BaseDef, field?: Field): BoxComponent {
27202718
let box = Box.create(model);
27212719
let boxComponent = getBoxComponent(
27222720
model.constructor as BaseDefConstructor,
27232721
box,
27242722
field,
2725-
format,
27262723
);
27272724
return boxComponent;
27282725
}

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

+91-20
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,19 @@ 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 { type BoxComponentSignature, getBoxComponent } from './field-component';
1415
import { AddButton, IconButton } from '@cardstack/boxel-ui/components';
1516
import { getPlural } from '@cardstack/runtime-common';
1617
import { IconTrash } from '@cardstack/boxel-ui/icons';
18+
import ContextConsumer from 'ember-provide-consume-context/components/context-consumer';
19+
import ContextProvider from 'ember-provide-consume-context/components/context-provider';
20+
import { TemplateOnlyComponent } from '@ember/component/template-only';
1721

18-
interface Signature {
22+
interface ContainsManyEditorSignature {
1923
Args: {
2024
model: Box<FieldDef>;
2125
arrayField: Box<FieldDef[]>;
22-
format: Format;
2326
field: Field<typeof FieldDef>;
2427
cardTypeFor(
2528
field: Field<typeof BaseDef>,
@@ -28,7 +31,7 @@ interface Signature {
2831
};
2932
}
3033

31-
class ContainsManyEditor extends GlimmerComponent<Signature> {
34+
class ContainsManyEditor extends GlimmerComponent<ContainsManyEditorSignature> {
3235
<template>
3336
<div data-test-contains-many={{@field.name}}>
3437
{{#if @arrayField.children.length}}
@@ -37,11 +40,11 @@ class ContainsManyEditor extends GlimmerComponent<Signature> {
3740
<li class='editor' data-test-item={{i}}>
3841
{{#let
3942
(getBoxComponent
40-
(@cardTypeFor @field boxedElement) @format boxedElement @field
43+
(@cardTypeFor @field boxedElement) boxedElement @field
4144
)
4245
as |Item|
4346
}}
44-
<Item @format={{@format}} />
47+
<Item />
4548
{{/let}}
4649
<div class='remove-button-container'>
4750
<IconButton
@@ -118,33 +121,101 @@ class ContainsManyEditor extends GlimmerComponent<Signature> {
118121
};
119122
}
120123

124+
function adjustFormat(model: Box<FieldDef>, defaultFormat: Format) {
125+
if (
126+
defaultFormat === 'edit' &&
127+
'isFieldDef' in model.value.constructor &&
128+
model.value.constructor.isFieldDef
129+
) {
130+
return 'atom';
131+
}
132+
return defaultFormat;
133+
}
134+
121135
export function getContainsManyComponent({
122136
model,
123137
arrayField,
124-
format,
125138
field,
126139
cardTypeFor,
127140
}: {
128141
model: Box<FieldDef>;
129142
arrayField: Box<FieldDef[]>;
130-
format: Format;
131143
field: Field<typeof FieldDef>;
132144
cardTypeFor(
133145
field: Field<typeof BaseDef>,
134146
boxedElement: Box<BaseDef>,
135147
): typeof BaseDef;
136148
}): 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-
/>
149+
let getComponents = () =>
150+
arrayField.children.map((child) =>
151+
getBoxComponent(cardTypeFor(field, child), child, field),
152+
); // Wrap the the components in a function so that the template is reactive to changes in the model (this is essentially a helper)
153+
let containsManyComponent: TemplateOnlyComponent<BoxComponentSignature> =
154+
<template>
155+
<ContextConsumer @key='default-format' as |defaultFormat|>
156+
{{#let (adjustFormat model defaultFormat) as |defaultFormat|}}
157+
<ContextProvider @key='default-format' @value={{defaultFormat}}>
158+
{{#if (eq defaultFormat 'edit')}}
159+
<ContainsManyEditor
160+
@model={{model}}
161+
@arrayField={{arrayField}}
162+
@field={{field}}
163+
@cardTypeFor={{cardTypeFor}}
164+
/>
165+
{{else}}
166+
{{#let (if @format @format defaultFormat) as |format|}}
167+
<div
168+
class='plural-field containsMany-field
169+
{{format}}-format
170+
{{unless arrayField.children.length "empty"}}'
171+
data-test-plural-view={{field.fieldType}}
172+
data-test-plural-view-format={{format}}
173+
>
174+
<ContextProvider @key='default-format' @value={{format}}>
175+
{{#each (getComponents) as |Item i|}}
176+
<div data-test-plural-view-item={{i}}>
177+
<Item />
178+
</div>
179+
{{/each}}
180+
</ContextProvider>
181+
</div>
182+
{{/let}}
183+
{{/if}}
184+
</ContextProvider>
185+
{{/let}}
186+
</ContextConsumer>
187+
<style>
188+
.containsMany-field.atom-format {
189+
padding: var(--boxel-sp-sm);
190+
background-color: var(--boxel-100);
191+
border: none !important;
192+
border-radius: var(--boxel-border-radius);
193+
}
194+
</style>
146195
</template>;
147-
} else {
148-
return getPluralViewComponent(arrayField, field, format, cardTypeFor);
149-
}
196+
return new Proxy(containsManyComponent, {
197+
get(target, property, received) {
198+
// proxying the bare minimum of an Array in order to render within a
199+
// template. add more getters as necessary...
200+
let components = getComponents();
201+
202+
if (property === Symbol.iterator) {
203+
return components[Symbol.iterator];
204+
}
205+
if (property === 'length') {
206+
return components.length;
207+
}
208+
if (typeof property === 'string' && property.match(/\d+/)) {
209+
return components[parseInt(property)];
210+
}
211+
return Reflect.get(target, property, received);
212+
},
213+
getPrototypeOf() {
214+
// This is necessary for Ember to be able to locate the template associated
215+
// with a proxied component. Our Proxy object won't be in the template WeakMap,
216+
// but we can pretend our Proxy object inherits from the true component, and
217+
// Ember's template lookup respects inheritance.
218+
return containsManyComponent;
219+
},
220+
});
150221
}

0 commit comments

Comments
 (0)
Failed to load comments.