diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index 273c39401e..312cd7d7e5 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -16,7 +16,6 @@ import { getLinksToManyComponent } from './links-to-many-component'; import { assertIsSerializerName, baseRef, - baseRealm, CardContextName, CardError, CodeRef, @@ -32,12 +31,12 @@ import { getSerializer, humanReadable, identifyCard, - normalizeCodeRef, isBaseInstance, isCardError, isCardInstance as _isCardInstance, isCardResource, isFileMetaResource, + isFileDef, isField, isFieldInstance, isRelationship, @@ -170,6 +169,7 @@ export { getFields, isCard, isField, + isFileDef, localId, meta, primitive, @@ -336,28 +336,6 @@ export function instanceOf(instance: BaseDef, clazz: typeof BaseDef): boolean { return false; } -export function isFileDefConstructor(card: typeof BaseDef): boolean { - let baseFileDefRef = { - module: `${baseRealm.url}file-api`, - name: 'FileDef', - }; - let current: typeof BaseDef | undefined = card; - while (current) { - let ref = identifyCard(current); - if (ref) { - let normalized = normalizeCodeRef(ref); - if ( - normalized.module === baseFileDefRef.module && - normalized.name === baseFileDefRef.name - ) { - return true; - } - } - current = getAncestor(current) as typeof BaseDef | undefined; - } - return false; -} - class Logger { private promises: Promise[] = []; @@ -1090,7 +1068,7 @@ class LinksTo implements Field { visited: Set, opts?: SerializeOpts, ) { - let relationshipType = isFileDefConstructor(this.card as typeof BaseDef) + let relationshipType = isFileDef(this.card) ? FileMetaResourceType : CardResourceType; if (isNotLoadedValue(value)) { @@ -1100,6 +1078,10 @@ class LinksTo implements Field { links: { self: makeRelativeURL(value.reference, opts), }, + data: { + type: relationshipType, + id: makeRelativeURL(value.reference, opts), + }, }, }, }; @@ -1113,7 +1095,7 @@ class LinksTo implements Field { }, }; } - if (isFileDefConstructor(this.card as typeof BaseDef) && !value.id) { + if (isFileDef(this.card) && !value.id) { throw new Error( `linksTo field '${this.name}' cannot serialize a FileDef without an id`, ); @@ -1269,7 +1251,7 @@ class LinksTo implements Field { if (isNotLoadedValue(value)) { return value; } - if (isFileDefConstructor(this.card as typeof BaseDef) && !value.id) { + if (isFileDef(this.card) && !value.id) { throw new Error( `field validation error: the linksTo field '${this.name}' cannot reference a FileDef without an id`, ); @@ -1313,14 +1295,13 @@ class LinksTo implements Field { let innerModel = model.field(fieldName); return innerModel as unknown as Box; }; - let isFileDef = isFileDefConstructor(linksToField.card as typeof BaseDef); + let isFileDefField = isFileDef(linksToField.card); function shouldRenderEditor( format: Format | undefined, defaultFormat: Format, isComputed: boolean, - isFileDef: boolean, ) { - return (format ?? defaultFormat) === 'edit' && !isComputed && !isFileDef; + return (format ?? defaultFormat) === 'edit' && !isComputed; } function getChildFormat( format: Format | undefined, @@ -1354,9 +1335,7 @@ class LinksTo implements Field { {{#if - (shouldRenderEditor - @format defaultFormats.cardDef isComputed isFileDef - ) + (shouldRenderEditor @format defaultFormats.cardDef isComputed) }} implements Field { @format defaultFormats.cardDef model - isFileDef + isFileDefField }} @displayContainer={{@displayContainer}} ...attributes @@ -1583,7 +1562,7 @@ class LinksToMany throw new Error(`Expected array for field value ${this.name}`); } - let relationshipType = isFileDefConstructor(this.card as typeof BaseDef) + let relationshipType = isFileDef(this.card) ? FileMetaResourceType : CardResourceType; let relationships: Record = {}; @@ -1606,7 +1585,7 @@ class LinksToMany }; return; } - if (isFileDefConstructor(this.card as typeof BaseDef) && !value.id) { + if (isFileDef(this.card) && !value.id) { throw new Error( `linksToMany field '${this.name}' cannot serialize a FileDef without an id`, ); @@ -1818,7 +1797,7 @@ class LinksToMany if ( !isNotLoadedValue(value) && value != null && - isFileDefConstructor(expectedCard as typeof BaseDef) && + isFileDef(expectedCard) && !value.id ) { throw new Error( @@ -2773,7 +2752,7 @@ function lazilyLoadLink( ), ); (async () => { - let isFileLink = isFileDefConstructor(field.card as typeof BaseDef); + let isFileLink = isFileDef(field.card); try { let fieldValue: CardDef | FileDef; if (isFileLink) { @@ -2840,6 +2819,11 @@ function lazilyLoadLink( isFileLink || reference.endsWith('.json') ? reference : `${reference}.json`; + if (isMissingFile) { + console.warn( + `[linksTo missing file] field=${field.name} reference=${reference} referenceForMissingFile=${referenceForMissingFile} isFileLink=${isFileLink}`, + ); + } let payloadError: { title: string; status: number; @@ -2859,6 +2843,12 @@ function lazilyLoadLink( if (isCardError(error) && error.deps?.length) { payloadError.deps = [...new Set(error.deps)]; } + if (isMissingFile) { + let missingDep = isFileLink ? reference : referenceForMissingFile; + payloadError.deps = [ + ...new Set([...(payloadError.deps ?? []), missingDep]), + ]; + } let payload = JSON.stringify({ type: 'error', error: payloadError, diff --git a/packages/base/links-to-editor.gts b/packages/base/links-to-editor.gts index d451f07b60..3a70f46a2d 100644 --- a/packages/base/links-to-editor.gts +++ b/packages/base/links-to-editor.gts @@ -10,16 +10,17 @@ import { getBoxComponent, } from './field-component'; import { - type CardDef, type BaseDef, type Box, type Field, type CardContext, type LinkableDefConstructor, CreateCardFn, + isFileDef, } from './card-api'; import { chooseCard, + chooseFile, baseCardRef, identifyCard, CardContextName, @@ -37,7 +38,7 @@ import { hash } from '@ember/helper'; interface Signature { Element: HTMLElement; Args: { - model: Box; + model: Box; field: Field; typeConstraint?: ResolvedCodeRef; createCard?: CreateCardFn; @@ -166,6 +167,14 @@ export class LinksToEditor extends GlimmerComponent { } private chooseCard = restartableTask(async () => { + if (isFileDef(this.args.field.card)) { + let file = await chooseFile(); + if (file) { + this.args.model.value = file; + } + return; + } + let type = identifyCard(this.args.field.card) ?? baseCardRef; if (this.args.typeConstraint) { type = await getNarrowestType(this.args.typeConstraint, type, myLoader()); diff --git a/packages/base/links-to-many-component.gts b/packages/base/links-to-many-component.gts index 2a6b173fd2..d33e5bc920 100644 --- a/packages/base/links-to-many-component.gts +++ b/packages/base/links-to-many-component.gts @@ -13,7 +13,7 @@ import { type LinkableDefConstructor, CreateCardFn, CardCrudFunctions, - isFileDefConstructor, + isFileDef, } from './card-api'; import { BoxComponentSignature, @@ -482,9 +482,8 @@ function shouldRenderEditor( format: Format | undefined, defaultFormat: Format, isComputed: boolean, - isFileDef: boolean, ) { - return (format ?? defaultFormat) === 'edit' && !isComputed && !isFileDef; + return (format ?? defaultFormat) === 'edit' && !isComputed; } const componentCache = initSharedState( 'linksToManyComponentCache', @@ -515,15 +514,11 @@ export function getLinksToManyComponent({ getBoxComponent(cardTypeFor(field, child), child, field), ); // Wrap the the components in a function so that the template is reactive to changes in the model (this is essentially a helper) let isComputed = !!field.computeVia || !!field.queryDefinition; - let isFileDef = isFileDefConstructor(field.card as typeof BaseDef); + let isFileDefField = isFileDef(field.card); let linksToManyComponent = class LinksToManyComponent extends GlimmerComponent {