From b8cdbe70a521295f87e88c30324103061626a8e6 Mon Sep 17 00:00:00 2001 From: tintinthong Date: Mon, 13 Jan 2025 20:32:19 -0500 Subject: [PATCH 01/42] update catalog entry card --- packages/base/catalog-entry.gts | 324 ++++++++++++++++++++++++++------ 1 file changed, 262 insertions(+), 62 deletions(-) diff --git a/packages/base/catalog-entry.gts b/packages/base/catalog-entry.gts index 0c67b6bfbf..3e4ad886f4 100644 --- a/packages/base/catalog-entry.gts +++ b/packages/base/catalog-entry.gts @@ -3,73 +3,61 @@ import { field, Component, CardDef, - FieldDef, relativeTo, - realmInfo, + linksToMany, } from './card-api'; import StringField from './string'; import BooleanField from './boolean'; import CodeRef from './code-ref'; +import MarkdownField from './markdown'; -import { FieldContainer } from '@cardstack/boxel-ui/components'; import GlimmerComponent from '@glimmer/component'; import BoxModel from '@cardstack/boxel-icons/box-model'; +import BookOpenText from '@cardstack/boxel-icons/book-open-text'; +import LayersSubtract from '@cardstack/boxel-icons/layers-subtract'; +import GitBranch from '@cardstack/boxel-icons/git-branch'; +import { DiagonalArrowLeftUp } from '@cardstack/boxel-ui/icons'; +import { Pill } from '@cardstack/boxel-ui/components'; +import StackIcon from '@cardstack/boxel-icons/stack'; +import AppsIcon from '@cardstack/boxel-icons/apps'; +import LayoutList from '@cardstack/boxel-icons/layout-list'; +import Brain from '@cardstack/boxel-icons/brain'; + +export class SpecType extends StringField { + static displayName = 'Spec Type'; +} export class CatalogEntry extends CardDef { static displayName = 'Catalog Entry'; static icon = BoxModel; - @field title = contains(StringField); - @field description = contains(StringField); + @field name = contains(StringField); + @field readMe = contains(MarkdownField); + @field ref = contains(CodeRef); + @field type = contains(SpecType); - // If it's not a field, then it's a card - @field isField = contains(BooleanField); + @field isField = contains(BooleanField, { + computeVia: function (this: CatalogEntry) { + return this.type === 'field'; + }, + }); + @field isCard = contains(BooleanField, { + computeVia: function (this: CatalogEntry) { + return this.type === 'card'; + }, + }); @field moduleHref = contains(StringField, { computeVia: function (this: CatalogEntry) { return new URL(this.ref.module, this[relativeTo]).href; }, }); - @field demo = contains(FieldDef); - @field realmName = contains(StringField, { + @field examples = linksToMany(CardDef); + @field title = contains(StringField, { computeVia: function (this: CatalogEntry) { - return this[realmInfo]?.name; + return this.name || this.ref.name; }, }); - @field thumbnailURL = contains(StringField, { computeVia: () => null }); // remove this if we want card type entries to have images - - get showDemo() { - return !this.isField; - } - - // An explicit edit template is provided since computed isPrimitive bool - // field (which renders in the embedded format) looks a little wonky - // right now in the edit view. - static edit = class Edit extends Component { - - }; static fitted = class Fitted extends Component { + }; + } +`; + +const petCardSource = ` + import { contains, field, Component, CardDef } from "https://cardstack.com/base/card-api"; + import StringCard from "https://cardstack.com/base/string"; + + export class Pet extends CardDef { + static displayName = 'Pet'; + @field name = contains(StringCard); + @field title = contains(StringCard, { + computeVia: function (this: Pet) { + return this.name; + }, + }); + static embedded = class Embedded extends Component { + + } + static isolated = class Isolated extends Component { + + } + } +`; + +const employeeCardSource = ` + import { contains, field, Component, CardDef } from "https://cardstack.com/base/card-api"; + import StringCard from "https://cardstack.com/base/string"; + + export default class Employee extends CardDef { + static displayName = 'Employee'; + @field name = contains(StringCard); + @field title = contains(StringCard, { + computeVia: function (this: Pet) { + return this.name; + }, + }); + } +`; + +const newSkillCardSource = ` + import { contains, field, Component, CardDef } from "https://cardstack.com/base/card-api"; + import { SkillCard } from 'https://cardstack.com/base/skill-card'; + + export class NewSkill extends CardDef { + static displayName = 'NewSkill'; + } +`; + +let matrixRoomId: string; +module('boxel spec preview', function (hooks) { + let realm: Realm; + setupApplicationTest(hooks); + setupLocalIndexing(hooks); + setupServerSentEvents(hooks); + let { setActiveRealms, createAndJoinRoom } = setupMockMatrix(hooks, { + loggedInAs: '@testuser:staging', + activeRealms: [testRealmURL], + }); + + hooks.beforeEach(async function () { + matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test'); + setupUserSubscription(matrixRoomId); + + // this seeds the loader used during index which obtains url mappings + // from the global loader + ({ realm } = await setupAcceptanceTestRealm({ + contents: { + 'person.gts': personCardSource, + 'pet.gts': petCardSource, + 'employee.gts': employeeCardSource, + 'new-skill.gts': newSkillCardSource, + 'person-entry.json': { + data: { + type: 'card', + attributes: { + title: 'Person', + description: 'Catalog entry', + type: 'card', + ref: { + module: `./person`, + name: 'Person', + }, + }, + meta: { + adoptsFrom: { + module: `${baseRealm.url}catalog-entry`, + name: 'CatalogEntry', + }, + }, + }, + }, + 'employee-entry.json': { + data: { + type: 'card', + attributes: { + type: 'card', + ref: { + module: `./employee`, + name: 'default', + }, + }, + meta: { + adoptsFrom: { + module: `${baseRealm.url}catalog-entry`, + name: 'CatalogEntry', + }, + }, + }, + }, + 'pet-entry.json': { + data: { + type: 'card', + attributes: { + type: 'card', + ref: { + module: `./pet`, + name: 'Pet', + }, + }, + meta: { + adoptsFrom: { + module: `${baseRealm.url}catalog-entry`, + name: 'CatalogEntry', + }, + }, + }, + }, + 'pet-entry-2.json': { + data: { + type: 'card', + attributes: { + type: 'card', + ref: { + module: `./pet`, + name: 'Pet', + }, + }, + meta: { + adoptsFrom: { + module: `${baseRealm.url}catalog-entry`, + name: 'CatalogEntry', + }, + }, + }, + }, + 'Person/fadhlan.json': { + data: { + attributes: { + firstName: 'Fadhlan', + address: [ + { + city: 'Bandung', + country: 'Indonesia', + shippingInfo: { + preferredCarrier: 'DHL', + remarks: `Don't let bob deliver the package--he's always bringing it to the wrong address`, + }, + }, + ], + }, + relationships: { + pet: { + links: { + self: `${testRealmURL}Pet/mango`, + }, + }, + }, + meta: { + adoptsFrom: { + module: `${testRealmURL}person`, + name: 'Person', + }, + }, + }, + }, + 'Person/1.json': { + data: { + type: 'card', + attributes: { + firstName: 'Hassan', + lastName: 'Abdel-Rahman', + }, + meta: { + adoptsFrom: { + module: '../person', + name: 'Person', + }, + }, + }, + }, + 'Pet/mango.json': { + data: { + attributes: { + name: 'Mango', + }, + meta: { + adoptsFrom: { + module: `${testRealmURL}pet`, + name: 'Pet', + }, + }, + }, + }, + '.realm.json': { + name: 'Test Workspace B', + backgroundURL: + 'https://i.postimg.cc/VNvHH93M/pawel-czerwinski-Ly-ZLa-A5jti-Y-unsplash.jpg', + iconURL: 'https://i.postimg.cc/L8yXRvws/icon.png', + }, + }, + })); + setActiveRealms([testRealmURL]); + }); + test('view when there is a single boxel spec instance', async function (assert) { + await visitOperatorMode({ + submode: 'code', + codePath: `${testRealmURL}person.gts`, + }); + await waitFor('[data-test-accordion-item="boxel-spec-preview"]'); + assert.dom('[data-test-accordion-item="boxel-spec-preview"]').exists(); + assert.dom('[data-test-has-boxel-spec]').containsText('card'); + await click('[data-test-accordion-item="boxel-spec-preview"] button'); + await waitFor('[data-test-boxel-spec-selector]'); + assert.dom('[data-test-boxel-spec-selector]').exists(); + await percySnapshot(assert); + assert.dom('[data-test-title]').containsText('Person'); + assert.dom('[data-test-description]').containsText('Catalog entry'); + assert.dom('[data-test-module-href]').containsText(`${testRealmURL}person`); + assert.dom('[data-test-exported-name]').containsText('Person'); + assert.dom('[data-test-exported-type]').containsText('card'); + }); + test('view when there are multiple boxel spec instances', async function (assert) { + await visitOperatorMode({ + submode: 'code', + codePath: `${testRealmURL}pet.gts`, + }); + await waitFor('[data-test-accordion-item="boxel-spec-preview"]'); + assert.dom('[data-test-accordion-item="boxel-spec-preview"]').exists(); + assert.dom('[data-test-has-boxel-spec]').containsText('2 instances'); + await click('[data-test-accordion-item="boxel-spec-preview"] button'); + await waitFor('[data-test-boxel-spec-selector]'); + assert.dom('[data-test-boxel-spec-selector]').exists(); + assert.dom('[data-test-caret-down]').exists(); + }); + test('view when there are no boxel spec instances', async function (assert) { + await visitOperatorMode({ + submode: 'code', + codePath: `${testRealmURL}new-skill.gts`, + }); + await waitFor('[data-test-accordion-item="boxel-spec-preview"]'); + assert.dom('[data-test-accordion-item="boxel-spec-preview"]').exists(); + assert.dom('[data-test-create-boxel-spec-button]').exists(); + assert.dom('[data-test-create-boxel-spec-intent-message]').exists(); + }); + test('have ability to create new boxel spec instances', async function (assert) { + await visitOperatorMode({ + submode: 'code', + codePath: `${testRealmURL}new-skill.gts`, + }); + assert.dom('[data-test-create-boxel-spec-button]').exists(); + await click('[data-test-create-boxel-spec-button]'); + assert.dom('[data-test-create-file-modal]').exists(); + await waitFor('[data-test-create-boxel-spec-instance]'); + assert.dom('[data-test-selected-type="NewSkill"]'); + await click('[data-test-create-boxel-spec-instance]'); + await waitFor('[data-test-field="type"]'); + assert.dom('[data-test-field="type"] input').hasValue('card'); + }); + test('title does not default to "default"', async function (assert) { + await visitOperatorMode({ + submode: 'code', + codePath: `${testRealmURL}employee.gts`, + }); + await waitFor('[data-test-accordion-item="boxel-spec-preview"]'); + assert.dom('[data-test-accordion-item="boxel-spec-preview"]').exists(); + await click('[data-test-accordion-item="boxel-spec-preview"] button'); + assert.dom('[data-test-title]').doesNotContainText('default'); + assert.dom('[data-test-exported-name]').containsText('default'); + }); +}); From cae1a39e18379a9a4000be153aa01cb91056e777 Mon Sep 17 00:00:00 2001 From: tintinthong Date: Wed, 15 Jan 2025 11:11:34 -0500 Subject: [PATCH 28/42] fix lint --- packages/host/app/components/operator-mode/code-submode.gts | 3 ++- .../host/app/components/operator-mode/create-file-modal.gts | 3 ++- .../host/tests/acceptance/code-submode/boxel-spec-test.gts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/host/app/components/operator-mode/code-submode.gts b/packages/host/app/components/operator-mode/code-submode.gts index 30f18eafd3..2d41594e22 100644 --- a/packages/host/app/components/operator-mode/code-submode.gts +++ b/packages/host/app/components/operator-mode/code-submode.gts @@ -56,6 +56,8 @@ import type RecentFilesService from '@cardstack/host/services/recent-files-servi import type { CardDef, Format } from 'https://cardstack.com/base/card-api'; +import { type BoxelSpecType } from 'https://cardstack.com/base/catalog-entry'; + import { htmlComponent } from '../../lib/html-component'; import { CodeModePanelWidths } from '../../utils/local-storage-keys'; import FileTree from '../editor/file-tree'; @@ -74,7 +76,6 @@ import DeleteModal from './delete-modal'; import DetailPanel from './detail-panel'; import NewFileButton from './new-file-button'; import SubmodeLayout from './submode-layout'; -import { type BoxelSpecType } from 'https://cardstack.com/base/catalog-entry'; interface Signature { Args: { diff --git a/packages/host/app/components/operator-mode/create-file-modal.gts b/packages/host/app/components/operator-mode/create-file-modal.gts index 56ed0eb29f..4a72f5912d 100644 --- a/packages/host/app/components/operator-mode/create-file-modal.gts +++ b/packages/host/app/components/operator-mode/create-file-modal.gts @@ -44,6 +44,8 @@ import type RealmService from '@cardstack/host/services/realm'; import type { CardDef } from 'https://cardstack.com/base/card-api'; import type { CatalogEntry } from 'https://cardstack.com/base/catalog-entry'; +import { type BoxelSpecType } from 'https://cardstack.com/base/catalog-entry'; + import { cleanseString } from '../../lib/utils'; import ModalContainer from '../modal-container'; @@ -54,7 +56,6 @@ import WithKnownRealmsLoaded from '../with-known-realms-loaded'; import type CardService from '../../services/card-service'; import type NetworkService from '../../services/network'; -import { type BoxelSpecType } from 'https://cardstack.com/base/catalog-entry'; export type NewFileType = | 'duplicate-instance' diff --git a/packages/host/tests/acceptance/code-submode/boxel-spec-test.gts b/packages/host/tests/acceptance/code-submode/boxel-spec-test.gts index 4ba487121a..d8134e44f8 100644 --- a/packages/host/tests/acceptance/code-submode/boxel-spec-test.gts +++ b/packages/host/tests/acceptance/code-submode/boxel-spec-test.gts @@ -325,7 +325,7 @@ module('boxel spec preview', function (hooks) { await click('[data-test-create-boxel-spec-button]'); assert.dom('[data-test-create-file-modal]').exists(); await waitFor('[data-test-create-boxel-spec-instance]'); - assert.dom('[data-test-selected-type="NewSkill"]'); + assert.dom('[data-test-selected-type="NewSkill"]').exists(); await click('[data-test-create-boxel-spec-instance]'); await waitFor('[data-test-field="type"]'); assert.dom('[data-test-field="type"] input').hasValue('card'); From 453be4edfe8e887f28431be932dcf70c2d3cf54a Mon Sep 17 00:00:00 2001 From: tintinthong Date: Thu, 16 Jan 2025 23:37:30 -0500 Subject: [PATCH 29/42] fix lint for test --- .../host/tests/acceptance/code-submode/boxel-spec-test.gts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/host/tests/acceptance/code-submode/boxel-spec-test.gts b/packages/host/tests/acceptance/code-submode/boxel-spec-test.gts index d8134e44f8..ff3b2d2e24 100644 --- a/packages/host/tests/acceptance/code-submode/boxel-spec-test.gts +++ b/packages/host/tests/acceptance/code-submode/boxel-spec-test.gts @@ -112,7 +112,6 @@ const newSkillCardSource = ` let matrixRoomId: string; module('boxel spec preview', function (hooks) { - let realm: Realm; setupApplicationTest(hooks); setupLocalIndexing(hooks); setupServerSentEvents(hooks); @@ -127,7 +126,7 @@ module('boxel spec preview', function (hooks) { // this seeds the loader used during index which obtains url mappings // from the global loader - ({ realm } = await setupAcceptanceTestRealm({ + await setupAcceptanceTestRealm({ contents: { 'person.gts': personCardSource, 'pet.gts': petCardSource, @@ -272,7 +271,7 @@ module('boxel spec preview', function (hooks) { iconURL: 'https://i.postimg.cc/L8yXRvws/icon.png', }, }, - })); + }); setActiveRealms([testRealmURL]); }); test('view when there is a single boxel spec instance', async function (assert) { From 95235dff3fd03ef94f161508ee0ba843a621da31 Mon Sep 17 00:00:00 2001 From: tintinthong Date: Fri, 17 Jan 2025 00:42:52 -0500 Subject: [PATCH 30/42] rename type -> specType --- packages/base/cards-grid.gts | 4 ++-- packages/base/catalog-entry.gts | 20 +++++++++---------- packages/base/fields/base64-image.json | 2 +- packages/base/fields/biginteger-field.json | 2 +- packages/base/fields/boolean-field.json | 2 +- packages/base/fields/code-ref-field.json | 2 +- packages/base/fields/date-field.json | 2 +- packages/base/fields/datetime-field.json | 2 +- .../base/fields/ethereum-address-field.json | 2 +- packages/base/fields/field.json | 2 +- packages/base/fields/markdown-field.json | 2 +- packages/base/fields/number-field.json | 2 +- packages/base/fields/skill-card.json | 2 +- packages/base/fields/string-field.json | 2 +- packages/base/fields/text-area-field.json | 2 +- packages/base/types/card.json | 2 +- .../experiments-realm/CatalogEntry/1.json | 2 +- .../experiments-realm/CatalogEntry/10.json | 2 +- .../experiments-realm/CatalogEntry/11.json | 2 +- .../experiments-realm/CatalogEntry/2.json | 2 +- .../experiments-realm/CatalogEntry/6.json | 2 +- .../experiments-realm/CatalogEntry/7.json | 2 +- .../experiments-realm/CatalogEntry/app.json | 2 +- .../CatalogEntry/author.json | 2 +- .../CatalogEntry/blog-post.json | 2 +- .../experiments-realm/CatalogEntry/blog.json | 2 +- .../CatalogEntry/crm-app.json | 2 +- .../fields/contact-link-field.json | 2 +- .../CatalogEntry/fields/email-field.json | 2 +- .../fields/featured-image-field.json | 2 +- .../CatalogEntry/fields/rating-field.json | 2 +- .../CatalogEntry/fields/url-field.json | 2 +- .../CatalogEntry/plant-info.json | 2 +- .../product-requirement-document.json | 2 +- .../experiments-realm/CatalogEntry/puppy.json | 2 +- .../CatalogEntry/sprint-planner.json | 2 +- .../CatalogEntry/sprint-task.json | 2 +- .../experiments-realm/CatalogEntry/tag.json | 2 +- .../experiments-realm/CatalogEntry/task.json | 2 +- .../CatalogEntry/team-member.json | 2 +- .../experiments-realm/CatalogEntry/team.json | 2 +- .../experiments-realm/CatalogEntry/todo.json | 2 +- .../code-submode/boxel-spec-preview.gts | 10 +++++----- .../operator-mode/create-file-modal.gts | 2 +- .../tests/acceptance/code-submode-test.ts | 6 +++--- .../code-submode/boxel-spec-test.gts | 10 ++++------ .../code-submode/create-file-test.gts | 6 +++--- .../acceptance/code-submode/file-tree-test.ts | 2 +- .../acceptance/code-submode/inspector-test.ts | 2 +- .../code-submode/recent-files-test.ts | 2 +- .../code-submode/schema-editor-test.ts | 2 +- .../acceptance/interact-submode-test.gts | 8 ++++---- .../operator-mode-acceptance-test.gts | 2 +- .../acceptance/permissioned-realm-test.gts | 2 +- .../components/card-catalog-test.gts | 14 ++++++------- .../components/operator-mode-test.gts | 6 +++--- .../prerendered-card-search-test.gts | 4 ++-- .../realm-indexing-and-querying-test.gts | 20 +++++++++---------- .../integration/resources/search-test.ts | 4 ++-- packages/seed-realm/CatalogEntry/blog.json | 2 +- .../seed-realm/CatalogEntry/hello-world.json | 2 +- .../CatalogEntry/mortgage-calculator.json | 2 +- .../seed-realm/CatalogEntry/product-list.json | 2 +- .../CatalogEntry/product-with-video.json | 2 +- packages/seed-realm/CatalogEntry/product.json | 2 +- .../seed-realm/CatalogEntry/review-blog.json | 2 +- packages/seed-realm/CatalogEntry/review.json | 2 +- packages/seed-realm/CatalogEntry/seller.json | 2 +- packages/seed-realm/CatalogEntry/skill.json | 2 +- .../CatalogEntry/sprint-planner.json | 2 +- .../seed-realm/CatalogEntry/sprint-task.json | 2 +- packages/seed-realm/CatalogEntry/tag.json | 2 +- packages/seed-realm/CatalogEntry/task.json | 2 +- .../seed-realm/CatalogEntry/team-member.json | 2 +- packages/seed-realm/CatalogEntry/team.json | 2 +- packages/seed-realm/CatalogEntry/todo.json | 2 +- .../CatalogEntry/video-product.json | 2 +- 77 files changed, 120 insertions(+), 122 deletions(-) diff --git a/packages/base/cards-grid.gts b/packages/base/cards-grid.gts index 059516279a..a3cd2bab2c 100644 --- a/packages/base/cards-grid.gts +++ b/packages/base/cards-grid.gts @@ -377,7 +377,7 @@ class Isolated extends Component { private createCard = restartableTask(async () => { let preselectedCardTypeQuery: Query | undefined; - let activeFilterRef = this.activeFilter?.query?.filter?.type; + let activeFilterRef = this.activeFilter?.query?.filter?.specType; if (activeFilterRef) { preselectedCardTypeQuery = { filter: { @@ -396,7 +396,7 @@ class Isolated extends Component { { filter: { on: catalogEntryRef, - every: [{ eq: { type: 'card' } }], + every: [{ eq: { specType: 'card' } }], }, }, { preselectedCardTypeQuery }, diff --git a/packages/base/catalog-entry.gts b/packages/base/catalog-entry.gts index 8ff0893097..66c93cbb56 100644 --- a/packages/base/catalog-entry.gts +++ b/packages/base/catalog-entry.gts @@ -36,17 +36,17 @@ export class CatalogEntry extends CardDef { @field readMe = contains(MarkdownField); @field ref = contains(CodeRef); - @field type = contains(SpecType); + @field specType = contains(SpecType); @field isField = contains(BooleanField, { computeVia: function (this: CatalogEntry) { - return this.type === 'field'; + return this.specType === 'field'; }, }); @field isCard = contains(BooleanField, { computeVia: function (this: CatalogEntry) { - return this.type === 'card'; + return this.specType === 'card'; }, }); @field moduleHref = contains(StringField, { @@ -149,7 +149,7 @@ export class CatalogEntry extends CardDef { {{@model.ref.name}}
- {{@model.type}} + {{@model.specType}}
@@ -249,8 +249,8 @@ export class CatalogEntry extends CardDef {

<@fields.description />

- {{#if @model.type}} - + {{#if @model.specType}} + {{/if}}
@@ -317,24 +317,24 @@ class CatalogEntryContainer extends GlimmerComponent { interface SpecTagSignature { Element: HTMLDivElement; Args: { - type: string; + specType: string; }; } export class SpecTag extends GlimmerComponent { get icon() { - return getIcon(this.args.type); + return getIcon(this.args.specType); }