-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhance catalog entry and code mode RHS panel to be able to create new catalog entry #1993
Changes from 23 commits
b8cdbe7
de4baaa
f488ac9
1a24148
47b8e08
30732e5
4d5451b
651302e
63222a9
997799a
6b26bef
ac05498
dc40e25
a6dcabf
d9978cd
80a7386
e8ac5db
35d479f
b21fbb4
0baf052
1ec41ea
5102958
adab3db
99c4674
d3244be
d4a6244
6a2b2aa
cae1a39
453be4e
95235df
18643fc
58801d8
9cc39f3
63ed23a
36de466
c6c26ff
f5e5845
1bc9750
a6402ac
c231d13
08cce23
a4801a0
32be607
4a32548
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
tintinthong marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed this for now and will treat adding examples of different boxel spec in separate PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this actually needed a lot more discussion in terms of how we render this as it should be a polymorphic card and not a field. but yeah, good to remove for now... |
||
@field realmName = contains(StringField, { | ||
@field examples = linksToMany(CardDef); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. stub this for now. We need to consider ways to model examples for both fieldDef and cardDef. I think @demo used to be the way to do this but let's think about this in another PR. We may very well just re-include demo but we need to investigate its usage more carefully. But, given fields are mostly displayed in cards anyway, maybe we only want to link to cards for now |
||
@field title = contains(StringField, { | ||
computeVia: function (this: CatalogEntry) { | ||
return this[realmInfo]?.name; | ||
return this.name || this.ref.name; | ||
}, | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't work well for default exports. The ref name will be 'default' for each one |
||
@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<typeof this> { | ||
<template> | ||
<CatalogEntryContainer> | ||
<FieldContainer @tag='label' @label='Title' data-test-field='title'> | ||
<@fields.title /> | ||
</FieldContainer> | ||
<FieldContainer | ||
@tag='label' | ||
@label='Description' | ||
data-test-field='description' | ||
> | ||
<@fields.description /> | ||
</FieldContainer> | ||
<FieldContainer @label='Ref' data-test-field='ref'> | ||
<@fields.ref /> | ||
</FieldContainer> | ||
<FieldContainer @label='Workspace Name' data-test-field='realmName'> | ||
<@fields.realmName /> | ||
</FieldContainer> | ||
<FieldContainer @vertical={{true}} @label='Demo' data-test-field='demo'> | ||
<@fields.demo /> | ||
</FieldContainer> | ||
</CatalogEntryContainer> | ||
</template> | ||
}; | ||
|
||
static fitted = class Fitted extends Component<typeof this> { | ||
<template> | ||
|
@@ -102,30 +90,193 @@ export class CatalogEntry extends CardDef { | |
}; | ||
|
||
static isolated = class Isolated extends Component<typeof this> { | ||
get icon() { | ||
return this.args.model.constructor?.icon; | ||
} | ||
<template> | ||
<CatalogEntryContainer class='container'> | ||
tintinthong marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<h1 data-test-title><@fields.title /></h1> | ||
<em data-test-description><@fields.description /></em> | ||
<div data-test-ref> | ||
Module: | ||
<@fields.moduleHref /> | ||
Name: | ||
{{@model.ref.name}} | ||
<div class='container'> | ||
<div class='header'> | ||
<div class='header-icon-container'> | ||
<this.icon class='box header-icon-svg' /> | ||
</div> | ||
<div class='header-info-container'> | ||
<div class='box'> | ||
<h1 data-test-title><@fields.title /></h1> | ||
<em data-test-description><@fields.description /></em> | ||
</div> | ||
</div> | ||
</div> | ||
<div class='realm-name' data-test-realm-name> | ||
in | ||
<@fields.realmName /> | ||
<div class='readme section'> | ||
<div class='row-header'> | ||
<BookOpenText /> | ||
Read Me | ||
</div> | ||
{{#if @model.readMe}} | ||
<div class='box'> | ||
<@fields.readMe /> | ||
</div> | ||
{{/if}} | ||
</div> | ||
{{#if @model.showDemo}} | ||
<div data-test-demo><@fields.demo /></div> | ||
{{/if}} | ||
</CatalogEntryContainer> | ||
<div class='examples section'> | ||
<div class='row-header'> | ||
<LayersSubtract /> | ||
Examples | ||
</div> | ||
<@fields.examples /> | ||
</div> | ||
<div class='module section'> | ||
<div class='row-header'> | ||
<GitBranch /> | ||
Module</div> | ||
<div class='container-code-ref'> | ||
<div class='row-code-ref'> | ||
<div class='row-code-ref-label'>URL</div> | ||
<div class='row-code-ref-value box' data-test-module-href> | ||
{{@model.moduleHref}} | ||
</div> | ||
</div> | ||
<div class='row-code-ref'> | ||
<div class='row-code-ref-label'>Exported Name</div> | ||
<div class='row-code-ref-value box'> | ||
<div class='exported-row'> | ||
<div class='exported-name' data-test-exported-name> | ||
<DiagonalArrowLeftUp class='exported-arrow' /> | ||
{{@model.ref.name}} | ||
</div> | ||
<div class='exported-type' data-test-exported-type> | ||
{{@model.type}} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<style scoped> | ||
.container { | ||
padding: var(--boxel-sp); | ||
background-color: var(--boxel-200); | ||
} | ||
.box { | ||
border: 2px solid var(--boxel-border-color); | ||
border-radius: var(--boxel-border-radius-lg); | ||
padding: var(--boxel-sp-xs); | ||
background-color: var(--boxel-light); | ||
} | ||
.header { | ||
display: flex; | ||
gap: var(--boxel-sp-sm); | ||
} | ||
.section { | ||
padding: var(--boxel-sp-sm); | ||
} | ||
.header-icon-container { | ||
padding: var(--boxel-sp-sm); | ||
flex-shrink: 0; | ||
} | ||
.header-icon-svg { | ||
height: var(--boxel-icon-xxl); | ||
width: var(--boxel-icon-xxl); | ||
border: 2px solid var(--boxel-border-color); | ||
border-radius: var(--boxel-border-radius-lg); | ||
} | ||
.header-info-container { | ||
padding: var(--boxel-sp-sm); | ||
flex: 1; | ||
} | ||
.row-header { | ||
display: inline-flex; | ||
align-items: center; | ||
justify-content: center; | ||
gap: var(--boxel-sp-xs); | ||
font-weight: 600; | ||
} | ||
.container-code-ref { | ||
display: flex; | ||
flex-direction: column; | ||
gap: var(--boxel-sp-xs); | ||
} | ||
.row-code-ref { | ||
display: flex; | ||
flex-direction: column; | ||
gap: var(--boxel-sp-xs); | ||
} | ||
.row-code-ref-value { | ||
background-color: var(--boxel-300); | ||
white-space: nowrap; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
} | ||
.exported-row { | ||
display: flex; | ||
justify-content: space-between; | ||
} | ||
.exported-name { | ||
display: inline-flex; | ||
align-items: center; | ||
gap: var(--boxel-sp-xxs); | ||
} | ||
.exported-type { | ||
text-transform: uppercase; | ||
font: 500 var(--boxel-font-xs); | ||
color: var(--boxel-450); | ||
letter-spacing: var(--boxel-lsp-xl); | ||
} | ||
.realm-name { | ||
color: var(--boxel-teal); | ||
.exported-arrow { | ||
width: var(--boxel-icon-xxs); | ||
height: var(--boxel-icon-xxs); | ||
--icon-color: var(--boxel-teal); | ||
} | ||
</style> | ||
</template> | ||
}; | ||
|
||
static embedded = class Embedded extends Component<typeof this> { | ||
get icon() { | ||
return this.args.model.constructor?.icon; | ||
} | ||
<template> | ||
<div class='header'> | ||
<div class='header-icon-container'> | ||
<this.icon class='box header-icon-svg' /> | ||
</div> | ||
<div class='header-info-container'> | ||
<header class='title'><@fields.title /></header> | ||
<p class='description'><@fields.description /></p> | ||
</div> | ||
<div class='pill-container'> | ||
{{#if @model.type}} | ||
<SpecTag @type={{@model.type}} /> | ||
{{/if}} | ||
</div> | ||
</div> | ||
<style scoped> | ||
.header { | ||
display: flex; | ||
align-items: center; | ||
gap: var(--boxel-sp-sm); | ||
} | ||
.header-icon-container { | ||
flex-shrink: 0; | ||
padding: var(--boxel-sp-sm); | ||
} | ||
.header-info-container { | ||
flex: 1; | ||
} | ||
.pill-container { | ||
padding-right: var(--boxel-sp-sm); | ||
} | ||
.header-icon-svg { | ||
width: 50px; | ||
height: 50px; | ||
border: 2px solid var(--boxel-border-color); | ||
border-radius: var(--boxel-border-radius-lg); | ||
} | ||
.title { | ||
font: 600 var(--boxel-font-sm); | ||
} | ||
.description { | ||
margin: 0; | ||
color: var(--boxel-500); | ||
font-size: var(--boxel-font-size-xs); | ||
} | ||
</style> | ||
|
@@ -157,3 +308,52 @@ class CatalogEntryContainer extends GlimmerComponent<Signature> { | |
</style> | ||
</template> | ||
} | ||
|
||
interface SpecTagSignature { | ||
Element: HTMLDivElement; | ||
Args: { | ||
type: string; | ||
}; | ||
} | ||
|
||
export class SpecTag extends GlimmerComponent<SpecTagSignature> { | ||
get icon() { | ||
return getIcon(this.args.type); | ||
} | ||
<template> | ||
{{#if this.icon}} | ||
<Pill class='spec-tag-pill' ...attributes> | ||
<:iconLeft> | ||
<div class='spec-tagicon'> | ||
{{this.icon}} | ||
</div> | ||
</:iconLeft> | ||
<:default> | ||
{{@type}} | ||
</:default> | ||
</Pill> | ||
|
||
{{/if}} | ||
<style scoped> | ||
.spec-tag-pill { | ||
--pill-font: 500 var(--boxel-font-xs); | ||
--pill-background-color: var(--boxel-200); | ||
} | ||
</style> | ||
</template> | ||
} | ||
|
||
function getIcon(specType: string) { | ||
switch (specType) { | ||
case 'card': | ||
return StackIcon; | ||
case 'app': | ||
return AppsIcon; | ||
case 'field': | ||
return LayoutList; | ||
case 'skill': | ||
return Brain; | ||
default: | ||
return; | ||
} | ||
Comment on lines
+388
to
+400
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are the different types of boxel specs we are supporting for now |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Focus on the last newly created catalog entry, if there are more than one