Skip to content

Commit

Permalink
Add ability to choose another card to instance chooser
Browse files Browse the repository at this point in the history
  • Loading branch information
FadhlanR committed Feb 13, 2025
1 parent 9fe7e54 commit 7fc8d66
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 3 deletions.
7 changes: 6 additions & 1 deletion packages/boxel-ui/addon/src/components/select/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ const BoxelSelect: TemplateOnlyComponent<Signature> = <template>
@matchTriggerWidth={{@matchTriggerWidth}}
@eventType='click'
@searchEnabled={{@searchEnabled}}
@beforeOptionsComponent={{component BeforeOptions autofocus=false}}
@beforeOptionsComponent={{if
@beforeOptionsComponent
@beforeOptionsComponent
(component BeforeOptions autofocus=false)
}}
@afterOptionsComponent={{@afterOptionsComponent}}
...attributes
as |item|
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { TemplateOnlyComponent } from '@ember/component/template-only';
import { on } from '@ember/modifier';
import { action } from '@ember/object';
import type Owner from '@ember/owner';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

import { task } from 'ember-concurrency';
import Folder from '@cardstack/boxel-icons/folder';
import { restartableTask, task } from 'ember-concurrency';
import perform from 'ember-concurrency/helpers/perform';
import window from 'ember-window-mock';
import { TrackedObject } from 'tracked-built-ins';

Expand All @@ -21,6 +24,7 @@ import { Eye, IconCode, IconLink } from '@cardstack/boxel-ui/icons';
import {
cardTypeDisplayName,
cardTypeIcon,
chooseCard,
type Query,
type ResolvedCodeRef,
} from '@cardstack/runtime-common';
Expand Down Expand Up @@ -81,6 +85,75 @@ const SelectedItem: TemplateOnlyComponent<{ Args: { title?: string } }> =
</style>
</template>;

const BeforeOptions: TemplateOnlyComponent<{ Args: {} }> = <template>
<div class='before-options'>
<span class='title'>
Recent
</span>
</div>
<style scoped>
.before-options {
width: 100%;
background-color: var(--boxel-light);
padding: var(--boxel-sp-xs) calc(var(--boxel-sp-xxs) + var(--boxel-sp-xs))
var(--boxel-sp-xxxs) calc(var(--boxel-sp-xxs) + var(--boxel-sp-xs));
}
.title {
font: 600 var(--boxel-font-sm);
}
</style>
</template>;

interface AfterOptionsSignature {
Args: {
chooseCard: () => void;
};
}
const AfterOptions: TemplateOnlyComponent<AfterOptionsSignature> = <template>
<div class='after-options'>
<span class='title'>
Action
</span>
<button
class='action'
{{on 'click' @chooseCard}}
data-test-choose-another-instance
>
<Folder width='16px' height='16px' />
Choose another instance
</button>
</div>
<style scoped>
.after-options {
display: flex;
flex-direction: column;
border-top: var(--boxel-border);
background-color: var(--boxel-light);
padding: var(--boxel-sp-xxs) var(--boxel-sp-xs);
margin-top: var(--boxel-sp-xs);
gap: var(--boxel-sp-xxs);
}
.title {
font: 600 var(--boxel-font-sm);
padding: var(--boxel-sp-xs) var(--boxel-sp-xxs) var(--boxel-sp-xxxs)
var(--boxel-sp-xxs);
}
.action {
display: flex;
align-items: center;
font: 500 var(--boxel-font-sm);
border: none;
background-color: transparent;
gap: var(--boxel-sp-xs);
padding: var(--boxel-sp-xs);
border-radius: var(--boxel-border-radius);
}
.action:hover {
background-color: var(--boxel-100);
}
</style>
</template>;

