Skip to content

Commit

Permalink
Playground panel accordion item (#2098)
Browse files Browse the repository at this point in the history
* cs-7927 display playground-panel when clicked on card-def

* add instance chooser

* update getCards and add tests

* lint fix

* rename getter for clarity
  • Loading branch information
burieberry authored Jan 28, 2025
1 parent 68873c5 commit 593f8cf
Show file tree
Hide file tree
Showing 8 changed files with 364 additions and 14 deletions.
2 changes: 1 addition & 1 deletion packages/boxel-ui/addon/raw-icons/loading-indicator.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ interface Signature {
Args: {
color?: string;
};
Element: HTMLDivElement;
Element: HTMLSpanElement;
}

const LoadingIndicator: TemplateOnlyComponent<Signature> = <template>
<div
<span
class='boxel-loading-indicator'
data-test-loading-indicator
...attributes
>
<LoadingIndicatorIcon
style={{cssVar icon-color=(if @color @color '#000')}}
style={{cssVar icon-color=@color}}
role='presentation'
/>
</div>
</span>
<style scoped>
/* zero specificity default sizing */
:where(.boxel-loading-indicator) {
Expand Down
5 changes: 4 additions & 1 deletion packages/boxel-ui/addon/src/components/select/trigger.gts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ export class BoxelTriggerWrapper extends Component<TriggerSignature> {
gap: var(--boxel-sp-xxs);
}
.boxel-trigger-placeholder {
color: var(--boxel-400);
color: var(--boxel-450);
font: var(--boxel-font-sm);
letter-spacing: var(--boxel-lsp-sm);
}
</style>
<style scoped>
Expand Down Expand Up @@ -99,6 +101,7 @@ export class BoxelSelectDefaultTrigger extends Component<TriggerSignature> {
.icon {
width: 10px;
height: 10px;
flex-shrink: 0;
}
.is-open {
transform: rotate(180deg);
Expand Down
2 changes: 1 addition & 1 deletion packages/boxel-ui/addon/src/icons/loading-indicator.gts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const IconComponent: TemplateOnlyComponent<Signature> = <template>
...attributes
><g
fill='none'
stroke='var(--icon-color, #000)'
stroke='var(--icon-color, currentColor)'
stroke-linecap='round'
stroke-linejoin='round'
stroke-width='1.5'
Expand Down
21 changes: 19 additions & 2 deletions packages/host/app/components/operator-mode/code-submode.gts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { and, not, bool, eq } from '@cardstack/boxel-ui/helpers';
import { File } from '@cardstack/boxel-ui/icons';

import {
isCardDef,
isCardDocumentString,
hasExecutableExtension,
RealmPaths,
Expand Down Expand Up @@ -57,7 +58,7 @@ import type OperatorModeStateService from '@cardstack/host/services/operator-mod
import type RealmService from '@cardstack/host/services/realm';
import type RecentFilesService from '@cardstack/host/services/recent-files-service';

import { type CardDef, type Format } from 'https://cardstack.com/base/card-api';
import type { CardDef, Format } from 'https://cardstack.com/base/card-api';

import { type BoxelSpecType } from 'https://cardstack.com/base/catalog-entry';

Expand All @@ -73,6 +74,7 @@ import CodeEditor from './code-editor';
import BoxelSpecPreview from './code-submode/boxel-spec-preview';
import InnerContainer from './code-submode/inner-container';
import CodeSubmodeLeftPanelToggle from './code-submode/left-panel-toggle';
import PlaygroundPanel from './code-submode/playground-panel';
import SchemaEditor, { SchemaEditorTitle } from './code-submode/schema-editor';
import CreateFileModal, { type FileType } from './create-file-modal';
import DeleteModal from './delete-modal';
Expand Down Expand Up @@ -496,6 +498,17 @@ export default class CodeSubmode extends Component<Signature> {
return undefined;
}

private get shouldDisplayPlayground() {
if (!isPlaygroundEnabled) {
return false;
}
let declaration = this.selectedDeclaration;
if (!declaration || !('cardOrField' in declaration)) {
return false;
}
return isCardDef(declaration.cardOrField);
}

get showBoxelSpecPreview() {
return (
!this.moduleContentsResource.isLoading &&
Expand Down Expand Up @@ -983,7 +996,7 @@ export default class CodeSubmode extends Component<Signature> {
</:content>
</A.Item>
</SchemaEditor>
{{#if isPlaygroundEnabled}}
{{#if this.shouldDisplayPlayground}}
<A.Item
class='accordion-item'
@contentClass='accordion-item-content'
Expand All @@ -993,6 +1006,10 @@ export default class CodeSubmode extends Component<Signature> {
>
<:title>Playground</:title>
<:content>
<PlaygroundPanel
@moduleContentsResource={{this.moduleContentsResource}}
@cardType={{this.selectedCardOrField.cardType}}
/>
</:content>
</A.Item>
{{/if}}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import type { TemplateOnlyComponent } from '@ember/component/template-only';
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { cached, tracked } from '@glimmer/tracking';

import { LoadingIndicator, BoxelSelect } from '@cardstack/boxel-ui/components';

import { getCards, type ResolvedCodeRef } from '@cardstack/runtime-common';

import { getCodeRef, type CardType } from '@cardstack/host/resources/card-type';
import { ModuleContentsResource } from '@cardstack/host/resources/module-contents';

import type RealmServerService from '@cardstack/host/services/realm-server';

import type { CardDef } from 'https://cardstack.com/base/card-api';

const getItemTitle = (item: CardDef, displayName?: string) => {
if (!item) {
return;
}
if (item.title) {
return item.title;
}
let fallbackName = displayName ?? item.constructor.displayName ?? 'Card';
return `Untitled ${fallbackName}`;
};

const SelectedItem: TemplateOnlyComponent<{ Args: { title?: string } }> =
<template>
<div class='selected-item'>
Instance:
<span class='title' data-test-selected-item>
{{@title}}
</span>
</div>
<style scoped>
.selected-item {
display: flex;
align-items: center;
gap: var(--boxel-sp-xs);
overflow: hidden;
font: 600 var(--boxel-font-xs);
letter-spacing: var(--boxel-lsp-sm);
}
.title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
</style>
</template>;

interface PlaygroundContentSignature {
Args: {
codeRef: ResolvedCodeRef;
displayName?: string;
};
}
class PlaygroundPanelContent extends Component<PlaygroundContentSignature> {
<template>
<BoxelSelect
class='instance-chooser'
@options={{this.instances}}
@selected={{this.selectedItem}}
@selectedItemComponent={{if
this.selectedItem
(component
SelectedItem title=(getItemTitle this.selectedItem @displayName)
)
}}
@onChange={{this.onSelect}}
@placeholder='Please Select'
data-test-instance-chooser
as |item|
>
{{getItemTitle item @displayName}}
</BoxelSelect>
<style scoped>
.instance-chooser {
color: var(--boxel-dark);
height: var(--boxel-form-control-height);
}
</style>
</template>

@service private declare realmServer: RealmServerService;
@tracked private selectedItem?: CardDef;

private options = getCards(
() => ({
filter: { type: this.args.codeRef },
sort: [{ by: 'createdAt', direction: 'desc' }],
}),
() => this.realmServer.availableRealmURLs,
);

@cached
private get instances() {
if (this.options?.isLoading) {
return undefined;
}
return this.options.instances;
}

@action private onSelect(item: CardDef) {
this.selectedItem = item;
}
}

interface Signature {
Args: {
moduleContentsResource: ModuleContentsResource;
cardType?: CardType;
};
Element: HTMLElement;
}
export default class PlaygroundPanel extends Component<Signature> {
<template>
<section class='playground-panel' data-test-playground-panel>
{{#if this.isLoading}}
<LoadingIndicator class='loading-icon' />
Loading...
{{else if @cardType.type}}
{{#let (getCodeRef @cardType.type) as |codeRef|}}
{{#if codeRef}}
<PlaygroundPanelContent
@codeRef={{codeRef}}
@displayName={{@cardType.type.displayName}}
/>
{{else}}
Error: Playground could not be loaded.
{{/if}}
{{/let}}
{{else}}
{{! TODO: error state }}
Error: Playground could not be loaded.
{{/if}}
</section>
<style scoped>
.playground-panel {
background-image: url('./playground-background.png');
background-position: left top;
background-repeat: repeat;
background-size: 22.5px;
height: 100%;
width: 100%;
padding: var(--boxel-sp);
background-color: var(--boxel-dark);
color: var(--boxel-light);
font: var(--boxel-font-sm);
letter-spacing: var(--boxel-lsp-xs);
overflow: auto;
}
.loading-icon {
display: inline-block;
margin-right: var(--boxel-sp-xxxs);
vertical-align: middle;
}
</style>
</template>

get isLoading() {
return (
this.args.moduleContentsResource.isLoadingNewModule ||
this.args.cardType?.isLoading
);
}
}
Loading

0 comments on commit 593f8cf

Please sign in to comment.