From adb9af9016e4ac79573a73423507c38b4d5e1fb5 Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Thu, 18 Apr 2024 17:32:58 -0400 Subject: [PATCH 1/3] Remove unused "renderedIn" from StackItem context --- packages/base/card-api.gts | 1 - packages/host/app/components/operator-mode/stack-item.gts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index d06e520c6a..981c78de44 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -116,7 +116,6 @@ export interface CardContext { }; }; }>; - renderedIn?: Component; } function isNotLoadedValue(val: any): val is NotLoadedValue { diff --git a/packages/host/app/components/operator-mode/stack-item.gts b/packages/host/app/components/operator-mode/stack-item.gts index 627dbfa6e5..68589e006d 100644 --- a/packages/host/app/components/operator-mode/stack-item.gts +++ b/packages/host/app/components/operator-mode/stack-item.gts @@ -175,7 +175,6 @@ export default class OperatorModeStackItem extends Component { private get context() { return { - renderedIn: this as Component, cardComponentModifier: this.cardTracker.trackElement, actions: this.args.publicAPI, }; From 0b48d91318ad6399f536b6412b9d1071f4bc79f3 Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Thu, 18 Apr 2024 17:33:15 -0400 Subject: [PATCH 2/3] Add ember-provide-consume-context addon --- packages/host/package.json | 1 + pnpm-lock.yaml | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/host/package.json b/packages/host/package.json index f5b5ec82d0..faf0e780c4 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -114,6 +114,7 @@ "ember-modifier": "^4.1.0", "ember-moment": "^10.0.0", "ember-page-title": "^8.2.3", + "ember-provide-consume-context": "^0.3.1", "ember-qunit": "^8.0.1", "ember-resolver": "^11.0.1", "ember-resources": "^6.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 409ad88776..08415fd91f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1243,6 +1243,9 @@ importers: ember-page-title: specifier: ^8.2.3 version: 8.2.3(ember-source@5.4.1) + ember-provide-consume-context: + specifier: ^0.3.1 + version: 0.3.1(@babel/core@7.24.3)(@ember/test-helpers@3.3.0)(ember-source@5.4.1) ember-qunit: specifier: ^8.0.1 version: 8.0.2(@ember/test-helpers@3.3.0)(@glint/template@1.3.0)(ember-source@5.4.1)(qunit@2.20.1) @@ -12085,6 +12088,21 @@ packages: - supports-color dev: false + /ember-provide-consume-context@0.3.1(@babel/core@7.24.3)(@ember/test-helpers@3.3.0)(ember-source@5.4.1): + resolution: {integrity: sha512-bit/W23qNto4uO2H+O9NPK1klO1FwLhzwN9Goo/5cdKHKM4u0G6QFfY/vEHMaHIu97Exh1MMFtLBx5y9iZTPmw==} + peerDependencies: + '@ember/test-helpers': ^2.9.1 || ^3.0.0 + ember-source: ^4.8.0 || ^5.0.0 + dependencies: + '@ember/test-helpers': 3.3.0(@glint/template@1.3.0)(ember-source@5.4.1)(webpack@5.89.0) + '@embroider/addon-shim': 1.8.7 + '@glimmer/component': 1.1.2(@babel/core@7.24.3) + ember-source: 5.4.1(@babel/core@7.24.3)(@glimmer/component@1.1.2)(@glint/template@1.3.0)(rsvp@4.8.5)(webpack@5.89.0) + transitivePeerDependencies: + - '@babel/core' + - supports-color + dev: true + /ember-qunit@5.1.2(@ember/test-helpers@2.9.3)(ember-source@3.27.0)(qunit@2.19.4): resolution: {integrity: sha512-mrnFaUhAJWkJWeRZKajLuuRmKsifRrhAla1sQAfIiuh7OCVY1eqFvSUFzLR7/WgdNwPrUzEOOx39MdVRZBd/7A==} engines: {node: 10.* || 12.* || >= 14.*} From 91cd9f756bc468e348ca0ac3a04651da0a1528d3 Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Mon, 22 Apr 2024 10:29:40 -0400 Subject: [PATCH 3/3] Use dynamic context for "card context" --- packages/base/card-api.gts | 52 ++---- packages/base/field-component.gts | 158 ++++++++++-------- packages/base/links-to-editor.gts | 13 +- packages/base/links-to-many-component.gts | 20 +-- packages/base/package.json | 1 + .../app/components/card-catalog/index.gts | 4 +- .../host/app/components/card-catalog/item.gts | 3 - .../app/components/card-catalog/modal.gts | 7 +- .../components/operator-mode/stack-item.gts | 10 +- packages/host/app/components/preview.gts | 3 - packages/host/app/lib/externals.ts | 5 + .../host/tests/helpers/render-component.ts | 4 +- .../tests/integration/search-index-test.gts | 1 + packages/host/types/global.d.ts | 9 + packages/realm-server/lib/externals.ts | 8 + packages/runtime-common/constants.ts | 2 + pnpm-lock.yaml | 4 +- 17 files changed, 154 insertions(+), 150 deletions(-) diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index 981c78de44..49cedd60a5 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -22,6 +22,7 @@ import { isNotLoadedError, isNotReadyError, CardError, + CardContextName, NotLoaded, NotReady, getField, @@ -283,11 +284,7 @@ export interface Field< ): Promise; emptyValue(instance: BaseDef): any; validate(instance: BaseDef, value: any): void; - component( - model: Box, - defaultFormat: Format, - context?: CardContext, - ): BoxComponent; + component(model: Box, defaultFormat: Format): BoxComponent; getter(instance: BaseDef): BaseInstanceType; queryableValue(value: any, stack: BaseDef[]): SearchT; queryMatcher( @@ -753,12 +750,8 @@ class Contains implements Field { ); } - component( - model: Box, - format: Format, - context?: CardContext, - ): BoxComponent { - return fieldComponent(this, model, format, context); + component(model: Box, format: Format): BoxComponent { + return fieldComponent(this, model, format); } } @@ -1049,18 +1042,14 @@ class LinksTo implements Field { return fieldInstance; } - component( - model: Box, - format: Format, - context?: CardContext, - ): BoxComponent { + 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, context); + return getLinksToEditor(innerModel, this); } - return fieldComponent(this, model, format, context); + return fieldComponent(this, model, format); } } @@ -1435,11 +1424,7 @@ class LinksToMany return fieldInstances; } - component( - model: Box, - format: Format, - context?: CardContext, - ): BoxComponent { + component(model: Box, format: Format): BoxComponent { let fieldName = this.name as keyof BaseDef; let arrayField = model.field( fieldName, @@ -1462,7 +1447,6 @@ class LinksToMany field: this, format: renderFormat ?? format, cardTypeFor, - context, }); } } @@ -1471,7 +1455,6 @@ function fieldComponent( field: Field, model: Box, defaultFormat: Format, - context?: CardContext, ): BoxComponent { let fieldName = field.name as keyof BaseDef; let card: typeof BaseDef; @@ -1482,7 +1465,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, context); + return getBoxComponent(card, defaultFormat, innerModel, field); } // our decorators are implemented by Babel, not TypeScript, so they have a @@ -1660,13 +1643,8 @@ export class BaseDef { return _createFromSerialized(this, data, doc, relativeTo, identityContext); } - static getComponent( - card: BaseDef, - format: Format, - field?: Field, - context?: CardContext, - ) { - return getComponent(card, format, field, context); + static getComponent(card: BaseDef, format: Format, field?: Field) { + return getComponent(card, format, field); } static assignInitialFieldValue( @@ -2738,7 +2716,6 @@ export function getComponent( model: BaseDef, format: Format, field?: Field, - context?: CardContext, ): BoxComponent { let box = Box.create(model); let boxComponent = getBoxComponent( @@ -2746,7 +2723,6 @@ export function getComponent( format, box, field, - context, ); return boxComponent; } @@ -3101,3 +3077,9 @@ type ElementType = T extends (infer V)[] ? V : never; function makeRelativeURL(maybeURL: string, opts?: SerializeOpts): string { return opts?.maybeRelativeURL ? opts.maybeRelativeURL(maybeURL) : maybeURL; } + +declare module 'ember-provide-consume-context/context-registry' { + export default interface ContextRegistry { + [CardContextName]: CardContext; + } +} diff --git a/packages/base/field-component.gts b/packages/base/field-component.gts index 6eca02d93b..b2cfe5e481 100644 --- a/packages/base/field-component.gts +++ b/packages/base/field-component.gts @@ -12,12 +12,14 @@ import { isCompoundField, formats, } from './card-api'; -import { getField } from '@cardstack/runtime-common'; +import { CardContextName, getField } from '@cardstack/runtime-common'; import type { ComponentLike } from '@glint/template'; import { CardContainer } from '@cardstack/boxel-ui/components'; import Modifier from 'ember-modifier'; import { initSharedState } from './shared-state'; import { eq } from '@cardstack/boxel-ui/helpers'; +import { consume } from 'ember-provide-consume-context'; +import Component from '@glimmer/component'; interface BoxComponentSignature { Args: { Named: { format?: Format; displayContainer?: boolean } }; @@ -26,6 +28,33 @@ interface BoxComponentSignature { export type BoxComponent = ComponentLike; +interface CardContextConsumerSignature { + Blocks: { default: [CardContext] }; +} + +// cardComponentModifier, when provided, is used for the host environment to get access to card's rendered elements +const DEFAULT_CARD_CONTEXT = { + cardComponentModifier: class NoOpModifier extends Modifier { + modify() {} + }, + actions: undefined, +}; + +export class CardContextConsumer extends Component { + @consume(CardContextName) declare dynamicCardContext: CardContext; + + get context(): CardContext { + return { + ...DEFAULT_CARD_CONTEXT, + ...this.dynamicCardContext, + }; + } + + +} + const componentCache = initSharedState( 'componentCache', () => new WeakMap, BoxComponent>(), @@ -36,7 +65,6 @@ export function getBoxComponent( defaultFormat: Format, model: Box, field: Field | undefined, - context: CardContext = {}, ): BoxComponent { let stable = componentCache.get(model); if (stable) { @@ -46,13 +74,6 @@ export function getBoxComponent( | { fields: FieldsTypeFor; format: Format } | undefined; - // cardComponentModifier, when provided, is used for the host environment to get access to card's rendered elements - let cardComponentModifier = - context.cardComponentModifier ?? - class NoOpModifier extends Modifier { - modify() {} - }; - function lookupFormat(userFormat: Format | undefined): { Implementation: BaseDefComponent; fields: FieldsTypeFor; @@ -78,12 +99,7 @@ export function getBoxComponent( if (internalFieldsCache?.format === format) { fields = internalFieldsCache.fields; } else { - fields = fieldsComponentsFor( - {}, - model, - defaultFieldFormat(format), - context, - ); + fields = fieldsComponentsFor({}, model, defaultFieldFormat(format)); internalFieldsCache = { fields, format }; } @@ -97,26 +113,57 @@ export function getBoxComponent( let component: TemplateOnlyComponent<{ Args: { format?: Format; displayContainer?: boolean }; }> =