Skip to content

Commit 48bc199

Browse files
authored
Merge pull request #1992 from cardstack/cs-7710-cannot-create-new-card-when-2-stack-items-are-opened
Use CardResource to create card instances on the host
2 parents 6717eb2 + 780b6fb commit 48bc199

File tree

11 files changed

+205
-128
lines changed

11 files changed

+205
-128
lines changed

packages/host/app/commands/show-card.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default class ShowCardCommand extends HostBaseCommand<
3232
1,
3333
);
3434
let newStackItem = await this.operatorModeStateService.createStackItem(
35-
input.cardToShow,
35+
new URL(input.cardToShow.id),
3636
newStackIndex,
3737
);
3838
this.operatorModeStateService.addItemToStack(newStackItem);

packages/host/app/components/operator-mode/card-preview-panel/index.gts

+3-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ export default class CardPreviewPanel extends Component<Signature> {
9393
}
9494

9595
openInInteractMode = task(async () => {
96-
await this.operatorModeStateService.openCardInInteractMode(this.args.card);
96+
await this.operatorModeStateService.openCardInInteractMode(
97+
new URL(this.args.card.id),
98+
);
9799
});
98100

99101
<template>

packages/host/app/components/operator-mode/copy-button.gts

+7-7
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ const LEFT = 0;
3232
const RIGHT = 1;
3333

