Skip to content

Commit 0bb32de

Browse files
committed
add previews, deletes, edits, to bulk-input flow
1 parent 34dcc6d commit 0bb32de

File tree

3 files changed

+598
-20
lines changed

3 files changed

+598
-20
lines changed
Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
<template>
2+
<div class="card-preview-list">
3+
<!-- Delete Confirmation Dialog -->
4+
<v-dialog v-model="showDeleteConfirm" max-width="400px">
5+
<v-card>
6+
<v-card-title class="text-h5">Remove Card</v-card-title>
7+
<v-card-text>
8+
<p>Are you sure you want to remove this card?</p>
9+
<v-checkbox
10+
v-model="dontAskAgain"
11+
label="Don't ask me again"
12+
hide-details
13+
class="mt-2"
14+
></v-checkbox>
15+
</v-card-text>
16+
<v-card-actions>
17+
<v-spacer></v-spacer>
18+
<v-btn color="grey-darken-1" variant="text" @click="cancelDelete">Cancel</v-btn>
19+
<v-btn color="error" @click="confirmDelete">Remove</v-btn>
20+
</v-card-actions>
21+
</v-card>
22+
</v-dialog>
23+
24+
<v-card v-if="parsedCards.length > 0" class="mb-4">
25+
<v-card-title class="d-flex align-center justify-space-between">
26+
<span>Card Preview</span>
27+
<div class="d-flex align-center">
28+
<div class="text-subtitle-1 mr-2">{{ currentIndex + 1 }} of {{ parsedCards.length }}</div>
29+
<sk-mouse-trap />
30+
</div>
31+
</v-card-title>
32+
33+
<v-card-text>
34+
<div v-if="loading" class="d-flex justify-center align-center my-4">
35+
<v-progress-circular indeterminate color="primary"></v-progress-circular>
36+
</div>
37+
<div v-else>
38+
<div v-if="currentCard">
39+
<v-sheet class="card-content pa-4 mb-4" rounded border>
40+
<!-- Rendered card content -->
41+
<div v-if="viewComponents && viewComponents.length > 0">
42+
<card-browser :views="viewComponents" :data="[currentViewData]" :suppress-spinner="true" />
43+
</div>
44+
<!-- Fallback markdown display when no view components are available -->
45+
<div v-else>
46+
<div class="mb-2 font-weight-bold">Card content:</div>
47+
<div class="markdown-content">{{ currentCard.markdown }}</div>
48+
</div>
49+
</v-sheet>
50+
51+
<div class="card-metadata mt-2">
52+
<div class="d-flex flex-wrap gap-1">
53+
<v-chip
54+
v-for="tag in currentCard.tags"
55+
:key="tag"
56+
size="small"
57+
color="primary"
58+
variant="outlined"
59+
class="mr-1 mb-1"
60+
>
61+
{{ tag }}
62+
</v-chip>
63+
</div>
64+
<div v-if="currentCard.elo !== undefined" class="mt-2 text-caption">ELO: {{ currentCard.elo }}</div>
65+
</div>
66+
</div>
67+
<div v-else class="text-center pa-4">No card selected or available to preview.</div>
68+
</div>
69+
</v-card-text>
70+
71+
<v-card-actions class="px-4 pb-4">
72+
<v-btn variant="outlined" icon :disabled="currentIndex === 0 || loading" @click="prevCard">
73+
<v-icon>mdi-chevron-left</v-icon>
74+
</v-btn>
75+
76+
<v-spacer></v-spacer>
77+
78+
<v-btn
79+
variant="tonal"
80+
color="error"
81+
prepend-icon="mdi-delete"
82+
class="mx-2"
83+
@click="promptDelete"
84+
:disabled="!currentCard || loading"
85+
>
86+
Remove
87+
</v-btn>
88+
89+
<v-btn
90+
variant="tonal"
91+
color="primary"
92+
prepend-icon="mdi-pencil"
93+
class="mx-2"
94+
@click="editCurrentCard"
95+
:disabled="!currentCard || loading"
96+
>
97+
Edit
98+
</v-btn>
99+
100+
<v-spacer></v-spacer>
101+
102+
<v-btn variant="outlined" icon :disabled="currentIndex >= parsedCards.length - 1 || loading" @click="nextCard">
103+
<v-icon>mdi-chevron-right</v-icon>
104+
</v-btn>
105+
</v-card-actions>
106+
</v-card>
107+
</div>
108+
</template>
109+
110+
<script lang="ts">
111+
import { ViewComponent, SkldrMouseTrap, HotKey, SkMouseTrap } from '@vue-skuilder/common-ui';
112+
import { DataShape, ParsedCard, ViewData } from '@vue-skuilder/common';
113+
import { defineComponent, PropType } from 'vue';
114+
import CardBrowser from '../CardBrowser.vue';
115+
116+
export default defineComponent({
117+
name: 'CardPreviewList',
118+
119+
components: {
120+
CardBrowser,
121+
SkMouseTrap,
122+
},
123+
124+
props: {
125+
parsedCards: {
126+
type: Array as PropType<ParsedCard[]>,
127+
required: true,
128+
default: () => [],
129+
},
130+
dataShape: {
131+
type: Object as PropType<DataShape>,
132+
required: true,
133+
},
134+
viewComponents: {
135+
type: Array as PropType<ViewComponent[]>,
136+
required: false,
137+
default: () => [],
138+
},
139+
},
140+
141+
emits: ['update:parsedCards', 'edit-card', 'delete-card'],
142+
143+
data() {
144+
return {
145+
currentIndex: 0,
146+
loading: false,
147+
keyBindings: [] as HotKey[],
148+
showDeleteConfirm: false,
149+
dontAskAgain: false,
150+
skipDeleteConfirmation: false,
151+
shortcutsEnabled: true,
152+
};
153+
},
154+
155+
computed: {
156+
currentCard(): ParsedCard | null {
157+
if (this.parsedCards.length === 0 || this.currentIndex >= this.parsedCards.length) {
158+
return null;
159+
}
160+
return this.parsedCards[this.currentIndex];
161+
},
162+
163+
currentViewData(): ViewData {
164+
if (!this.currentCard) {
165+
return { Input: '' };
166+
}
167+
168+
// Convert ParsedCard to ViewData
169+
return {
170+
Input: this.currentCard.markdown,
171+
// Any additional fields the view might need
172+
};
173+
},
174+
},
175+
176+
methods: {
177+
nextCard() {
178+
if (this.currentIndex < this.parsedCards.length - 1) {
179+
this.currentIndex++;
180+
}
181+
},
182+
183+
prevCard() {
184+
if (this.currentIndex > 0) {
185+
this.currentIndex--;
186+
}
187+
},
188+
189+
showCard(index: number) {
190+
if (index >= 0 && index < this.parsedCards.length) {
191+
this.currentIndex = index;
192+
}
193+
},
194+
195+
setupKeyBindings() {
196+
// Define key bindings for navigation
197+
this.keyBindings = [
198+
{
199+
command: 'Previous Card',
200+
hotkey: 'left',
201+
callback: () => {
202+
this.prevCard();
203+
return false; // Prevent default
204+
},
205+
},
206+
{
207+
command: 'Next Card',
208+
hotkey: 'right',
209+
callback: () => {
210+
this.nextCard();
211+
return false; // Prevent default
212+
},
213+
},
214+
{
215+
command: 'Delete Card',
216+
hotkey: 'del',
217+
callback: () => {
218+
this.promptDelete();
219+
return false; // Prevent default
220+
},
221+
},
222+
{
223+
command: 'Edit Card',
224+
hotkey: 'e',
225+
callback: () => {
226+
this.editCurrentCard();
227+
return false; // Prevent default
228+
},
229+
},
230+
];
231+
232+
if (this.shortcutsEnabled) {
233+
// Register keyboard shortcuts
234+
SkldrMouseTrap.bind(this.keyBindings);
235+
}
236+
},
237+
238+
enableShortcuts() {
239+
if (!this.shortcutsEnabled) {
240+
this.shortcutsEnabled = true;
241+
SkldrMouseTrap.bind(this.keyBindings);
242+
console.log('[CardPreviewList] Keyboard shortcuts enabled');
243+
}
244+
},
245+
246+
disableShortcuts() {
247+
if (this.shortcutsEnabled) {
248+
this.shortcutsEnabled = false;
249+
SkldrMouseTrap.reset();
250+
console.log('[CardPreviewList] Keyboard shortcuts disabled');
251+
}
252+
},
253+
254+
toggleShortcuts(enable: boolean) {
255+
if (enable) {
256+
this.enableShortcuts();
257+
} else {
258+
this.disableShortcuts();
259+
}
260+
},
261+
262+
promptDelete() {
263+
if (!this.currentCard) return;
264+
265+
if (this.skipDeleteConfirmation) {
266+
this.deleteCurrentCard();
267+
} else {
268+
this.showDeleteConfirm = true;
269+
}
270+
},
271+
272+
cancelDelete() {
273+
this.showDeleteConfirm = false;
274+
},
275+
276+
confirmDelete() {
277+
if (this.dontAskAgain) {
278+
this.skipDeleteConfirmation = true;
279+
}
280+
281+
this.showDeleteConfirm = false;
282+
this.deleteCurrentCard();
283+
},
284+
285+
deleteCurrentCard() {
286+
if (!this.currentCard) return;
287+
288+
// Create a new array without the current card
289+
const updatedCards = [...this.parsedCards];
290+
updatedCards.splice(this.currentIndex, 1);
291+
292+
// Emit event with updated array
293+
this.$emit('update:parsedCards', updatedCards);
294+
295+
// Also emit a specific event for parent component to handle
296+
this.$emit('delete-card', this.currentIndex);
297+
298+
// Adjust current index if needed
299+
if (this.currentIndex >= updatedCards.length && updatedCards.length > 0) {
300+
this.currentIndex = updatedCards.length - 1;
301+
}
302+
},
303+
304+
editCurrentCard() {
305+
if (!this.currentCard) return;
306+
307+
// Emit event with current card and index
308+
this.$emit('edit-card', this.currentCard, this.currentIndex);
309+
},
310+
},
311+
mounted() {
312+
this.setupKeyBindings();
313+
},
314+
315+
beforeUnmount() {
316+
// Clean up key bindings when component is unmounted
317+
SkldrMouseTrap.reset();
318+
},
319+
});
320+
</script>
321+
322+
<style scoped>
323+
.card-preview-list {
324+
width: 100%;
325+
}
326+
327+
.card-content {
328+
min-height: 150px;
329+
background-color: #f8f9fa;
330+
}
331+
332+
.markdown-content {
333+
white-space: pre-wrap;
334+
word-break: break-word;
335+
line-height: 1.5;
336+
font-family: var(--v-font-family);
337+
}
338+
339+
.gap-1 {
340+
gap: 4px;
341+
}
342+
</style>

0 commit comments

Comments
 (0)