diff --git a/packages/base/avif-image-def.gts b/packages/base/avif-image-def.gts index fbf86464df7..62a83990617 100644 --- a/packages/base/avif-image-def.gts +++ b/packages/base/avif-image-def.gts @@ -1,5 +1,5 @@ import { readFirstBytes } from '@cardstack/runtime-common'; -import { ImageDef } from './image-file-def'; +import ImageDef from './image-file-def'; import { type ByteStream, type SerializedFile } from './file-api'; import { extractAvifDimensions } from './avif-meta-extractor'; diff --git a/packages/base/brand-guide.gts b/packages/base/brand-guide.gts index 4d091d2cf17..f8b1a250e8f 100644 --- a/packages/base/brand-guide.gts +++ b/packages/base/brand-guide.gts @@ -1081,8 +1081,10 @@ export default class BrandGuide extends DetailedStyleRef { @field cardThumbnailURL = contains(StringField, { computeVia: function (this: BrandGuide) { - return this.cardInfo?.cardThumbnailURL?.length - ? this.cardInfo?.cardThumbnailURL + let thumbnailURL = + this.cardInfo?.cardThumbnail?.url ?? this.cardInfo?.cardThumbnailURL; + return thumbnailURL?.length + ? thumbnailURL : this.markUsage?.socialMediaProfileIcon; }, }); diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index ff7bf2d30e9..3d7dea9f99f 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -21,6 +21,7 @@ import { CodeRef, CommandContext, Deferred, + byteStreamToUint8Array, fields, fieldSerializer, fieldsUntracked, @@ -31,6 +32,7 @@ import { getSerializer, humanReadable, identifyCard, + inferContentType, isBaseInstance, isCardError, isCardInstance as _isCardInstance, @@ -51,6 +53,7 @@ import { relativeTo, SingleCardDocument, uuidv4, + NumberSerializer, type Format, type Meta, type CardFields, @@ -102,7 +105,13 @@ import DefaultHeadTemplate from './default-templates/head'; import MissingTemplate from './default-templates/missing-template'; import FieldDefEditTemplate from './default-templates/field-edit'; import MarkdownTemplate from './default-templates/markdown'; +import FileDefEditTemplate from './default-templates/file-def-edit'; +import ImageDefAtomTemplate from './default-templates/image-def-atom'; +import ImageDefEmbeddedTemplate from './default-templates/image-def-embedded'; +import ImageDefFittedTemplate from './default-templates/image-def-fitted'; +import ImageDefIsolatedTemplate from './default-templates/image-def-isolated'; import CaptionsIcon from '@cardstack/boxel-icons/captions'; +import FileIcon from '@cardstack/boxel-icons/file'; import LetterCaseIcon from '@cardstack/boxel-icons/letter-case'; import MarkdownIcon from '@cardstack/boxel-icons/align-box-left-middle'; import RectangleEllipsisIcon from '@cardstack/boxel-icons/rectangle-ellipsis'; @@ -111,9 +120,11 @@ import ThemeIcon from '@cardstack/boxel-icons/palette'; import ImportIcon from '@cardstack/boxel-icons/import'; import FilePencilIcon from '@cardstack/boxel-icons/file-pencil'; import WandIcon from '@cardstack/boxel-icons/wand'; +import HashIcon from '@cardstack/boxel-icons/hash'; // normalizeEnumOptions used by enum moved to packages/base/enum.gts import PatchThemeCommand from '@cardstack/boxel-host/commands/patch-theme'; import CopyAndEditCommand from '@cardstack/boxel-host/commands/copy-and-edit'; +import { md5 } from 'super-fast-md5'; import { callSerializeHook, @@ -153,13 +164,14 @@ import { setRealmContextOnField, type NotLoadedValue, } from './field-support'; +import { TextInputValidator } from './text-input-validator'; import { type GetMenuItemParams, getDefaultCardMenuItems } from './menu-items'; +import { getDefaultFileMenuItems } from './file-menu-items'; import { LinkableDocument, SingleFileMetaDocument, } from '@cardstack/runtime-common/document-types'; import type { FileMetaResource } from '@cardstack/runtime-common'; -import type { FileDef } from './file-api'; export const BULK_GENERATED_ITEM_COUNT = 3; @@ -253,6 +265,17 @@ export type FieldFormats = { }; type Setter = (value: any) => void; +export type SerializedFile = { + sourceUrl: string; + url: string; + name: string; + contentType: string; + contentHash?: string; + contentSize?: number; +} & Extra; + +export type ByteStream = ReadableStream | Uint8Array; + interface Options { computeVia?: () => unknown; description?: string; @@ -2204,7 +2227,10 @@ export class BaseDef { if (isNotLoadedValue(rawValue)) { let normalizedId = rawValue.reference; if (value[relativeTo]) { - normalizedId = resolveCardReference(normalizedId, value[relativeTo]); + normalizedId = resolveCardReference( + normalizedId, + value[relativeTo], + ); } return [fieldName, { id: makeAbsoluteURL(rawValue.reference) }]; } @@ -2489,10 +2515,206 @@ export class MarkdownField extends StringField { }; } +export function deserializeForUI(value: string | number | null): number | null { + let validationError = NumberSerializer.validate(value); + if (validationError) { + return null; + } + + return NumberSerializer.deserializeSync(value); +} + +export function serializeForUI(val: number | null): string | undefined { + let serialized = NumberSerializer.serialize(val); + if (serialized != null) { + return String(serialized); + } + return undefined; +} + +export class NumberField extends FieldDef { + static displayName = 'Number'; + static icon = HashIcon; + static [primitive]: number; + static [fieldSerializer] = 'number'; + static [useIndexBasedKey]: never; + static embedded = class View extends Component { + + }; + static atom = this.embedded; + + static edit = class Edit extends Component { + + + textInputValidator: TextInputValidator = new TextInputValidator( + () => this.args.model, + (inputVal) => this.args.set(inputVal), + deserializeForUI, + serializeForUI, + NumberSerializer.validate, + ); + }; +} + +export interface SerializedFileDef { + url?: string; + sourceUrl: string; + name?: string; + contentHash?: string; + contentSize?: number; + contentType?: string; + content?: string; + error?: string; +} + +// Throw this error from extractAttributes when the file content doesn't match this FileDef's +// expectations so the extractor can fall back to a superclass/base FileDef. +export class FileContentMismatchError extends Error { + name = 'FileContentMismatchError'; +} + +export class FileDef extends BaseDef { + static displayName = 'File'; + static isFileDef = true; + static icon = FileIcon; + [isSavedInstance] = true; + + get [realmURL](): URL | undefined { + let realmURLString = getCardMeta(this, 'realmURL'); + return realmURLString ? new URL(realmURLString) : undefined; + } + + static assignInitialFieldValue( + instance: BaseDef, + fieldName: string, + value: any, + ) { + if (fieldName === 'id') { + // Similar to CardDef, set 'id' directly in the deserialized cache + // to avoid triggering recomputes during instantiation + let deserialized = getDataBucket(instance); + deserialized.set('id', value); + } else { + super.assignInitialFieldValue(instance, fieldName, value); + } + } + + @field id = contains(ReadOnlyField); + @field sourceUrl = contains(StringField); + @field url = contains(StringField); + @field name = contains(StringField); + @field contentType = contains(StringField); + @field contentHash = contains(StringField); + @field contentSize = contains(NumberField); + + static embedded: BaseDefComponent = class View extends Component< + typeof this + > { + + }; + static fitted = this.embedded; + static isolated = this.embedded; + static atom = this.embedded; + static edit: BaseDefComponent = FileDefEditTemplate; + + static async extractAttributes( + url: string, + getStream: () => Promise, + options: { contentHash?: string; contentSize?: number } = {}, + ): Promise { + let parsed = new URL(url); + let name = decodeURIComponent( + parsed.pathname.split('/').pop() ?? parsed.pathname, + ); + let contentType = inferContentType(name); + let contentHash: string | undefined = options.contentHash; + let contentSize: number | undefined = options.contentSize; + if (!contentHash || contentSize === undefined) { + let bytes = await byteStreamToUint8Array(await getStream()); + if (!contentHash) { + try { + contentHash = md5(bytes); + } catch { + contentHash = md5(new TextDecoder().decode(bytes)); + } + } + if (contentSize === undefined) { + contentSize = bytes.byteLength; + } + } + + return { + sourceUrl: url, + url, + name, + contentType, + contentHash, + contentSize, + }; + } + + serialize() { + return { + sourceUrl: this.sourceUrl, + url: this.url, + name: this.name, + contentType: this.contentType, + contentHash: this.contentHash, + contentSize: this.contentSize, + }; + } + + [getMenuItems](params: GetMenuItemParams): MenuItemOptions[] { + return getDefaultFileMenuItems(this, params); + } +} + +export function createFileDef({ + url, + sourceUrl, + name, + contentType, + contentHash, + contentSize, +}: SerializedFileDef) { + return new FileDef({ + url, + sourceUrl, + name, + contentType, + contentHash, + contentSize, + }); +} + +export { getDefaultFileMenuItems } from './file-menu-items'; + +export class ImageDef extends FileDef { + static displayName = 'Image'; + static acceptTypes = 'image/*'; + + @field width = contains(NumberField); + @field height = contains(NumberField); + + static isolated: BaseDefComponent = ImageDefIsolatedTemplate; + static atom: BaseDefComponent = ImageDefAtomTemplate; + static embedded: BaseDefComponent = ImageDefEmbeddedTemplate; + static fitted: BaseDefComponent = ImageDefFittedTemplate; +} + export class CardInfoField extends FieldDef { static displayName = 'Card Info'; @field name = contains(StringField); @field summary = contains(StringField); + @field cardThumbnail = linksTo(() => ImageDef); @field cardThumbnailURL = contains(MaybeBase64Field); @field theme = linksTo(() => Theme); @field notes = contains(MarkdownField); diff --git a/packages/base/default-templates/card-info.gts b/packages/base/default-templates/card-info.gts index 108797f08fb..80bf3ec11ae 100644 --- a/packages/base/default-templates/card-info.gts +++ b/packages/base/default-templates/card-info.gts @@ -6,6 +6,7 @@ import CaptionsIcon from '@cardstack/boxel-icons/captions'; import NameIcon from '@cardstack/boxel-icons/folder-pen'; import SummaryIcon from '@cardstack/boxel-icons/notepad-text'; import LinkIcon from '@cardstack/boxel-icons/link'; +import ImageIcon from '@cardstack/boxel-icons/image'; import ThemeIcon from '@cardstack/boxel-icons/palette'; import type { CardOrFieldTypeIcon, CardDef, FieldsTypeFor } from '../card-api'; @@ -217,6 +218,14 @@ class CardInfoEditor extends GlimmerComponent { {{#if this.isThumbnailEditorVisible}}
+ + <@fields.cardInfo.cardThumbnail /> + { private get showThumbnailPlaceholder() { return ( + !this.args.model?.cardInfo?.cardThumbnail && !this.args.model?.cardInfo?.cardThumbnailURL && this.args.model?.cardThumbnailURL ); diff --git a/packages/base/default-templates/file-def-edit.gts b/packages/base/default-templates/file-def-edit.gts new file mode 100644 index 00000000000..3b99ff3cfc8 --- /dev/null +++ b/packages/base/default-templates/file-def-edit.gts @@ -0,0 +1,27 @@ +import GlimmerComponent from '@glimmer/component'; +import { concat } from '@ember/helper'; +import type { FileDef } from '../card-api'; + +export default class FileDefEditTemplate extends GlimmerComponent<{ + Args: { + model: FileDef; + }; +}> { + +} diff --git a/packages/base/default-templates/head.gts b/packages/base/default-templates/head.gts index 75a75bc178d..6a93fbdb0db 100644 --- a/packages/base/default-templates/head.gts +++ b/packages/base/default-templates/head.gts @@ -19,7 +19,10 @@ export default class DefaultHeadTemplate extends GlimmerComponent<{ } get image(): string | undefined { - return this.args.model?.cardThumbnailURL; + return ( + this.args.model?.cardInfo?.cardThumbnail?.url ?? + this.args.model?.cardThumbnailURL + ); } get themeIcon(): string | undefined { diff --git a/packages/base/default-templates/image-def-atom.gts b/packages/base/default-templates/image-def-atom.gts new file mode 100644 index 00000000000..a095504dbe4 --- /dev/null +++ b/packages/base/default-templates/image-def-atom.gts @@ -0,0 +1,41 @@ +import GlimmerComponent from '@glimmer/component'; +import type { ImageDef } from '../card-api'; + +export default class ImageDefAtomTemplate extends GlimmerComponent<{ + Args: { + model: ImageDef; + }; +}> { + +} diff --git a/packages/base/default-templates/image-def-embedded.gts b/packages/base/default-templates/image-def-embedded.gts new file mode 100644 index 00000000000..f1e75fc300b --- /dev/null +++ b/packages/base/default-templates/image-def-embedded.gts @@ -0,0 +1,35 @@ +import GlimmerComponent from '@glimmer/component'; +import type { ImageDef } from '../card-api'; + +export default class ImageDefEmbeddedTemplate extends GlimmerComponent<{ + Args: { + model: ImageDef; + }; +}> { + +} diff --git a/packages/base/default-templates/image-def-fitted.gts b/packages/base/default-templates/image-def-fitted.gts new file mode 100644 index 00000000000..ec785c5f612 --- /dev/null +++ b/packages/base/default-templates/image-def-fitted.gts @@ -0,0 +1,62 @@ +import GlimmerComponent from '@glimmer/component'; + +import setBackgroundImage from '../helpers/set-background-image'; +import type { ImageDef } from '../card-api'; + +export default class ImageDefFittedTemplate extends GlimmerComponent<{ + Args: { + model: ImageDef; + }; +}> { + +} diff --git a/packages/base/default-templates/image-def-isolated.gts b/packages/base/default-templates/image-def-isolated.gts new file mode 100644 index 00000000000..1c7d281fee3 --- /dev/null +++ b/packages/base/default-templates/image-def-isolated.gts @@ -0,0 +1,66 @@ +import GlimmerComponent from '@glimmer/component'; +import type { ImageDef } from '../card-api'; + +export default class ImageDefIsolatedTemplate extends GlimmerComponent<{ + Args: { + model: ImageDef; + }; +}> { + +} diff --git a/packages/base/file-api.gts b/packages/base/file-api.gts index aab737ad92a..f8579fde5a5 100644 --- a/packages/base/file-api.gts +++ b/packages/base/file-api.gts @@ -1,265 +1,9 @@ -import { concat } from '@ember/helper'; -import FileIcon from '@cardstack/boxel-icons/file'; -import type { MenuItemOptions } from '@cardstack/boxel-ui/helpers'; -import { copyCardURLToClipboard } from '@cardstack/boxel-ui/helpers'; -import { - byteStreamToUint8Array, - getMenuItems, - inferContentType, -} from '@cardstack/runtime-common'; -import { md5 } from 'super-fast-md5'; -import { - BaseDef, - BaseDefComponent, - Component, - GetMenuItemParams, - ReadOnlyField, - StringField, - contains, - field, - getCardMeta, - getDataBucket, - realmURL, +export { + FileContentMismatchError, + FileDef, + FileDef as default, + createFileDef, + getDefaultFileMenuItems, } from './card-api'; -import NumberField from './number'; -import ArrowLeft from '@cardstack/boxel-icons/arrow-left'; -import LinkIcon from '@cardstack/boxel-icons/link'; -import CopyFileToRealmCommand from '@cardstack/boxel-host/commands/copy-file-to-realm'; -import OpenInInteractModeCommand from '@cardstack/boxel-host/commands/open-in-interact-mode'; -import ShowFileCommand from '@cardstack/boxel-host/commands/show-file'; -import Eye from '@cardstack/boxel-icons/eye'; -import SwitchSubmodeCommand from '@cardstack/boxel-host/commands/switch-submode'; -import CodeIcon from '@cardstack/boxel-icons/code'; -import { isSavedInstance } from './-private'; -class View extends Component { - -} - -class Edit extends Component { - -} - -export type SerializedFile = { - sourceUrl: string; - url: string; - name: string; - contentType: string; - contentHash?: string; - contentSize?: number; -} & Extra; - -export type ByteStream = ReadableStream | Uint8Array; - -// Throw this error from extractAttributes when the file content doesn't match this FileDef's -// expectations so the extractor can fall back to a superclass/base FileDef. -export class FileContentMismatchError extends Error { - name = 'FileContentMismatchError'; -} - -export class FileDef extends BaseDef { - static displayName = 'File'; - static isFileDef = true; - static icon = FileIcon; - [isSavedInstance] = true; - - get [realmURL](): URL | undefined { - let realmURLString = getCardMeta(this, 'realmURL'); - return realmURLString ? new URL(realmURLString) : undefined; - } - - static assignInitialFieldValue( - instance: BaseDef, - fieldName: string, - value: any, - ) { - if (fieldName === 'id') { - // Similar to CardDef, set 'id' directly in the deserialized cache - // to avoid triggering recomputes during instantiation - let deserialized = getDataBucket(instance); - deserialized.set('id', value); - } else { - super.assignInitialFieldValue(instance, fieldName, value); - } - } - - @field id = contains(ReadOnlyField); - @field sourceUrl = contains(StringField); - @field url = contains(StringField); - @field name = contains(StringField); - @field contentType = contains(StringField); - @field contentHash = contains(StringField); - @field contentSize = contains(NumberField); - - static embedded: BaseDefComponent = View; - static fitted: BaseDefComponent = View; - static isolated: BaseDefComponent = View; - static atom: BaseDefComponent = View; - static edit: BaseDefComponent = Edit; - - static async extractAttributes( - url: string, - getStream: () => Promise, - options: { contentHash?: string; contentSize?: number } = {}, - ): Promise { - let parsed = new URL(url); - let name = decodeURIComponent( - parsed.pathname.split('/').pop() ?? parsed.pathname, - ); - let contentType = inferContentType(name); - let contentHash: string | undefined = options.contentHash; - let contentSize: number | undefined = options.contentSize; - if (!contentHash || contentSize === undefined) { - let bytes = await byteStreamToUint8Array(await getStream()); - if (!contentHash) { - try { - contentHash = md5(bytes); - } catch { - contentHash = md5(new TextDecoder().decode(bytes)); - } - } - if (contentSize === undefined) { - contentSize = bytes.byteLength; - } - } - - return { - sourceUrl: url, - url, - name, - contentType, - contentHash, - contentSize, - }; - } - - serialize() { - return { - sourceUrl: this.sourceUrl, - url: this.url, - name: this.name, - contentType: this.contentType, - contentHash: this.contentHash, - contentSize: this.contentSize, - }; - } - - [getMenuItems](params: GetMenuItemParams): MenuItemOptions[] { - return getDefaultFileMenuItems(this, params); - } -} - -export interface SerializedFileDef { - url?: string; - sourceUrl: string; - name?: string; - contentHash?: string; - contentSize?: number; - contentType?: string; - content?: string; - error?: string; -} - -export function createFileDef({ - url, - sourceUrl, - name, - contentType, - contentHash, - contentSize, -}: SerializedFileDef) { - return new FileDef({ url, sourceUrl, name, contentType, contentHash, contentSize }); -} - -export function getDefaultFileMenuItems( - fileDefInstance: FileDef, - params: GetMenuItemParams, -): MenuItemOptions[] { - let fileDefInstanceId = fileDefInstance.id as unknown as string; - let menuItems: MenuItemOptions[] = []; - if ( - ['interact', 'code-mode-preview', 'code-mode-playground'].includes( - params.menuContext, - ) - ) { - menuItems.push({ - label: 'Copy File URL', - action: () => copyCardURLToClipboard(fileDefInstanceId), - icon: LinkIcon, - disabled: !fileDefInstanceId, - }); - } - if (params.menuContext === 'interact') { - if (fileDefInstanceId && params.canEdit) { - // TODO: add menu item to delete the file - } - } - if ( - params.menuContext === 'ai-assistant' && - params.menuContextParams.canEditActiveRealm - ) { - menuItems.push({ - label: 'Copy to Workspace', - action: async () => { - const { newFileUrl } = await new CopyFileToRealmCommand( - params.commandContext, - ).execute({ - sourceFileUrl: fileDefInstance.sourceUrl, - targetRealm: params.menuContextParams.activeRealmURL, - }); - - await new ShowFileCommand(params.commandContext).execute({ - fileUrl: newFileUrl, - }); - }, - icon: ArrowLeft, - }); - } - if ( - ['code-mode-preview', 'code-mode-playground'].includes(params.menuContext) - ) { - menuItems.push({ - label: 'Open in Interact Mode', - action: () => { - new OpenInInteractModeCommand(params.commandContext).execute({ - cardId: fileDefInstanceId, - format: params.format === 'edit' ? 'edit' : 'isolated', - }); - }, - icon: Eye, - }); - } - if (params.menuContext === 'code-mode-playground') { - menuItems.push({ - label: 'Open in Code Mode', - action: async () => { - await new SwitchSubmodeCommand(params.commandContext).execute({ - submode: 'code', - codePath: fileDefInstanceId - ? new URL(fileDefInstanceId).href - : undefined, - }); - }, - icon: CodeIcon, - }); - } - return menuItems; -} +export type { ByteStream, SerializedFile, SerializedFileDef } from './card-api'; diff --git a/packages/base/file-menu-items.ts b/packages/base/file-menu-items.ts new file mode 100644 index 00000000000..6ea72f10480 --- /dev/null +++ b/packages/base/file-menu-items.ts @@ -0,0 +1,92 @@ +import { + copyCardURLToClipboard, + type MenuItemOptions, +} from '@cardstack/boxel-ui/helpers'; + +import ArrowLeft from '@cardstack/boxel-icons/arrow-left'; +import CodeIcon from '@cardstack/boxel-icons/code'; +import Eye from '@cardstack/boxel-icons/eye'; +import LinkIcon from '@cardstack/boxel-icons/link'; + +import CopyFileToRealmCommand from '@cardstack/boxel-host/commands/copy-file-to-realm'; +import OpenInInteractModeCommand from '@cardstack/boxel-host/commands/open-in-interact-mode'; +import ShowFileCommand from '@cardstack/boxel-host/commands/show-file'; +import SwitchSubmodeCommand from '@cardstack/boxel-host/commands/switch-submode'; + +import type { FileDef } from './card-api'; +import type { GetMenuItemParams } from './menu-items'; + +export function getDefaultFileMenuItems( + fileDefInstance: FileDef, + params: GetMenuItemParams, +): MenuItemOptions[] { + let fileDefInstanceId = fileDefInstance.id as unknown as string; + let menuItems: MenuItemOptions[] = []; + if ( + ['interact', 'code-mode-preview', 'code-mode-playground'].includes( + params.menuContext, + ) + ) { + menuItems.push({ + label: 'Copy File URL', + action: () => copyCardURLToClipboard(fileDefInstanceId), + icon: LinkIcon, + disabled: !fileDefInstanceId, + }); + } + if (params.menuContext === 'interact') { + if (fileDefInstanceId && params.canEdit) { + // TODO: add menu item to delete the file + } + } + if ( + params.menuContext === 'ai-assistant' && + params.menuContextParams.canEditActiveRealm + ) { + menuItems.push({ + label: 'Copy to Workspace', + action: async () => { + let { newFileUrl } = await new CopyFileToRealmCommand( + params.commandContext, + ).execute({ + sourceFileUrl: fileDefInstance.sourceUrl, + targetRealm: params.menuContextParams.activeRealmURL, + }); + + await new ShowFileCommand(params.commandContext).execute({ + fileUrl: newFileUrl, + }); + }, + icon: ArrowLeft, + }); + } + if ( + ['code-mode-preview', 'code-mode-playground'].includes(params.menuContext) + ) { + menuItems.push({ + label: 'Open in Interact Mode', + action: () => { + new OpenInInteractModeCommand(params.commandContext).execute({ + cardId: fileDefInstanceId, + format: params.format === 'edit' ? 'edit' : 'isolated', + }); + }, + icon: Eye, + }); + } + if (params.menuContext === 'code-mode-playground') { + menuItems.push({ + label: 'Open in Code Mode', + action: async () => { + await new SwitchSubmodeCommand(params.commandContext).execute({ + submode: 'code', + codePath: fileDefInstanceId + ? new URL(fileDefInstanceId).href + : undefined, + }); + }, + icon: CodeIcon, + }); + } + return menuItems; +} diff --git a/packages/base/gif-image-def.gts b/packages/base/gif-image-def.gts index 5e68fca9c61..a3335993436 100644 --- a/packages/base/gif-image-def.gts +++ b/packages/base/gif-image-def.gts @@ -1,5 +1,5 @@ import { readFirstBytes } from '@cardstack/runtime-common'; -import { ImageDef } from './image-file-def'; +import ImageDef from './image-file-def'; import { type ByteStream, type SerializedFile } from './file-api'; import { extractGifDimensions } from './gif-meta-extractor'; diff --git a/packages/base/image-file-def.gts b/packages/base/image-file-def.gts index ea00c671d07..1b64bb79ab0 100644 --- a/packages/base/image-file-def.gts +++ b/packages/base/image-file-def.gts @@ -1,205 +1 @@ -import NumberField from './number'; -import { BaseDefComponent, Component, contains, field } from './card-api'; -import { FileDef } from './file-api'; - -class Isolated extends Component { - -} - -class Atom extends Component { - -} - -class Embedded extends Component { - -} - -class Fitted extends Component { - get backgroundImageStyle() { - if (this.args.model.url) { - return `background-image: url(${this.args.model.url});`; - } - return undefined; - } - - -} - -export class ImageDef extends FileDef { - static displayName = 'Image'; - static acceptTypes = 'image/*'; - - @field width = contains(NumberField); - @field height = contains(NumberField); - - static isolated: BaseDefComponent = Isolated; - static embedded: BaseDefComponent = Embedded; - static atom: BaseDefComponent = Atom; - static fitted: BaseDefComponent = Fitted; -} +export { ImageDef, ImageDef as default } from './card-api'; diff --git a/packages/base/jpg-image-def.gts b/packages/base/jpg-image-def.gts index 9d2faf62bf1..3469fab67b5 100644 --- a/packages/base/jpg-image-def.gts +++ b/packages/base/jpg-image-def.gts @@ -1,5 +1,5 @@ import { readFirstBytes } from '@cardstack/runtime-common'; -import { ImageDef } from './image-file-def'; +import ImageDef from './image-file-def'; import { type ByteStream, type SerializedFile } from './file-api'; import { extractJpgDimensions } from './jpg-meta-extractor'; diff --git a/packages/base/number.gts b/packages/base/number.gts index 33e2e1606d9..9c3913e527b 100644 --- a/packages/base/number.gts +++ b/packages/base/number.gts @@ -1,59 +1,5 @@ -import { primitive, Component, useIndexBasedKey, FieldDef } from './card-api'; -import { BoxelInput } from '@cardstack/boxel-ui/components'; -import { TextInputValidator } from './text-input-validator'; -import { not } from '@cardstack/boxel-ui/helpers'; -import HashIcon from '@cardstack/boxel-icons/hash'; -import { fieldSerializer, NumberSerializer } from '@cardstack/runtime-common'; - -export function deserializeForUI(value: string | number | null): number | null { - const validationError = NumberSerializer.validate(value); - if (validationError) { - return null; - } - - return NumberSerializer.deserializeSync(value); -} - -export function serializeForUI(val: number | null): string | undefined { - let serialized = NumberSerializer.serialize(val); - if (serialized != null) { - return String(serialized); - } - return undefined; -} - -class View extends Component { - -} - -export default class NumberField extends FieldDef { - static displayName = 'Number'; - static icon = HashIcon; - static [primitive]: number; - static [fieldSerializer] = 'number'; - static [useIndexBasedKey]: never; - static embedded = View; - static atom = View; - - static edit = class Edit extends Component { - - - textInputValidator: TextInputValidator = new TextInputValidator( - () => this.args.model, - (inputVal) => this.args.set(inputVal), - deserializeForUI, - serializeForUI, - NumberSerializer.validate, - ); - }; -} +export { + NumberField as default, + deserializeForUI, + serializeForUI, +} from './card-api'; diff --git a/packages/base/png-image-def.gts b/packages/base/png-image-def.gts index 05dbfbd0bc5..5fd820ab02b 100644 --- a/packages/base/png-image-def.gts +++ b/packages/base/png-image-def.gts @@ -1,5 +1,5 @@ import { readFirstBytes } from '@cardstack/runtime-common'; -import { ImageDef } from './image-file-def'; +import ImageDef from './image-file-def'; import { type ByteStream, type SerializedFile } from './file-api'; import { extractPngDimensions } from './png-meta-extractor'; diff --git a/packages/base/svg-image-def.gts b/packages/base/svg-image-def.gts index 5e341b99551..61400217214 100644 --- a/packages/base/svg-image-def.gts +++ b/packages/base/svg-image-def.gts @@ -1,5 +1,5 @@ import { byteStreamToUint8Array } from '@cardstack/runtime-common'; -import { ImageDef } from './image-file-def'; +import ImageDef from './image-file-def'; import { type ByteStream, type SerializedFile } from './file-api'; import { extractSvgDimensions } from './svg-meta-extractor'; diff --git a/packages/base/webp-image-def.gts b/packages/base/webp-image-def.gts index dda8887f7a2..18a325e97a9 100644 --- a/packages/base/webp-image-def.gts +++ b/packages/base/webp-image-def.gts @@ -1,5 +1,5 @@ import { readFirstBytes } from '@cardstack/runtime-common'; -import { ImageDef } from './image-file-def'; +import ImageDef from './image-file-def'; import { type ByteStream, type SerializedFile } from './file-api'; import { extractWebpDimensions } from './webp-meta-extractor'; diff --git a/packages/experiments-realm/file-search-playground.gts b/packages/experiments-realm/file-search-playground.gts index 1f69ec29911..a1397d02174 100644 --- a/packages/experiments-realm/file-search-playground.gts +++ b/packages/experiments-realm/file-search-playground.gts @@ -15,13 +15,13 @@ const fileSearchQuery = { every: [ { type: { - module: 'https://cardstack.com/base/file-api', + module: 'https://cardstack.com/base/card-api', name: 'FileDef', }, }, { on: { - module: 'https://cardstack.com/base/file-api', + module: 'https://cardstack.com/base/card-api', name: 'FileDef', }, contains: { diff --git a/packages/experiments-realm/image-def-playground.gts b/packages/experiments-realm/image-def-playground.gts index 9f5925fe674..33354b9ae75 100644 --- a/packages/experiments-realm/image-def-playground.gts +++ b/packages/experiments-realm/image-def-playground.gts @@ -4,11 +4,11 @@ import { StringField, contains, field, + ImageDef, linksTo, linksToMany, } from 'https://cardstack.com/base/card-api'; import { gt } from '@cardstack/boxel-ui/helpers'; -import { ImageDef } from 'https://cardstack.com/base/image-file-def'; /** * Playground card for demonstrating ImageDef capabilities. diff --git a/packages/host/tests/cards/file-query-card.gts b/packages/host/tests/cards/file-query-card.gts index 341033feb39..57bbb064e8e 100644 --- a/packages/host/tests/cards/file-query-card.gts +++ b/packages/host/tests/cards/file-query-card.gts @@ -9,7 +9,7 @@ import StringField from 'https://cardstack.com/base/string'; const fileSearchQuery = { filter: { - type: { module: 'https://cardstack.com/base/file-api', name: 'FileDef' }, + type: { module: 'https://cardstack.com/base/card-api', name: 'FileDef' }, }, realm: '$thisRealm', }; diff --git a/packages/host/tests/helpers/interact-submode-setup.gts b/packages/host/tests/helpers/interact-submode-setup.gts index 048184f1859..eea21922ab4 100644 --- a/packages/host/tests/helpers/interact-submode-setup.gts +++ b/packages/host/tests/helpers/interact-submode-setup.gts @@ -81,7 +81,7 @@ export function setupInteractSubmodeTests( let { Spec } = spec; let { CardsGrid } = cardsGrid; let { FileDef } = fileApi; - let { ImageDef } = imageFileApi; + let { default: ImageDef } = imageFileApi; class Pet extends CardDef { static displayName = 'Pet'; diff --git a/packages/host/tests/integration/commands/patch-instance-test.gts b/packages/host/tests/integration/commands/patch-instance-test.gts index e5cee817dfc..b52a7abea47 100644 --- a/packages/host/tests/integration/commands/patch-instance-test.gts +++ b/packages/host/tests/integration/commands/patch-instance-test.gts @@ -493,6 +493,7 @@ module('Integration | commands | patch-instance', function (hooks) { instance.relationships, { bestFriend: { links: { self: `./queenzy` } }, + 'cardInfo.cardThumbnail': { links: { self: null } }, 'cardInfo.theme': { links: { self: null } }, friends: { links: { diff --git a/packages/host/tests/integration/components/file-def-serialization-test.gts b/packages/host/tests/integration/components/file-def-serialization-test.gts index 1bcb2b8f7a9..9d82eb91e3c 100644 --- a/packages/host/tests/integration/components/file-def-serialization-test.gts +++ b/packages/host/tests/integration/components/file-def-serialization-test.gts @@ -49,7 +49,7 @@ module('Integration | serializeFileDef', function (hooks) { let doc = serializeFileDef(fileDef); assert.deepEqual(doc.data.meta.adoptsFrom, { - module: `${baseRealm.url}file-api`, + module: `${baseRealm.url}card-api`, name: 'FileDef', }); }); diff --git a/packages/host/tests/integration/components/operator-mode-links-test.gts b/packages/host/tests/integration/components/operator-mode-links-test.gts index a80417c9a58..efceadd7edf 100644 --- a/packages/host/tests/integration/components/operator-mode-links-test.gts +++ b/packages/host/tests/integration/components/operator-mode-links-test.gts @@ -522,6 +522,11 @@ module('Integration | operator-mode | links', function (hooks) { }, }, relationships: { + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, diff --git a/packages/host/tests/integration/components/prerendered-card-search-test.gts b/packages/host/tests/integration/components/prerendered-card-search-test.gts index e943c17ef5a..ce833905c7a 100644 --- a/packages/host/tests/integration/components/prerendered-card-search-test.gts +++ b/packages/host/tests/integration/components/prerendered-card-search-test.gts @@ -464,7 +464,7 @@ module(`Integration | prerendered-card-search`, function (hooks) { let query: Query = { filter: { on: { - module: `${baseRealm.url}file-api`, + module: `${baseRealm.url}card-api`, name: 'FileDef', }, eq: { diff --git a/packages/host/tests/integration/realm-indexing-test.gts b/packages/host/tests/integration/realm-indexing-test.gts index f5eb48ff3b1..a769d4edd8a 100644 --- a/packages/host/tests/integration/realm-indexing-test.gts +++ b/packages/host/tests/integration/realm-indexing-test.gts @@ -2284,6 +2284,7 @@ module(`Integration | realm indexing`, function (hooks) { cardInfo, }, relationships: { + 'cardInfo.cardThumbnail': { links: { self: null } }, 'cardInfo.theme': { links: { self: null } }, }, meta: { @@ -2325,6 +2326,7 @@ module(`Integration | realm indexing`, function (hooks) { cardInfo, }, relationships: { + 'cardInfo.cardThumbnail': { links: { self: null } }, 'cardInfo.theme': { links: { self: null } }, }, meta: { @@ -2475,9 +2477,10 @@ module(`Integration | realm indexing`, function (hooks) { posts: 100, cardTitle: 'Hassan Abdel-Rahman', cardDescription: 'Person', + cardThumbnailURL: null, fullName: 'Hassan Abdel-Rahman', _cardType: 'Person', - cardInfo: { theme: null }, + cardInfo: { cardThumbnail: null, theme: null }, }, `search doc includes fullName field`, ); @@ -2561,7 +2564,8 @@ module(`Integration | realm indexing`, function (hooks) { id: `${testRealmURL}Publication/pacific`, }, views: 5, - cardInfo: { theme: null }, + cardInfo: { cardThumbnail: null, theme: null }, + cardThumbnailURL: null, }, `post 1 search doc includes publication relationship`, ); @@ -2582,9 +2586,8 @@ module(`Integration | realm indexing`, function (hooks) { fullName: ' ', cardTitle: ' ', }, - cardInfo: { - theme: null, - }, + cardInfo: { cardThumbnail: null, theme: null }, + cardThumbnailURL: null, id: `${testRealmURL}Post/1`, publication: { id: `${testRealmURL}Publication/pacific`, @@ -2598,9 +2601,8 @@ module(`Integration | realm indexing`, function (hooks) { fullName: ' ', cardTitle: ' ', }, - cardInfo: { - theme: null, - }, + cardInfo: { cardThumbnail: null, theme: null }, + cardThumbnailURL: null, id: `${testRealmURL}Post/2`, publication: { id: `${testRealmURL}Publication/pacific`, @@ -2609,7 +2611,8 @@ module(`Integration | realm indexing`, function (hooks) { views: 24, }, ], - cardInfo: { theme: null }, + cardInfo: { cardThumbnail: null, theme: null }, + cardThumbnailURL: null, }, `publication search doc includes featuredPosts relationship via isUsed=true`, ); @@ -2919,14 +2922,14 @@ module(`Integration | realm indexing`, function (hooks) { firstName: 'Mango', owner: null, cardTitle: 'Mango', - cardInfo: { theme: null }, + cardInfo: { cardThumbnail: null, theme: null }, }, { id: `${testRealmURL}Pet/vanGogh`, firstName: 'Van Gogh', owner: null, cardTitle: 'Van Gogh', - cardInfo: { theme: null }, + cardInfo: { cardThumbnail: null, theme: null }, }, ], friend: null, @@ -2973,6 +2976,7 @@ module(`Integration | realm indexing`, function (hooks) { ); if (card?.type === 'doc') { + // debug assert.deepEqual(card.doc, { data: { id: `${testRealmURL}PetPerson/burcu`, @@ -3700,7 +3704,7 @@ module(`Integration | realm indexing`, function (hooks) { id: `${testRealmURL}Friend/mango`, }, cardDescription: 'Dog owner', - cardInfo: { theme: null }, + cardInfo: { cardThumbnail: null, theme: null }, }, cardInfo: { theme: null }, }); @@ -4250,7 +4254,7 @@ module(`Integration | realm indexing`, function (hooks) { cardInfo: { theme: null }, }, ], - cardInfo: { theme: null }, + cardInfo: { cardThumbnail: null, theme: null }, }, ], cardInfo: { theme: null }, @@ -4423,6 +4427,7 @@ module(`Integration | realm indexing`, function (hooks) { friends: [ { cardInfo: { + cardThumbnail: null, theme: null, }, firstName: 'Mango', @@ -4436,7 +4441,7 @@ module(`Integration | realm indexing`, function (hooks) { }, { id: vanGoghID }, ], - cardInfo: { theme: null }, + cardInfo: { cardThumbnail: null, theme: null }, }, ], cardInfo: { theme: null }, @@ -4482,9 +4487,11 @@ module(`Integration | realm indexing`, function (hooks) { 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/captions', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/code', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/eye', + 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/file', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/file-pencil', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/folder-pen', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/hash', + 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/image', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/import', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/letter-case', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/link', @@ -4502,13 +4509,19 @@ module(`Integration | realm indexing`, function (hooks) { 'https://cardstack.com/base/default-templates/card-info', 'https://cardstack.com/base/default-templates/embedded', 'https://cardstack.com/base/default-templates/field-edit', + 'https://cardstack.com/base/default-templates/file-def-edit', 'https://cardstack.com/base/default-templates/fitted', 'https://cardstack.com/base/default-templates/head', + 'https://cardstack.com/base/default-templates/image-def-atom', + 'https://cardstack.com/base/default-templates/image-def-embedded', + 'https://cardstack.com/base/default-templates/image-def-fitted', + 'https://cardstack.com/base/default-templates/image-def-isolated', 'https://cardstack.com/base/default-templates/isolated-and-edit', 'https://cardstack.com/base/default-templates/markdown', 'https://cardstack.com/base/default-templates/missing-template', 'https://cardstack.com/base/field-component', 'https://cardstack.com/base/field-support', + 'https://cardstack.com/base/file-menu-items', 'https://cardstack.com/base/helpers/sanitized-html', 'https://cardstack.com/base/helpers/set-background-image', 'https://cardstack.com/base/links-to-editor', @@ -4522,6 +4535,7 @@ module(`Integration | realm indexing`, function (hooks) { 'https://cardstack.com/base/watched-array', 'https://packages/@cardstack/boxel-host/commands/copy-and-edit', 'https://packages/@cardstack/boxel-host/commands/copy-card', + 'https://packages/@cardstack/boxel-host/commands/copy-file-to-realm', 'https://packages/@cardstack/boxel-host/commands/create-ai-assistant-room', 'https://packages/@cardstack/boxel-host/commands/generate-example-cards', 'https://packages/@cardstack/boxel-host/commands/open-create-listing-modal', @@ -4530,6 +4544,7 @@ module(`Integration | realm indexing`, function (hooks) { 'https://packages/@cardstack/boxel-host/commands/populate-with-sample-data', 'https://packages/@cardstack/boxel-host/commands/send-ai-assistant-message', 'https://packages/@cardstack/boxel-host/commands/show-card', + 'https://packages/@cardstack/boxel-host/commands/show-file', 'https://packages/@cardstack/boxel-host/commands/switch-submode', 'https://packages/@cardstack/boxel-ui/components', 'https://packages/@cardstack/boxel-ui/helpers', @@ -4552,6 +4567,7 @@ module(`Integration | realm indexing`, function (hooks) { 'https://packages/ember-modifier', 'https://packages/ember-provide-consume-context', 'https://packages/lodash', + 'https://packages/super-fast-md5', 'https://packages/tracked-built-ins', ], 'the card references for the instance are correct', @@ -4610,10 +4626,12 @@ module(`Integration | realm indexing`, function (hooks) { 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/captions', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/code', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/eye', + 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/file', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/file-pencil', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/folder-pen', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/git-branch', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/hash', + 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/image', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/import', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/layers-subtract', 'http://localhost:4206/@cardstack/boxel-icons/v1/icons/layout-list', @@ -4637,13 +4655,19 @@ module(`Integration | realm indexing`, function (hooks) { 'https://cardstack.com/base/default-templates/card-info', 'https://cardstack.com/base/default-templates/embedded', 'https://cardstack.com/base/default-templates/field-edit', + 'https://cardstack.com/base/default-templates/file-def-edit', 'https://cardstack.com/base/default-templates/fitted', 'https://cardstack.com/base/default-templates/head', + 'https://cardstack.com/base/default-templates/image-def-atom', + 'https://cardstack.com/base/default-templates/image-def-embedded', + 'https://cardstack.com/base/default-templates/image-def-fitted', + 'https://cardstack.com/base/default-templates/image-def-isolated', 'https://cardstack.com/base/default-templates/isolated-and-edit', 'https://cardstack.com/base/default-templates/markdown', 'https://cardstack.com/base/default-templates/missing-template', 'https://cardstack.com/base/field-component', 'https://cardstack.com/base/field-support', + 'https://cardstack.com/base/file-menu-items', 'https://cardstack.com/base/helpers/sanitized-html', 'https://cardstack.com/base/helpers/set-background-image', 'https://cardstack.com/base/links-to-editor', @@ -4659,6 +4683,7 @@ module(`Integration | realm indexing`, function (hooks) { 'https://cardstack.com/base/watched-array', 'https://packages/@cardstack/boxel-host/commands/copy-and-edit', 'https://packages/@cardstack/boxel-host/commands/copy-card', + 'https://packages/@cardstack/boxel-host/commands/copy-file-to-realm', 'https://packages/@cardstack/boxel-host/commands/create-ai-assistant-room', 'https://packages/@cardstack/boxel-host/commands/generate-example-cards', 'https://packages/@cardstack/boxel-host/commands/generate-readme-spec', @@ -4668,6 +4693,7 @@ module(`Integration | realm indexing`, function (hooks) { 'https://packages/@cardstack/boxel-host/commands/populate-with-sample-data', 'https://packages/@cardstack/boxel-host/commands/send-ai-assistant-message', 'https://packages/@cardstack/boxel-host/commands/show-card', + 'https://packages/@cardstack/boxel-host/commands/show-file', 'https://packages/@cardstack/boxel-host/commands/switch-submode', 'https://packages/@cardstack/boxel-ui/components', 'https://packages/@cardstack/boxel-ui/helpers', @@ -4691,6 +4717,7 @@ module(`Integration | realm indexing`, function (hooks) { 'https://packages/ember-provide-consume-context', 'https://packages/ember-resources', 'https://packages/lodash', + 'https://packages/super-fast-md5', 'https://packages/tracked-built-ins', ], 'the card references for the instance are correct', diff --git a/packages/host/tests/integration/realm-querying-test.gts b/packages/host/tests/integration/realm-querying-test.gts index 152664e2cc1..60085d7a8e3 100644 --- a/packages/host/tests/integration/realm-querying-test.gts +++ b/packages/host/tests/integration/realm-querying-test.gts @@ -744,7 +744,7 @@ module(`Integration | realm querying`, function (hooks) { }); test('can search for file-meta entries by FileDef type', async function (assert) { - let fileDefRef = { module: `${baseRealm.url}file-api`, name: 'FileDef' }; + let fileDefRef = { module: `${baseRealm.url}card-api`, name: 'FileDef' }; let result = (await queryEngine.searchCards({ filter: { type: fileDefRef, @@ -764,7 +764,7 @@ module(`Integration | realm querying`, function (hooks) { }); test('can search for file-meta entries by url', async function (assert) { - let fileDefRef = { module: `${baseRealm.url}file-api`, name: 'FileDef' }; + let fileDefRef = { module: `${baseRealm.url}card-api`, name: 'FileDef' }; let targetUrl = `${testRealmURL}files/sample.txt`; let result = (await queryEngine.searchCards({ filter: { diff --git a/packages/host/tests/integration/realm-test.gts b/packages/host/tests/integration/realm-test.gts index f2794734e71..6f213461c4e 100644 --- a/packages/host/tests/integration/realm-test.gts +++ b/packages/host/tests/integration/realm-test.gts @@ -254,6 +254,7 @@ module('Integration | realm', function (hooks) { cardInfo, }, relationships: { + 'cardInfo.cardThumbnail': { links: { self: null } }, 'cardInfo.theme': { links: { self: null } }, }, meta: { @@ -374,7 +375,9 @@ module('Integration | realm', function (hooks) { cardTitle: 'Hassan Abdel-Rahman', cardInfo, }, - relationships: { 'cardInfo.theme': { links: { self: null } } }, + relationships: { + 'cardInfo.theme': { links: { self: null } }, + }, meta: { adoptsFrom: { module: 'http://localhost:4202/test/person', @@ -766,6 +769,7 @@ module('Integration | realm', function (hooks) { cardInfo, }, relationships: { + 'cardInfo.cardThumbnail': { links: { self: null } }, 'cardInfo.theme': { links: { self: null } }, }, meta: { @@ -975,6 +979,7 @@ module('Integration | realm', function (hooks) { cardInfo, }, relationships: { + 'cardInfo.cardThumbnail': { links: { self: null } }, 'cardInfo.theme': { links: { self: null } }, }, meta: { @@ -1328,6 +1333,7 @@ module('Integration | realm', function (hooks) { cardInfo, }, relationships: { + 'cardInfo.cardThumbnail': { links: { self: null } }, 'cardInfo.theme': { links: { self: null } }, }, meta: { @@ -2310,6 +2316,7 @@ module('Integration | realm', function (hooks) { cardInfo, }, relationships: { + 'cardInfo.cardThumbnail': { links: { self: null } }, 'cardInfo.theme': { links: { self: null } }, }, meta: { @@ -3203,6 +3210,7 @@ module('Integration | realm', function (hooks) { cardInfo, }, relationships: { + 'cardInfo.cardThumbnail': { links: { self: null } }, 'cardInfo.theme': { links: { self: null } }, }, meta: { diff --git a/packages/host/tests/integration/resources/search-data-test.ts b/packages/host/tests/integration/resources/search-data-test.ts index 31ea5d7f301..ce3123afbb7 100644 --- a/packages/host/tests/integration/resources/search-data-test.ts +++ b/packages/host/tests/integration/resources/search-data-test.ts @@ -207,7 +207,7 @@ module(`Integration | search data resource`, function (hooks) { let query: DataQuery = { filter: { type: { - module: `${baseRealm.url}file-api`, + module: `${baseRealm.url}card-api`, name: 'FileDef', }, }, diff --git a/packages/host/tests/integration/resources/search-test.ts b/packages/host/tests/integration/resources/search-test.ts index 4ed5d520057..9aa071a4032 100644 --- a/packages/host/tests/integration/resources/search-test.ts +++ b/packages/host/tests/integration/resources/search-test.ts @@ -595,7 +595,7 @@ module(`Integration | search resource`, function (hooks) { let query: Query = { filter: { type: { - module: `${baseRealm.url}file-api`, + module: `${baseRealm.url}card-api`, name: 'FileDef', }, }, @@ -634,7 +634,7 @@ module(`Integration | search resource`, function (hooks) { let query: Query = { filter: { type: { - module: `${baseRealm.url}file-api`, + module: `${baseRealm.url}card-api`, name: 'FileDef', }, }, diff --git a/packages/host/tests/integration/store-test.gts b/packages/host/tests/integration/store-test.gts index 70f0c16183b..6ac63e9e7ba 100644 --- a/packages/host/tests/integration/store-test.gts +++ b/packages/host/tests/integration/store-test.gts @@ -1277,7 +1277,7 @@ module('Integration | Store', function (hooks) { }, meta: { adoptsFrom: { - module: 'https://cardstack.com/base/file-api', + module: 'https://cardstack.com/base/card-api', name: 'FileDef', }, }, @@ -1293,7 +1293,7 @@ module('Integration | Store', function (hooks) { }, meta: { adoptsFrom: { - module: 'https://cardstack.com/base/file-api', + module: 'https://cardstack.com/base/card-api', name: 'FileDef', }, }, @@ -1309,7 +1309,7 @@ module('Integration | Store', function (hooks) { }, meta: { adoptsFrom: { - module: 'https://cardstack.com/base/file-api', + module: 'https://cardstack.com/base/card-api', name: 'FileDef', }, }, diff --git a/packages/host/tests/unit/ai-function-generation-test.ts b/packages/host/tests/unit/ai-function-generation-test.ts index 8d91fa737f9..f28d8e6d426 100644 --- a/packages/host/tests/unit/ai-function-generation-test.ts +++ b/packages/host/tests/unit/ai-function-generation-test.ts @@ -100,6 +100,7 @@ module('Unit | ai-function-generation-test', function (hooks) { const cardDefRelationshipsProperties: { [fieldName: string]: RelationshipSchema; } = { + 'cardInfo.cardThumbnail': linkedRelationship, 'cardInfo.theme': linkedRelationship, }; diff --git a/packages/host/tests/unit/services/render-service-test.ts b/packages/host/tests/unit/services/render-service-test.ts index 508af4bc9ea..1f69d3f5caa 100644 --- a/packages/host/tests/unit/services/render-service-test.ts +++ b/packages/host/tests/unit/services/render-service-test.ts @@ -134,7 +134,7 @@ module('Unit | Service | render-service', function (hooks) { type: 'file-meta', meta: { adoptsFrom: { - module: 'https://cardstack.com/base/file-api', + module: 'https://cardstack.com/base/card-api', name: 'FileDef', }, }, diff --git a/packages/realm-server/tests/card-endpoints-test.ts b/packages/realm-server/tests/card-endpoints-test.ts index ffa4bc71601..67fe68f3d50 100644 --- a/packages/realm-server/tests/card-endpoints-test.ts +++ b/packages/realm-server/tests/card-endpoints-test.ts @@ -182,6 +182,11 @@ module(basename(__filename), function () { cardThumbnailURL: null, }, relationships: { + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -927,6 +932,11 @@ module(basename(__filename), function () { realmURL: testRealmHref, }, relationships: { + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -1488,6 +1498,11 @@ module(basename(__filename), function () { id: `${testRealmHref}Friend/local-id-1`, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -1551,6 +1566,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -1580,6 +1600,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -1609,6 +1634,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -1675,6 +1705,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -1720,6 +1755,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -1749,6 +1789,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -1798,6 +1843,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -1850,6 +1900,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -2064,6 +2119,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -2243,6 +2303,11 @@ module(basename(__filename), function () { cardInfo, }, relationships: { + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -2597,6 +2662,11 @@ module(basename(__filename), function () { self: './Friend/local-id-1', }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -2748,6 +2818,11 @@ module(basename(__filename), function () { id: `${testRealmHref}Friend/local-id-1`, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -2811,6 +2886,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -2840,6 +2920,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -2869,6 +2954,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -2935,6 +3025,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -2980,6 +3075,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -3009,6 +3109,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -3058,6 +3163,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -3110,6 +3220,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -3228,6 +3343,11 @@ module(basename(__filename), function () { self: './FriendWithUsedLink/local-id-1', }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -3311,6 +3431,11 @@ module(basename(__filename), function () { id: `${testRealmHref}FriendWithUsedLink/local-id-1`, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -3357,6 +3482,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -3406,6 +3536,11 @@ module(basename(__filename), function () { self: null, }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -3504,6 +3639,11 @@ module(basename(__filename), function () { friend: { links: { self: './jade' }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, diff --git a/packages/realm-server/tests/card-source-endpoints-test.ts b/packages/realm-server/tests/card-source-endpoints-test.ts index 7b089a924e7..15ce9d56ff5 100644 --- a/packages/realm-server/tests/card-source-endpoints-test.ts +++ b/packages/realm-server/tests/card-source-endpoints-test.ts @@ -897,6 +897,11 @@ module(basename(__filename), function () { cardInfo, }, relationships: { + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, diff --git a/packages/realm-server/tests/indexing-test.ts b/packages/realm-server/tests/indexing-test.ts index 2908295fc39..d19ec874979 100644 --- a/packages/realm-server/tests/indexing-test.ts +++ b/packages/realm-server/tests/indexing-test.ts @@ -740,6 +740,11 @@ module(basename(__filename), function () { self: './ringo', }, }, + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, @@ -774,6 +779,7 @@ module(basename(__filename), function () { firstName: 'Hassan', cardTitle: 'Untitled Card', cardInfo: { + cardThumbnail: null, theme: null, }, }, @@ -1078,7 +1084,7 @@ module(basename(__filename), function () { // Mutate the index row so we can validate that the response must come from the index, // not from filesystem metadata. await testDbAdapter.execute( - `UPDATE boxel_index SET search_doc = '{"name":"from-index.txt","contentType":"application/x-index-test"}'::jsonb, pristine_doc = '{"id":"${testRealm}random-file.txt","type":"file-meta","attributes":{"name":"from-pristine.txt","contentType":"application/x-pristine","custom":"present"},"meta":{"adoptsFrom":{"module":"https://cardstack.com/base/file-api","name":"FileDef"}}}'::jsonb WHERE url = '${testRealm}random-file.txt'`, + `UPDATE boxel_index SET search_doc = '{"name":"from-index.txt","contentType":"application/x-index-test"}'::jsonb, pristine_doc = '{"id":"${testRealm}random-file.txt","type":"file-meta","attributes":{"name":"from-pristine.txt","contentType":"application/x-pristine","custom":"present"},"meta":{"adoptsFrom":{"module":"https://cardstack.com/base/card-api","name":"FileDef"}}}'::jsonb WHERE url = '${testRealm}random-file.txt'`, ); let response = await fetch(`${testRealm}random-file.txt`, { headers: { Accept: SupportedMimeType.FileMeta }, diff --git a/packages/realm-server/tests/prerendering-test.ts b/packages/realm-server/tests/prerendering-test.ts index a6cdea4c4e2..1da0adb2332 100644 --- a/packages/realm-server/tests/prerendering-test.ts +++ b/packages/realm-server/tests/prerendering-test.ts @@ -1920,7 +1920,7 @@ module(basename(__filename), function () { return { filter: { on: { - module: 'https://cardstack.com/base/file-api', + module: 'https://cardstack.com/base/card-api', name: 'FileDef', }, eq: { diff --git a/packages/realm-server/tests/realm-endpoints-test.ts b/packages/realm-server/tests/realm-endpoints-test.ts index 4a9a0a05202..75ad9dae1bb 100644 --- a/packages/realm-server/tests/realm-endpoints-test.ts +++ b/packages/realm-server/tests/realm-endpoints-test.ts @@ -1149,6 +1149,11 @@ module(basename(__filename), function () { self: newCardId, }, relationships: { + 'cardInfo.cardThumbnail': { + links: { + self: null, + }, + }, 'cardInfo.theme': { links: { self: null, diff --git a/packages/realm-server/tests/realm-endpoints/search-test.ts b/packages/realm-server/tests/realm-endpoints/search-test.ts index 9e230b5ad6a..b27d981d380 100644 --- a/packages/realm-server/tests/realm-endpoints/search-test.ts +++ b/packages/realm-server/tests/realm-endpoints/search-test.ts @@ -47,7 +47,7 @@ module(`realm-endpoints/${basename(__filename)}`, function () { return { filter: { type: { - module: `${baseRealm.url}file-api`, + module: `${baseRealm.url}card-api`, name: 'FileDef', }, }, @@ -223,7 +223,7 @@ module(`realm-endpoints/${basename(__filename)}`, function () { .send({ filter: { on: { - module: `${baseRealm.url}file-api`, + module: `${baseRealm.url}card-api`, name: 'FileDef', }, eq: { diff --git a/packages/realm-server/tests/search-prerendered-test.ts b/packages/realm-server/tests/search-prerendered-test.ts index 33260a4d18b..014e1b1379b 100644 --- a/packages/realm-server/tests/search-prerendered-test.ts +++ b/packages/realm-server/tests/search-prerendered-test.ts @@ -159,7 +159,7 @@ module(basename(__filename), function () { let queryBase: Query = { filter: { on: { - module: `${baseRealm.url}file-api`, + module: `${baseRealm.url}card-api`, name: 'FileDef', }, eq: { diff --git a/packages/runtime-common/constants.ts b/packages/runtime-common/constants.ts index 0c85ca32bcf..fdc477bc139 100644 --- a/packages/runtime-common/constants.ts +++ b/packages/runtime-common/constants.ts @@ -28,7 +28,7 @@ export const skillCardRef: ResolvedCodeRef = { name: 'Skill', }; export const baseFileRef: ResolvedCodeRef = { - module: `${baseRealm.url}file-api`, + module: `${baseRealm.url}card-api`, name: 'FileDef', }; diff --git a/packages/runtime-common/realm-index-query-engine.ts b/packages/runtime-common/realm-index-query-engine.ts index 5741d2c46ba..bf5d0a12a21 100644 --- a/packages/runtime-common/realm-index-query-engine.ts +++ b/packages/runtime-common/realm-index-query-engine.ts @@ -1218,7 +1218,7 @@ function fileResourceFromIndex( (isCodeRef(fileEntry.resource?.meta?.adoptsFrom) ? fileEntry.resource?.meta?.adoptsFrom : { - module: `${baseRealm.url}file-api`, + module: `${baseRealm.url}card-api`, name: 'FileDef', }); let resourceAttributes = fileEntry.resource?.attributes ?? {};