3434
export default class CopyButton extends Component<Signature> {
35-
@service declare loaderService: LoaderService;
36-
@service declare cardService: CardService;
37-
@service declare operatorModeStateService: OperatorModeStateService;
38-
3935
<template>
4036
{{#if (gt this.stacks.length 1)}}
4137
{{#if this.state}}
@@ -102,15 +98,19 @@ export default class CopyButton extends Component<Signature> {
10298
</style>
10399
</template>
104100

105-
get stacks() {
101+
@service private declare loaderService: LoaderService;
102+
@service private declare cardService: CardService;
103+
@service private declare operatorModeStateService: OperatorModeStateService;
104+
105+
private get stacks() {
106106
return this.operatorModeStateService.state?.stacks ?? [];
107107
}
108108

109-
get buttonKind() {
109+
private get buttonKind() {
110110
return this.args.isCopying ? 'primary-dark' : 'primary';
111111
}
112112

113-
get state() {
113+
private get state() {
114114
// Need to have 2 stacks in order for a copy button to exist
115115
if (this.stacks.length < 2) {
116116
return undefined;

packages/host/app/components/operator-mode/interact-submode.gts

+27-26
Original file line numberDiff line numberDiff line change
@@ -198,30 +198,28 @@ export default class InteractSubmode extends Component<Signature> {
198198
doc.data.meta.realmURL = opts.realmURL.href;
199199
}
200200

201-
let newCard = await here.cardService.createFromSerialized(
202-
doc.data,
203-
doc,
204-
relativeTo,
205-
);
206-
207201
let newItem = new StackItem({
208202
owner: here,
209-
card: newCard,
203+
newCard: { doc, relativeTo },
210204
format: opts?.cardModeAfterCreation ?? 'edit',
211205
request: new Deferred(),
212206
isLinkedCard: opts?.isLinkedCard,
213207
stackIndex,
214208
});
215209

216-
// TODO: it is important saveModel happens after newItem because it
217-
// looks like perhaps there is a race condition (or something else) when a
218-
// new linked card is created, and when it is added to the stack and closed
219-
// - the parent card is not updated with the new linked card
220-
await here.cardService.saveModel(newCard);
221-
222210
await newItem.ready();
211+
if (newItem.cardError) {
212+
console.error(
213+
`Encountered error creating card:\n${JSON.stringify(
214+
doc,
215+
null,
216+
2,
217+
)}\nError: ${JSON.stringify(newItem.cardError, null, 2)}`,
218+
);
219+
return undefined;
220+
}
223221
here.addToStack(newItem);
224-
return newCard;
222+
return newItem.card;
225223
},
226224
viewCard: async (
227225
cardOrURL: CardDef | URL,
@@ -233,9 +231,7 @@ export default class InteractSubmode extends Component<Signature> {
233231
}
234232
let newItem = new StackItem({
235233
owner: here,
236-
...(cardOrURL instanceof URL
237-
? { url: cardOrURL }
238-
: { card: cardOrURL }),
234+
url: cardOrURL instanceof URL ? cardOrURL : new URL(cardOrURL.id),
239235
format,
240236
stackIndex,
241237
});
@@ -561,19 +557,25 @@ export default class InteractSubmode extends Component<Signature> {
561557
private openSelectedSearchResultInStack = restartableTask(
562558
async (cardId: string) => {
563559
let waiterToken = waiter.beginAsync();
560+
let url = new URL(cardId);
564561
try {
565562
let searchSheetTrigger = this.searchSheetTrigger; // Will be set by showSearchWithTrigger
566-
let card = await this.cardService.getCard(cardId);
567-
if (!card) {
568-
return;
569-
}
570563

571564
// In case the left button was clicked, whatever is currently in stack with index 0 will be moved to stack with index 1,
572565
// and the card will be added to stack with index 0. shiftStack executes this logic.
573566
if (
574567
searchSheetTrigger ===
575568
SearchSheetTriggers.DropCardToLeftNeighborStackButton
576569
) {
570+
let newItem = new StackItem({
571+
owner: this,
572+
url,
573+
format: 'isolated',
574+
stackIndex: 0,
575+
});
576+
// it's important that we await the stack item readiness _before_
577+
// we mutate the stack, otherwise there are very odd visual artifacts
578+
await newItem.ready();
577579
for (
578580
let stackIndex = this.stacks.length - 1;
579581
stackIndex >= 0;
@@ -584,15 +586,14 @@ export default class InteractSubmode extends Component<Signature> {
584586
stackIndex + 1,
585587
);
586588
}
587-
await this.publicAPI(this, 0).viewCard(card, 'isolated');
588-
589+
this.addToStack(newItem);
589590
// In case the right button was clicked, the card will be added to stack with index 1.
590591
} else if (
591592
searchSheetTrigger ===
592593
SearchSheetTriggers.DropCardToRightNeighborStackButton
593594
) {
594595
await this.publicAPI(this, this.stacks.length).viewCard(
595-
card,
596+
url,
596597
'isolated',
597598
);
598599
} else {
@@ -606,15 +607,15 @@ export default class InteractSubmode extends Component<Signature> {
606607
numberOfStacks === 0 ||
607608
this.operatorModeStateService.stackIsEmpty(stackIndex)
608609
) {
609-
await this.publicAPI(this, 0).viewCard(card, 'isolated');
610+
await this.publicAPI(this, 0).viewCard(url, 'isolated');
610611
} else {
611612
stack = this.operatorModeStateService.rightMostStack();
612613
if (stack) {
613614
let bottomMostItem = stack[0];
614615
if (bottomMostItem) {
615616
let stackItem = new StackItem({
616617
owner: this,
617-
card,
618+
url,
618619
format: 'isolated',
619620
stackIndex,
620621
});

packages/host/app/components/operator-mode/overlays.gts

+1-16
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import type CardService from '@cardstack/host/services/card-service';
3636
import RealmService from '@cardstack/host/services/realm';
3737

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

4140
import { removeFileExtension } from '../search-sheet/utils';
4241

@@ -548,23 +547,9 @@ export default class OperatorModeOverlays extends Component<Signature> {
548547
) => {
549548
let cardId =
550549
typeof cardDefOrId === 'string' ? cardDefOrId : cardDefOrId.id;
551-
552550
let canWrite = this.realm.canWrite(cardId);
553-
554551
format = canWrite ? format : 'isolated';
555-
556-
let card: CardDef | undefined;
557-
try {
558-
if (typeof cardDefOrId === 'string') {
559-
card = await this.cardService.getCard(cardId);
560-
} else {
561-
card = cardDefOrId;
562-
}
563-
} catch (e) {
564-
console.error(`can't load card: ${cardId}`);
565-
}
566-
567-
await this.args.publicAPI.viewCard(card ?? new URL(cardId), format, {
552+
await this.args.publicAPI.viewCard(new URL(cardId), format, {
568553
fieldType,
569554
fieldName,
570555
});

packages/host/app/lib/stack-item.ts

+19-39
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1-
import { type Deferred, apiFor } from '@cardstack/runtime-common';
1+
import {
2+
type Deferred,
3+
type LooseSingleCardDocument,
4+
} from '@cardstack/runtime-common';
25

36
import {
47
type CardResource,
58
getCard,
69
} from '@cardstack/host/resources/card-resource';
710

811
import type { CardDef, Format } from 'https://cardstack.com/base/card-api';
9-
import type * as CardAPI from 'https://cardstack.com/base/card-api';
1012

1113
interface Args {
1214
format: Format;
1315
owner: object;
1416
request?: Deferred<CardDef | undefined>;
1517
stackIndex: number;
16-
card?: CardDef;
18+
newCard?: { doc: LooseSingleCardDocument; relativeTo: URL | undefined };
1719
url?: URL;
1820
cardResource?: CardResource;
1921
isLinkedCard?: boolean; // TODO: consider renaming this so its clearer that we use this for being able to tell whether the card needs to be closed after saving
@@ -25,39 +27,32 @@ export class StackItem {
2527
stackIndex: number;
2628
isLinkedCard?: boolean; // TODO: consider renaming this so its clearer that we use this for being able to tell whether the card needs to be closed after saving
2729
private owner: object;
28-
private newCard?: CardDef;
2930
private cardResource?: CardResource;
30-
private newCardApiPromise: Promise<typeof CardAPI> | undefined;
31-
private newCardApi: typeof CardAPI | undefined;
3231

3332
constructor(args: Args) {
3433
let {
3534
format,
3635
request,
3736
stackIndex,
38-
card,
37+
newCard,
3938
url,
4039
cardResource,
4140
isLinkedCard,
4241
owner,
4342
} = args;
44-
if (!card && !cardResource && !url) {
43+
if (!newCard && !cardResource && !url) {
4544
throw new Error(
46-
`Cannot create a StackItem without a 'card' or a 'cardResource' or a 'url'`,
45+
`Cannot create a StackItem without a 'newCard' or a 'cardResource' or a 'url'`,
4746
);
4847
}
4948
if (cardResource) {
5049
this.cardResource = cardResource;
51-
} else if (card?.id) {
52-
// if the card is not actually new--load a resource instead
53-
this.cardResource = getCard(owner, () => card!.id);
5450
} else if (url) {
5551
this.cardResource = getCard(owner, () => url!.href);
56-
} else if (card) {
57-
this.newCard = card;
58-
this.newCardApiPromise = apiFor(this.card).then(
59-
(api) => (this.newCardApi = api),
60-
);
52+
} else if (newCard) {
53+
this.cardResource = getCard(owner, () => newCard!.doc, {
54+
relativeTo: newCard.relativeTo,
55+
});
6156
}
6257

6358
this.format = format;
@@ -68,16 +63,11 @@ export class StackItem {
6863
}
6964

7065
get url() {
71-
return (
72-
(this.cardResource?.url ? new URL(this.cardResource.url) : undefined) ??
73-
(this.newCard?.id ? new URL(this.newCard.id) : undefined)
74-
);
66+
return this.cardResource?.url ? new URL(this.cardResource.url) : undefined;
7567
}
7668

7769
get card(): CardDef {
78-
if (this.newCard) {
79-
return this.newCard;
80-
} else if (this.cardResource) {
70+
if (this.cardResource) {
8171
if (!this.cardResource.card) {
8272
throw new Error(`The CardResource for this StackItem has no card set`);
8373
}
@@ -88,9 +78,7 @@ export class StackItem {
8878
}
8979

9080
get title() {
91-
if (this.newCard) {
92-
return this.newCard.title;
93-
} else if (this.cardResource?.card) {
81+
if (this.cardResource?.card) {
9482
return this.cardResource.card.title;
9583
}
9684
return undefined;
@@ -127,7 +115,7 @@ export class StackItem {
127115
}
128116

129117
get api() {
130-
let api = this.cardResource?.api ?? this.newCardApi;
118+
let api = this.cardResource?.api;
131119
if (!api) {
132120
throw new Error(
133121
`API for stack item is not available yet--use this.ready() to wait for API availability`,
@@ -137,22 +125,14 @@ export class StackItem {
137125
}
138126

139127
async ready() {
140-
await Promise.all([this.cardResource?.loaded, this.newCardApiPromise]);
128+
await this.cardResource?.loaded;
141129
}
142130

143131
clone(args: Partial<Args>) {
144-
let {
145-
card,
146-
format,
147-
request,
148-
isLinkedCard,
149-
owner,
150-
cardResource,
151-
stackIndex,
152-
} = this;
132+
let { format, request, isLinkedCard, owner, cardResource, stackIndex } =
133+
this;
153134
return new StackItem({
154135
cardResource,
155-
card,
156136
format,
157137
request,
158138
isLinkedCard,

0 commit comments

Comments
 (0)