From f3a4b772813970ee3a6f78aa02cc34ed43edc873 Mon Sep 17 00:00:00 2001 From: Boxel Submission Bot Date: Wed, 25 Mar 2026 16:31:29 +0800 Subject: [PATCH] add Simple Card Deck changes [boxel-content-hash:06ee7e5ae549] --- .../81269fcd-9c53-4ffe-8c9f-9a8db8b489e3.json | 69 +++ .../de434978-9437-4817-bce9-5ceb55b3768a.json | 16 + .../3a34947f-5311-408f-a879-3977de17a0de.json | 40 ++ simple-card-deck.gts | 445 ++++++++++++++++++ 4 files changed, 570 insertions(+) create mode 100644 CardListing/81269fcd-9c53-4ffe-8c9f-9a8db8b489e3.json create mode 100644 SimpleCardDeck/de434978-9437-4817-bce9-5ceb55b3768a.json create mode 100644 Spec/3a34947f-5311-408f-a879-3977de17a0de.json create mode 100644 simple-card-deck.gts diff --git a/CardListing/81269fcd-9c53-4ffe-8c9f-9a8db8b489e3.json b/CardListing/81269fcd-9c53-4ffe-8c9f-9a8db8b489e3.json new file mode 100644 index 0000000..bb07485 --- /dev/null +++ b/CardListing/81269fcd-9c53-4ffe-8c9f-9a8db8b489e3.json @@ -0,0 +1,69 @@ +{ + "data": { + "meta": { + "adoptsFrom": { + "name": "CardListing", + "module": "https://app.boxel.ai/catalog/catalog-app/listing/listing" + } + }, + "type": "card", + "attributes": { + "name": "Simple Card Deck", + "images": [], + "summary": "This catalog listing defines the SimpleCardDeck component, which models a virtual deck of standard playing cards. Its primary purpose is to enable users to draw, view, and reset a shuffled deck through an interactive interface. The component displays the remaining cards in the deck, allows drawing individual cards, and maintains a history of drawn cards. Users can click on specific drawn cards to view detailed information in a modal dialog. The design facilitates card deck simulations, educational demonstrations, or card game interfaces within applications.", + "cardInfo": { + "name": null, + "notes": null, + "summary": null, + "cardThumbnailURL": null + } + }, + "relationships": { + "tags.0": { + "links": { + "self": "https://app.boxel.ai/catalog/Tag/140feda8-625b-4a24-9ddb-6f4da891aef2" + } + }, + "tags.1": { + "links": { + "self": "https://app.boxel.ai/catalog/Tag/4d0f9ae2-048e-4ce0-b263-7006602ce6a4" + } + }, + "skills": { + "links": { + "self": null + } + }, + "license": { + "links": { + "self": "https://app.boxel.ai/catalog/License/4c5a023b-a72c-4f90-930b-da60a1de5b2d" + } + }, + "specs.0": { + "links": { + "self": "../Spec/3a34947f-5311-408f-a879-3977de17a0de" + } + }, + "publisher": { + "links": { + "self": null + } + }, + "categories": { + "links": { + "self": null + } + }, + "examples.0": { + "links": { + "self": "../SimpleCardDeck/de434978-9437-4817-bce9-5ceb55b3768a" + } + }, + "cardInfo.theme": { + "links": { + "self": null + } + } + } + } +} \ No newline at end of file diff --git a/SimpleCardDeck/de434978-9437-4817-bce9-5ceb55b3768a.json b/SimpleCardDeck/de434978-9437-4817-bce9-5ceb55b3768a.json new file mode 100644 index 0000000..45bccbc --- /dev/null +++ b/SimpleCardDeck/de434978-9437-4817-bce9-5ceb55b3768a.json @@ -0,0 +1,16 @@ +{ + "data": { + "type": "card", + "attributes": { + "cardTitle": null, + "cardDescription": null, + "cardThumbnailURL": null + }, + "meta": { + "adoptsFrom": { + "module": "../simple-card-deck", + "name": "SimpleCardDeck" + } + } + } +} diff --git a/Spec/3a34947f-5311-408f-a879-3977de17a0de.json b/Spec/3a34947f-5311-408f-a879-3977de17a0de.json new file mode 100644 index 0000000..b7a58fe --- /dev/null +++ b/Spec/3a34947f-5311-408f-a879-3977de17a0de.json @@ -0,0 +1,40 @@ +{ + "data": { + "type": "card", + "attributes": { + "readMe": null, + "ref": { + "module": "../simple-card-deck", + "name": "SimpleCardDeck" + }, + "specType": "card", + "containedExamples": [], + "cardTitle": "Simple Card Deck", + "cardDescription": null, + "cardInfo": { + "name": null, + "summary": null, + "cardThumbnailURL": null, + "notes": null + } + }, + "relationships": { + "linkedExamples": { + "links": { + "self": null + } + }, + "cardInfo.theme": { + "links": { + "self": null + } + } + }, + "meta": { + "adoptsFrom": { + "module": "https://cardstack.com/base/spec", + "name": "Spec" + } + } + } +} \ No newline at end of file diff --git a/simple-card-deck.gts b/simple-card-deck.gts new file mode 100644 index 0000000..fb3151f --- /dev/null +++ b/simple-card-deck.gts @@ -0,0 +1,445 @@ +import { + CardDef, + Component, + field, + contains, + StringField, +} from 'https://cardstack.com/base/card-api'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { on } from '@ember/modifier'; + +// Define card suits and values +const SUITS = ['♥', '♦', '♣', '♠']; +const VALUES = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']; + +// Card class for playing cards +class PlayingCard { + suit: string; + value: string; + + constructor(suit: string, value: string) { + this.suit = suit; + this.value = value; + } + + get color(): string { + return this.suit === '♥' || this.suit === '♦' ? 'red' : 'black'; + } + + get display(): string { + return `${this.value}${this.suit}`; + } +} + +export class SimpleCardDeck extends CardDef { + static displayName = 'Simple Card Deck'; + + @field cardTitle = contains(StringField); + + static isolated = class Isolated extends Component { + @tracked deck: PlayingCard[] = []; + @tracked drawnCards: PlayingCard[] = []; + @tracked isLoading = true; + @tracked selectedCard: PlayingCard | null = null; + @tracked showCardDialog = false; + @tracked drawButtonDisabled = false; + @tracked resetButtonDisabled = true; + @tracked currentCardIndex = -1; // For tracking selected card index + + constructor(owner: unknown, args: any) { + super(owner, args); + this.initializeDeck(); + } + + initializeDeck() { + this.isLoading = true; + + // Generate a standard deck of cards + const newDeck: PlayingCard[] = []; + for (const suit of SUITS) { + for (const value of VALUES) { + newDeck.push(new PlayingCard(suit, value)); + } + } + + // Shuffle the deck + this.deck = this.shuffleDeck([...newDeck]); + this.drawnCards = []; + + this.drawButtonDisabled = false; + this.resetButtonDisabled = true; + this.isLoading = false; + } + + shuffleDeck(cards: PlayingCard[]): PlayingCard[] { + for (let i = cards.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [cards[i], cards[j]] = [cards[j], cards[i]]; + } + return cards; + } + + // Draw a card from the deck + @action drawCard() { + if (this.deck.length === 0) { + return; + } + + // Draw a card from the top of the deck + const drawnCard = this.deck.pop()!; + this.drawnCards = [drawnCard, ...this.drawnCards]; + + // Update button states + if (this.deck.length === 0) { + this.drawButtonDisabled = true; + } + + this.resetButtonDisabled = false; + } + + // Reset the deck + @action resetDeck() { + this.initializeDeck(); + } + + // View a card in detail - now with index parameter + @action handleCardClick(index: number) { + this.selectedCard = this.drawnCards[index]; + this.showCardDialog = true; + } + + // Close the card dialog + @action closeDialog() { + this.showCardDialog = false; + } + + // Stop propagation + @action stopPropagation(e: Event) { + e.stopPropagation(); + } + + + } +} \ No newline at end of file