interface PlaygroundContentSignature {
Args: {
codeRef: ResolvedCodeRef;
Expand Down Expand Up @@ -115,6 +188,11 @@ class PlaygroundPanelContent extends Component<PlaygroundContentSignature> {
@renderInPlace={{true}}
@onChange={{this.onSelect}}
@placeholder='Please Select'
@beforeOptionsComponent={{component BeforeOptions}}
@afterOptionsComponent={{component
AfterOptions
chooseCard=(perform this.chooseCard)
}}
data-test-instance-chooser
as |card|
>
Expand Down Expand Up @@ -203,12 +281,23 @@ class PlaygroundPanelContent extends Component<PlaygroundContentSignature> {
:deep(.instances-dropdown-content > .ember-power-select-options) {
max-height: 20rem;
}
:deep(
.boxel-select__dropdown
.ember-power-select-option[aria-current='true']
),
:deep(.instances-dropdown-content .ember-power-select-option) {
background-color: var(--boxel-light);
}
:deep(.ember-power-select-option:hover .card) {
background-color: var(--boxel-100);
}
.card {
height: 75px;
width: 375px;
max-width: 100%;
container-name: fitted-card;
container-type: size;
background-color: var(--boxel-light);
}
.preview-area {
flex-grow: 1;
Expand Down Expand Up @@ -300,7 +389,7 @@ class PlaygroundPanelContent extends Component<PlaygroundContentSignature> {
type: this.args.codeRef,
},
{
any: this.recentCardIds.map((id) => ({ eq: { id } })).slice(0, 20),
any: this.recentCardIds.map((id) => ({ eq: { id } })).slice(0, 3),
},
],
},
Expand Down Expand Up @@ -371,6 +460,17 @@ class PlaygroundPanelContent extends Component<PlaygroundContentSignature> {
private setFormat(format: Format) {
this.format = format;
}

private chooseCard = restartableTask(async () => {
let chosenCard: CardDef | undefined = await chooseCard({
filter: { type: this.args.codeRef },
});

if (chosenCard) {
this.recentFilesService.addRecentFileUrl(`${chosenCard.id}.json`);
this.persistSelections(chosenCard.id);
}
});
}

interface Signature {
Expand Down
43 changes: 43 additions & 0 deletions packages/host/tests/acceptance/code-submode/playground-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,49 @@ export class BlogPost extends CardDef {
.exists();
});

test('can choose another instance to be opened in playground panel', async function (assert) {
window.localStorage.removeItem('recent-files');
await visitOperatorMode({
submode: 'code',
codePath: `${testRealmURL}blog-post.gts`,
});

await click('[data-boxel-selector-item-text="BlogPost"]');
await click('[data-test-accordion-item="playground"] button');
await click('[data-test-instance-chooser]');
await click('[data-test-choose-another-instance]');
assert.dom('[data-test-card-catalog-modal]').exists();
assert.dom('[data-test-card-catalog-item]').exists({ count: 3 });
assert
.dom(`[data-test-card-catalog-item="${testRealmURL}BlogPost/mad-hatter"]`)
.exists();
assert
.dom(
`[data-test-card-catalog-item="${testRealmURL}BlogPost/urban-living"]`,
)
.exists();
assert
.dom(
`[data-test-card-catalog-item="${testRealmURL}BlogPost/remote-work"]`,
)
.exists();

await click(
`[data-test-card-catalog-item="${testRealmURL}BlogPost/mad-hatter"]`,
);
await click('[data-test-card-catalog-go-button]');
assert
.dom(
`[data-test-playground-panel] [data-test-card="${testRealmURL}BlogPost/mad-hatter"][data-test-card-format="isolated"]`,
)
.exists();
let recentFiles = JSON.parse(window.localStorage.getItem('recent-files')!);
assert.deepEqual(recentFiles[0], [
testRealmURL,
'BlogPost/mad-hatter.json',
]);
});

test<TestContextWithSSE>('playground preview for card with contained fields can live update when module changes', async function (assert) {
// change: added "Hello" before rendering title on the template
const authorCard = `import { contains, field, CardDef, Component } from "https://cardstack.com/base/card-api";
Expand Down

0 comments on commit 7fc8d66

Please sign in to comment.