diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index 49cedd60a5..518e41d865 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -8,9 +8,13 @@ import { BoxelInput, FieldContainer } from '@cardstack/boxel-ui/components'; import { cn, eq, pick } from '@cardstack/boxel-ui/helpers'; import { on } from '@ember/modifier'; import { startCase } from 'lodash'; -import { getBoxComponent, type BoxComponent } from './field-component'; +import { + getBoxComponent, + type BoxComponent, + DefaultFormatConsumer, +} from './field-component'; import { getContainsManyComponent } from './contains-many-component'; -import { getLinksToEditor } from './links-to-editor'; +import { LinksToEditor } from './links-to-editor'; import { getLinksToManyComponent } from './links-to-many-component'; import { SupportedMimeType, @@ -284,7 +288,7 @@ export interface Field< ): Promise; emptyValue(instance: BaseDef): any; validate(instance: BaseDef, value: any): void; - component(model: Box, defaultFormat: Format): BoxComponent; + component(model: Box): BoxComponent; getter(instance: BaseDef): BaseInstanceType; queryableValue(value: any, stack: BaseDef[]): SearchT; queryMatcher( @@ -578,27 +582,17 @@ class ContainsMany ); } - component(model: Box, format: Format): BoxComponent { + component(model: Box): BoxComponent { let fieldName = this.name as keyof BaseDef; let arrayField = model.field( fieldName, useIndexBasedKey in this.card, ) as unknown as Box; - let renderFormat: Format | undefined = undefined; - if ( - format === 'edit' && - 'isFieldDef' in model.value.constructor && - model.value.constructor.isFieldDef - ) { - renderFormat = 'atom'; - } - return getContainsManyComponent({ model, arrayField, field: this, - format: renderFormat ?? format, cardTypeFor, }); } @@ -750,8 +744,8 @@ class Contains implements Field { ); } - component(model: Box, format: Format): BoxComponent { - return fieldComponent(this, model, format); + component(model: Box): BoxComponent { + return fieldComponent(this, model); } } @@ -1042,14 +1036,40 @@ class LinksTo implements Field { return fieldInstance; } - component(model: Box, format: Format): BoxComponent { - if (format === 'edit' && !this.computeVia) { - let innerModel = model.field( - this.name as keyof BaseDef, - ) as unknown as Box; - return getLinksToEditor(innerModel, this); - } - return fieldComponent(this, model, format); + component(model: Box): BoxComponent { + let isComputed = !!this.computeVia; + let fieldName = this.name as keyof CardDef; + let linksToField = this; + let getInnerModel = () => { + let innerModel = model.field(fieldName); + return innerModel as unknown as Box; + }; + function shouldRenderEditor( + format: Format | undefined, + defaultFormat: Format, + isComputed: boolean, + ) { + return (format ?? defaultFormat) === 'edit' && !isComputed; + } + return class LinksToComponent extends GlimmerComponent<{ + Args: { Named: { format?: Format; displayContainer?: boolean } }; + Blocks: {}; + }> { + + }; } } @@ -1424,28 +1444,16 @@ class LinksToMany return fieldInstances; } - component(model: Box, format: Format): BoxComponent { + component(model: Box): BoxComponent { let fieldName = this.name as keyof BaseDef; let arrayField = model.field( fieldName, useIndexBasedKey in this.card, ) as unknown as Box; - let renderFormat: Format | undefined = undefined; - if ( - format === 'edit' && - 'isFieldDef' in model.value.constructor && - model.value.constructor.isFieldDef - ) { - renderFormat = 'atom'; - } - if (format === 'edit' && this.computeVia) { - renderFormat = 'embedded'; - } return getLinksToManyComponent({ model, arrayField, field: this, - format: renderFormat ?? format, cardTypeFor, }); } @@ -1454,7 +1462,6 @@ class LinksToMany function fieldComponent( field: Field, model: Box, - defaultFormat: Format, ): BoxComponent { let fieldName = field.name as keyof BaseDef; let card: typeof BaseDef; @@ -1465,7 +1472,7 @@ function fieldComponent( (model.value[fieldName]?.constructor as typeof BaseDef) ?? field.card; } let innerModel = model.field(fieldName) as unknown as Box; - return getBoxComponent(card, defaultFormat, innerModel, field); + return getBoxComponent(card, innerModel, field); } // our decorators are implemented by Babel, not TypeScript, so they have a @@ -1643,8 +1650,8 @@ export class BaseDef { return _createFromSerialized(this, data, doc, relativeTo, identityContext); } - static getComponent(card: BaseDef, format: Format, field?: Field) { - return getComponent(card, format, field); + static getComponent(card: BaseDef, field?: Field) { + return getComponent(card, field); } static assignInitialFieldValue( @@ -1862,7 +1869,6 @@ export type BaseDefComponent = ComponentLike<{ cardOrField: typeof BaseDef; fields: any; format: Format; - displayContainer?: boolean; model: any; set: Setter; fieldName: string | undefined; @@ -2712,15 +2718,10 @@ export type SignatureFor = { }; }; -export function getComponent( - model: BaseDef, - format: Format, - field?: Field, -): BoxComponent { +export function getComponent(model: BaseDef, field?: Field): BoxComponent { let box = Box.create(model); let boxComponent = getBoxComponent( model.constructor as BaseDefConstructor, - format, box, field, ); diff --git a/packages/base/contains-many-component.gts b/packages/base/contains-many-component.gts index 083b2f0cf1..4d0e3834c6 100644 --- a/packages/base/contains-many-component.gts +++ b/packages/base/contains-many-component.gts @@ -10,16 +10,20 @@ import { type FieldDef, type BaseDef, } from './card-api'; -import { getBoxComponent, getPluralViewComponent } from './field-component'; +import { + type BoxComponentSignature, + getBoxComponent, + DefaultFormatConsumer, +} from './field-component'; import { AddButton, IconButton } from '@cardstack/boxel-ui/components'; import { getPlural } from '@cardstack/runtime-common'; import { IconTrash } from '@cardstack/boxel-ui/icons'; +import { TemplateOnlyComponent } from '@ember/component/template-only'; -interface Signature { +interface ContainsManyEditorSignature { Args: { model: Box; arrayField: Box; - format: Format; field: Field; cardTypeFor( field: Field, @@ -28,7 +32,7 @@ interface Signature { }; } -class ContainsManyEditor extends GlimmerComponent { +class ContainsManyEditor extends GlimmerComponent { } +interface DefaultFormatConsumerSignature { + Blocks: { default: [Format] }; +} + +export class DefaultFormatConsumer extends Component { + @consume(DefaultFormatContextName) declare defaultFormat: Format | undefined; + + get effectiveDefaultFormat(): Format { + return this.defaultFormat ?? 'isolated'; + } + + +} + +interface DefaultFormatProviderSignature { + Args: { value: Format }; + Blocks: { default: [] }; +} + +export class DefaultFormatProvider extends Component { + @provide(DefaultFormatContextName) + get defaultFormat() { + return this.args.value; + } +} + const componentCache = initSharedState( 'componentCache', () => new WeakMap, BoxComponent>(), ); export function getBoxComponent( - card: typeof BaseDef, - defaultFormat: Format, + cardOrField: typeof BaseDef, model: Box, field: Field | undefined, ): BoxComponent { @@ -70,15 +101,10 @@ export function getBoxComponent( if (stable) { return stable; } - let internalFieldsCache: - | { fields: FieldsTypeFor; format: Format } - | undefined; - - function lookupFormat(userFormat: Format | undefined): { - Implementation: BaseDefComponent; - fields: FieldsTypeFor; - format: Format; - } { + function determineFormat( + userFormat: Format | undefined, + defaultFormat: Format, + ): Format { let format: Format; let availableFormats = formats; let effectiveDefaultFormat = defaultFormat; @@ -94,19 +120,27 @@ export function getBoxComponent( userFormat && availableFormats.includes(userFormat) ? userFormat : effectiveDefaultFormat; + return format; + } + + let internalFieldsCache: + | { fields: FieldsTypeFor; format: Format } + | undefined; + function lookupComponents(effectiveFormat: Format): { + CardOrFieldFormatComponent: BaseDefComponent; + fields: FieldsTypeFor; + } { let fields: FieldsTypeFor; - if (internalFieldsCache?.format === format) { + if (internalFieldsCache?.format === effectiveFormat) { fields = internalFieldsCache.fields; } else { - fields = fieldsComponentsFor({}, model, defaultFieldFormat(format)); - internalFieldsCache = { fields, format }; + fields = fieldsComponentsFor({}, model); + internalFieldsCache = { fields, format: effectiveFormat }; } - return { - Implementation: (card as any)[format], + CardOrFieldFormatComponent: (cardOrField as any)[effectiveFormat], fields, - format, }; } @@ -114,68 +148,74 @@ export function getBoxComponent( Args: { format?: Format; displayContainer?: boolean }; }> = ; - return new Proxy(pluralViewComponent, { - get(target, property, received) { - // proxying the bare minimum of an Array in order to render within a - // template. add more getters as necessary... - let components = getComponents(); - - if (property === Symbol.iterator) { - return components[Symbol.iterator]; - } - if (property === 'length') { - return components.length; - } - if (typeof property === 'string' && property.match(/\d+/)) { - return components[parseInt(property)]; - } - return Reflect.get(target, property, received); - }, - getPrototypeOf() { - // This is necessary for Ember to be able to locate the template associated - // with a proxied component. Our Proxy object won't be in the template WeakMap, - // but we can pretend our Proxy object inherits from the true component, and - // Ember's template lookup respects inheritance. - return pluralViewComponent; - }, - }); -} diff --git a/packages/base/links-to-editor.gts b/packages/base/links-to-editor.gts index bd17b918db..078afb3998 100644 --- a/packages/base/links-to-editor.gts +++ b/packages/base/links-to-editor.gts @@ -4,7 +4,7 @@ import { restartableTask, type EncapsulatedTaskDescriptor as Descriptor, } from 'ember-concurrency'; -import { getBoxComponent } from './field-component'; +import { DefaultFormatProvider, getBoxComponent } from './field-component'; import { type CardDef, type BaseDef, @@ -18,7 +18,6 @@ import { identifyCard, CardContextName, } from '@cardstack/runtime-common'; -import type { ComponentLike } from '@glint/template'; import { AddButton, IconButton } from '@cardstack/boxel-ui/components'; import { IconMinusCircle } from '@cardstack/boxel-ui/icons'; import { consume } from 'ember-provide-consume-context'; @@ -30,7 +29,7 @@ interface Signature { }; } -class LinksToEditor extends GlimmerComponent { +export class LinksToEditor extends GlimmerComponent { @consume(CardContextName) declare cardContext: CardContext;