Skip to content
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

Add ability to choose another card to instance chooser #2144

Merged
merged 4 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -82,6 +86,74 @@ 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))
0 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-xs);
margin-top: var(--boxel-sp-xxs);
gap: var(--boxel-sp-xxs);
}
.title {
font: 600 var(--boxel-font-sm);
padding: 0 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 @@ -116,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 @@ -206,12 +283,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 @@ -374,6 +462,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 @@ -517,6 +517,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