Add exercise type components
Add functioning exercise components for support. Includes test exercises to make sure they work. ComposeDecompose: Takes a value and shows exercise with 2 inputs and hints automaticly. Can change the ammount of options it has. Connectlines: Exercise to connect between eachother. Has two modes, sum and picture, sum uses a 1+1 style of options, and picture uses the grid layout for more complexity. Picture in the middle is currently default blocks, this has to be conversed of how it should be displayed. PictureChoose.vue: This one shows grid with the first and second value, the correct answer must have a boolean value stating this is the correct one. Substractvisual.vue: This one is the most loose, as it only uses the grids as visuals and answer purposes. Not more than that. Wheremore.vue: Has two modes, less or more (if non it defaults to more), Takes a left value and a right value to create the execise. Examples of the structure are detailed in each component. And app.vue has demo exercises for testing and visual purposes.
This commit is contained in:
@@ -59,8 +59,10 @@
|
||||
<div v-for="(ex, i) in draft" :key="i" class="draft-item"
|
||||
:class="{ 'draft-item--pinned': pinnedPreview === ex }"
|
||||
@click="pinnedPreview = pinnedPreview === ex ? null : ex">
|
||||
<span class="draft-nums">{{ ex.grids.filter(g => g.mode !== 'target').map(g => g.label || g.value).join(' + ') }}</span>
|
||||
<span class="draft-dropzones">→ {{ ex.grids.filter(g => g.mode === 'target').map(g => g.label).join(', ') || 'sin banca' }}</span>
|
||||
<span class="draft-nums">{{ex.grids.filter(g => g.mode !== 'target').map(g => g.label ||
|
||||
g.value).join(' + ')}}</span>
|
||||
<span class="draft-dropzones">→ {{ex.grids.filter(g => g.mode === 'target').map(g => g.label).join(',')
|
||||
|| 'sin banca' }}</span>
|
||||
<button class="btn-icon btn-delete"
|
||||
@click.stop="draft.splice(i, 1); if (pinnedPreview === ex) pinnedPreview = null">✕</button>
|
||||
</div>
|
||||
@@ -79,23 +81,29 @@
|
||||
@click="form.exerciseType = 'input-check'">Escribir número</button>
|
||||
<button class="mode-btn" :class="{ 'mode-btn--active': form.exerciseType === 'sides' }"
|
||||
@click="form.exerciseType = 'sides'">Lados ↔</button>
|
||||
<button class="mode-btn" :class="{ 'mode-btn--active': form.exerciseType === 'mark-options' }"
|
||||
@click="form.exerciseType = 'mark-options'">Marcar ✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layout -->
|
||||
<div class="field" v-if="form.exerciseType !== 'sides'">
|
||||
<div class="field" v-if="form.exerciseType !== 'sides' && form.exerciseType !== 'mark-options'">
|
||||
<label>Disposición</label>
|
||||
<div class="mode-toggle">
|
||||
<button v-if="form.exerciseType !== 'move-blocks'" class="mode-btn" :class="{ 'mode-btn--active': form.layout === 'row' }"
|
||||
@click="form.layout = 'row'">Fila →</button>
|
||||
<button v-if="form.exerciseType !== 'move-blocks'" class="mode-btn" :class="{ 'mode-btn--active': form.layout === 'column' }"
|
||||
@click="form.layout = 'column'">Columna ↓</button>
|
||||
<button v-if="form.exerciseType === 'move-blocks'" class="mode-btn" :class="{ 'mode-btn--active': form.layout === 'row-answer-right' }"
|
||||
<button v-if="form.exerciseType !== 'move-blocks'" class="mode-btn"
|
||||
:class="{ 'mode-btn--active': form.layout === 'row' }" @click="form.layout = 'row'">Fila →</button>
|
||||
<button v-if="form.exerciseType !== 'move-blocks'" class="mode-btn"
|
||||
:class="{ 'mode-btn--active': form.layout === 'column' }" @click="form.layout = 'column'">Columna
|
||||
↓</button>
|
||||
<button v-if="form.exerciseType === 'move-blocks'" class="mode-btn"
|
||||
:class="{ 'mode-btn--active': form.layout === 'row-answer-right' }"
|
||||
@click="form.layout = 'row-answer-right'">Resp. →</button>
|
||||
<button v-if="form.exerciseType === 'move-blocks'" class="mode-btn" :class="{ 'mode-btn--active': form.layout === 'row-answer-bottom' }"
|
||||
<button v-if="form.exerciseType === 'move-blocks'" class="mode-btn"
|
||||
:class="{ 'mode-btn--active': form.layout === 'row-answer-bottom' }"
|
||||
@click="form.layout = 'row-answer-bottom'">Resp. ↓</button>
|
||||
<button v-if="form.exerciseType === 'move-blocks'" class="mode-btn" :class="{ 'mode-btn--active': form.layout === 'column' }"
|
||||
@click="form.layout = 'column'">Columna ↓</button>
|
||||
<button v-if="form.exerciseType === 'move-blocks'" class="mode-btn"
|
||||
:class="{ 'mode-btn--active': form.layout === 'column' }" @click="form.layout = 'column'">Columna
|
||||
↓</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -138,8 +146,8 @@
|
||||
v-model="g.label" />
|
||||
</div>
|
||||
<!-- Fruit picker — dropdown select -->
|
||||
<div v-if="g.value > 0" class="fruit-select-wrap">
|
||||
<select class="fruit-select" v-model="g.fruit">
|
||||
<div v-if="g.value > 0" class="image-select-wrap">
|
||||
<select class="image-select" v-model="g.image">
|
||||
<option :value="null">🟥 Bloques de color</option>
|
||||
<option v-for="f in FRUITS" :key="f.key" :value="f.key">
|
||||
{{ f.emoji }} {{ f.label }}
|
||||
@@ -150,8 +158,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- mark-options: which grid is correct -->
|
||||
<div class="field" v-if="form.exerciseType === 'mark-options' && validGrids.length >= 2">
|
||||
<label>Respuesta correcta</label>
|
||||
<select class="image-select" v-model="form.correctGridIdx" style="max-width:220px">
|
||||
<option v-for="(g, i) in validGrids" :key="i" :value="i">
|
||||
{{ g.label || ('Grid ' + (form.grids.indexOf(g) + 1)) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- move-blocks / sides: banca config -->
|
||||
<div class="field" v-if="(form.exerciseType === 'move-blocks' || form.exerciseType === 'sides') && validGrids.length">
|
||||
<div class="field"
|
||||
v-if="(form.exerciseType === 'move-blocks' || form.exerciseType === 'sides') && validGrids.length">
|
||||
<label>Banca <span class="hint">— respuesta esperada por origen</span></label>
|
||||
<div class="grid-row" style="margin-bottom:0.5rem">
|
||||
<input class="grid-label-input" type="text" placeholder="Etiqueta respuesta" v-model="form.bancaLabel"
|
||||
@@ -214,21 +233,20 @@ import Blocks from './components/Blocks.vue';
|
||||
const screen = ref(null);
|
||||
|
||||
// ── FRUIT CATALOGUE ──
|
||||
// Keys must match what Blocks.vue / FRUIT_DICT accepts
|
||||
// Keys must match FRUIT_DICT in Blocks.vue exactly
|
||||
// Keep in sync with IMAGE_DICT in Blocks.vue manually when adding/removing images.
|
||||
const FRUITS = [
|
||||
{ key: 'manzana', emoji: '🍎', label: 'Manzana' },
|
||||
{ key: 'banana', emoji: '🍌', label: 'Banana' },
|
||||
{ key: 'naranja', emoji: '🍊', label: 'Naranja' },
|
||||
{ key: 'frutilla', emoji: '🍓', label: 'Frutilla' },
|
||||
{ key: 'uva', emoji: '🍇', label: 'Uva' },
|
||||
{ key: 'limon', emoji: '🍋', label: 'Limón' },
|
||||
{ key: 'zanahoria', emoji: '🥕', label: 'Zanahoria' },
|
||||
{ key: 'mango', emoji: '🥭', label: 'Mango' },
|
||||
{ key: 'tomate', emoji: '🍅', label: 'Tomate' },
|
||||
{ key: 'palta', emoji: '🥑', label: 'Palta' },
|
||||
{ key: 'kiwi', emoji: '🥝', label: 'Kiwi' },
|
||||
{ key: 'pina', emoji: '🍍', label: 'Piña' },
|
||||
{ key: 'manzana', emoji: '🍎', label: 'Manzana' },
|
||||
{ key: 'banana', emoji: '🍌', label: 'Banana' },
|
||||
{ key: 'frutilla', emoji: '🍓', label: 'Frutilla' },
|
||||
{ key: 'zanahoria', emoji: '🥕', label: 'Zanahoria' },
|
||||
{ key: 'limon', emoji: '🍋', label: 'Limón' },
|
||||
{ key: 'mango', emoji: '🥭', label: 'Mango' },
|
||||
{ key: 'uva', emoji: '🍇', label: 'Uva' },
|
||||
{ key: 'tomate', emoji: '🍅', label: 'Tomate' },
|
||||
{ key: 'palta', emoji: '🥑', label: 'Palta' },
|
||||
{ key: 'kiwi', emoji: '🥝', label: 'Kiwi' },
|
||||
{ key: 'naranja', emoji: '🍊', label: 'Naranja' },
|
||||
{ key: 'pina', emoji: '🍍', label: 'Piña' },
|
||||
];
|
||||
|
||||
// ── DEMO EXERCISES ──
|
||||
@@ -242,73 +260,224 @@ const FRUITS = [
|
||||
// answerSlot: { expects: number } (for input-check at exercise level)
|
||||
|
||||
const demoExercises = ref([
|
||||
// ── move-blocks: operandos activos, banca target a la derecha ──
|
||||
|
||||
// ════════════════════════════════════════════
|
||||
// NEW EXERCISE COMPONENTS
|
||||
// ════════════════════════════════════════════
|
||||
|
||||
// ── compose-decompose ──
|
||||
{
|
||||
exerciseType: 'move-blocks',
|
||||
layout: 'row-answer-right',
|
||||
sidesAnswerLabel: '',
|
||||
text: "María tiene 2 manzanas y Juan tiene 3. ¿Cuántas tienen en total?",
|
||||
grids: [
|
||||
{ id: 'maria', mode: 'active', value: 2, label: 'María', fruit: 'manzana' },
|
||||
{ id: 'juan', mode: 'active', value: 3, label: 'Juan', fruit: 'zanahoria' },
|
||||
{ id: 'resp', mode: 'target', label: 'Total', answerSlot: { from: 'any', count: 5 } },
|
||||
exerciseType: 'compose-decompose',
|
||||
text: '¿Qué números forman el 10?',
|
||||
whole: 10, numberOfOptions: 4, image: 'manzana',
|
||||
},
|
||||
{
|
||||
exerciseType: 'compose-decompose',
|
||||
text: '¿Qué números forman el 7?',
|
||||
whole: 7, numberOfOptions: 3, image: 'naranja',
|
||||
},
|
||||
|
||||
// ── picture-choose ──
|
||||
{
|
||||
exerciseType: 'picture-choose',
|
||||
text: 'Hay 5 manzanas y 4 naranjas. ¿Cuántas hay en total?',
|
||||
firstImage: 'manzana', secondImage: 'naranja',
|
||||
options: [
|
||||
{ first: 3, second: 4, correct: false },
|
||||
{ first: 5, second: 2, correct: false },
|
||||
{ first: 5, second: 4, correct: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
exerciseType: 'picture-choose',
|
||||
text: 'Hay 6 fresas y 3 limones. ¿Cuántas hay en total?',
|
||||
firstImage: 'frutilla', secondImage: 'limon',
|
||||
options: [
|
||||
{ first: 4, second: 3, correct: false },
|
||||
{ first: 6, second: 3, correct: true },
|
||||
{ first: 6, second: 5, correct: false },
|
||||
{ first: 5, second: 4, correct: false },
|
||||
],
|
||||
},
|
||||
|
||||
// ── move-blocks: por origen específico, banca abajo ──
|
||||
// ── where-more ──
|
||||
{
|
||||
exerciseType: 'where-more',
|
||||
text: '¿Dónde hay más? Marca con una X.',
|
||||
mode: 'more',
|
||||
comparisons: [
|
||||
{ left: { value: 6, image: 'manzana' }, right: { value: 5, image: 'banana' } },
|
||||
{ left: { value: 4, image: 'naranja' }, right: { value: 7, image: 'frutilla' } },
|
||||
{ left: { value: 8, image: 'uva' }, right: { value: 3, image: 'limon' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
exerciseType: 'where-more',
|
||||
text: '¿Dónde hay menos? Marca con una X.',
|
||||
mode: 'less',
|
||||
comparisons: [
|
||||
{ left: { value: 9, image: 'manzana' }, right: { value: 2, image: 'naranja' } },
|
||||
{ left: { value: 3, image: 'banana' }, right: { value: 8, image: 'kiwi' } },
|
||||
],
|
||||
},
|
||||
|
||||
// ── connect: labels ──
|
||||
{
|
||||
exerciseType: 'connect',
|
||||
text: 'Conecta las sumas que tienen el mismo resultado.',
|
||||
columns: 2,
|
||||
leftItems: [
|
||||
{ id: 'l1', label: '3 + 5' },
|
||||
{ id: 'l2', label: '2 + 4' },
|
||||
{ id: 'l3', label: '4 + 5' },
|
||||
],
|
||||
rightItems: [
|
||||
{ id: 'r1', label: '6 + 3' },
|
||||
{ id: 'r2', label: '4 + 4' },
|
||||
{ id: 'r3', label: '1 + 5' },
|
||||
],
|
||||
correctPairs: [
|
||||
{ left: 'l1', right: 'r2' },
|
||||
{ left: 'l2', right: 'r3' },
|
||||
{ left: 'l3', right: 'r1' },
|
||||
],
|
||||
},
|
||||
|
||||
// ── connect: grids 2 columns ──
|
||||
{
|
||||
exerciseType: 'connect',
|
||||
text: 'Conecta los que tienen la misma cantidad.',
|
||||
columns: 2,
|
||||
leftItems: [
|
||||
{ id: 'l1', grids: [{ value: 3, image: 'manzana' }, { value: 5, image: 'manzana' }] },
|
||||
{ id: 'l2', grids: [{ value: 2, image: 'naranja' }, { value: 4, image: 'naranja' }] },
|
||||
{ id: 'l3', grids: [{ value: 4, image: 'banana' }, { value: 5, image: 'banana' }] },
|
||||
],
|
||||
rightItems: [
|
||||
{ id: 'r1', grids: [{ value: 4, image: 'frutilla' }, { value: 4, image: 'frutilla' }] },
|
||||
{ id: 'r2', grids: [{ value: 6, image: 'zanahoria' }, { value: 3, image: 'zanahoria' }] },
|
||||
{ id: 'r3', grids: [{ value: 5, image: 'limon' }, { value: 1, image: 'limon' }] },
|
||||
],
|
||||
correctPairs: [
|
||||
{ left: 'l1', right: 'r1' },
|
||||
{ left: 'l2', right: 'r3' },
|
||||
{ left: 'l3', right: 'r2' },
|
||||
],
|
||||
},
|
||||
|
||||
// ── connect: 3 columns ──
|
||||
{
|
||||
exerciseType: 'connect',
|
||||
text: 'Conecta cada grupo con su número.',
|
||||
columns: 3,
|
||||
leftItems: [
|
||||
{ id: 'l1', grids: [{ value: 4, image: 'naranja' }] },
|
||||
{ id: 'l2', grids: [{ value: 2, image: 'manzana' }] },
|
||||
{ id: 'l3', grids: [{ value: 3, image: 'banana' }] },
|
||||
],
|
||||
centerItems: [
|
||||
{ id: 'c1', grids: [{ value: 2, color: '#6366f1' }] },
|
||||
{ id: 'c2', grids: [{ value: 3, color: '#6366f1' }] },
|
||||
{ id: 'c3', grids: [{ value: 4, color: '#6366f1' }] },
|
||||
],
|
||||
rightItems: [
|
||||
{ id: 'r1', grids: [{ value: 3, image: 'limon' }] },
|
||||
{ id: 'r2', grids: [{ value: 4, image: 'zanahoria' }] },
|
||||
{ id: 'r3', grids: [{ value: 2, image: 'frutilla' }] },
|
||||
],
|
||||
correctPairs: [
|
||||
{ left: 'l1', center: 'c3' },
|
||||
{ left: 'l2', center: 'c1' },
|
||||
{ left: 'l3', center: 'c2' },
|
||||
{ right: 'r1', center: 'c2' },
|
||||
{ right: 'r2', center: 'c3' },
|
||||
{ right: 'r3', center: 'c1' },
|
||||
],
|
||||
},
|
||||
|
||||
// ── subtract-visual ──
|
||||
{
|
||||
exerciseType: 'subtract-visual',
|
||||
text: 'Hay 5 manzanas. Se comieron 2. ¿Cuántas quedan?',
|
||||
initialQuantity: 5, removedQuantity: 2, image: 'manzana',
|
||||
options: [{ id: 'o1', value: 3 }, { id: 'o2', value: 4 }, { id: 'o3', value: 2 }, { id: 'o4', value: 1 }],
|
||||
correctAnswer: 3,
|
||||
},
|
||||
{
|
||||
exerciseType: 'subtract-visual',
|
||||
text: 'Hay 9 naranjas. Se sacaron 4. ¿Cuántas quedan?',
|
||||
initialQuantity: 9, removedQuantity: 4, image: 'naranja',
|
||||
options: [{ id: 'o1', value: 5 }, { id: 'o2', value: 6 }, { id: 'o3', value: 3 }, { id: 'o4', value: 4 }],
|
||||
correctAnswer: 5,
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════
|
||||
// LEGACY / BLOCKS-BASED EXERCISES
|
||||
// ════════════════════════════════════════════
|
||||
|
||||
// ── move-blocks: answer right ──
|
||||
{
|
||||
exerciseType: 'move-blocks',
|
||||
layout: 'row-answer-right',
|
||||
text: "María tiene 2 manzanas y Juan tiene 3. ¿Cuántas tienen en total?",
|
||||
grids: [
|
||||
{ id: 'maria', mode: 'active', value: 2, label: 'María', image: 'manzana' },
|
||||
{ id: 'juan', mode: 'active', value: 3, label: 'Juan', image: 'zanahoria' },
|
||||
{ id: 'resp', mode: 'target', label: 'Total', answerSlot: { from: 'any', count: 5 } },
|
||||
],
|
||||
},
|
||||
|
||||
// ── move-blocks: answer bottom, by origin ──
|
||||
{
|
||||
exerciseType: 'move-blocks',
|
||||
layout: 'row-answer-bottom',
|
||||
text: "Pedro tiene 3 manzanas y Alicia tiene 2. Pedro le da 2 a Alicia. ¿Cuántas tiene Alicia?",
|
||||
grids: [
|
||||
{ id: 'pedro', mode: 'active', value: 3, label: 'Pedro', fruit: 'manzana' },
|
||||
{ id: 'alicia', mode: 'active', value: 2, label: 'Alicia', fruit: 'frutilla' },
|
||||
{ id: 'resp', mode: 'target', label: 'Alicia al final',
|
||||
answerSlot: { from: [{ id: 'pedro', count: 2 }, { id: 'alicia', count: 2 }] } },
|
||||
{ id: 'pedro', mode: 'active', value: 3, label: 'Pedro', image: 'manzana' },
|
||||
{ id: 'alicia', mode: 'active', value: 2, label: 'Alicia', image: 'frutilla' },
|
||||
{
|
||||
id: 'resp', mode: 'target', label: 'Alicia al final',
|
||||
answerSlot: { from: [{ id: 'pedro', count: 2 }, { id: 'alicia', count: 2 }] }
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ── input-check: grids estáticos, casilla de número a la derecha ──
|
||||
{
|
||||
exerciseType: 'input-check',
|
||||
layout: 'row',
|
||||
answerPosition: 'right',
|
||||
hideBorders: false,
|
||||
text: "¿Cuántos hay en total?",
|
||||
answerSlot: { expects: 5 },
|
||||
grids: [
|
||||
{ id: 'pollos', mode: 'static', value: 2, label: 'Pollos', fruit: 'naranja' },
|
||||
{ id: 'hamburguesas', mode: 'static', value: 3, label: 'Hamburguesas' },
|
||||
],
|
||||
},
|
||||
|
||||
// ── input-check: casilla de número abajo ──
|
||||
{
|
||||
exerciseType: 'input-check',
|
||||
layout: 'row',
|
||||
answerPosition: 'bottom',
|
||||
text: "¿Cuántos perros hay en el patio?",
|
||||
answerSlot: { expects: 6 },
|
||||
grids: [
|
||||
{ id: 'g1', mode: 'static', value: 4, label: 'Grupo 1', fruit: 'banana' },
|
||||
{ id: 'g2', mode: 'static', value: 2, label: 'Grupo 2', fruit: 'banana' },
|
||||
],
|
||||
},
|
||||
|
||||
// ── sides: banca en el centro, fuentes alternando izquierda/derecha ──
|
||||
// ── sides ──
|
||||
{
|
||||
exerciseType: 'sides',
|
||||
text: "Junta los bloques de ambos lados en el centro.",
|
||||
grids: [
|
||||
{ id: 'izq1', mode: 'active', value: 3, label: 'Grupo A', fruit: 'manzana' },
|
||||
{ id: 'der1', mode: 'active', value: 2, label: 'Grupo B', fruit: 'banana' },
|
||||
{ id: 'izq2', mode: 'active', value: 2, label: 'Grupo C', fruit: 'naranja' },
|
||||
{ id: 'resp', mode: 'target', label: 'Total',
|
||||
answerSlot: { from: 'any', count: 6 } },
|
||||
{ id: 'izq1', mode: 'active', value: 3, label: 'Grupo A', image: 'manzana' },
|
||||
{ id: 'der1', mode: 'active', value: 2, label: 'Grupo B', image: 'banana' },
|
||||
{ id: 'izq2', mode: 'active', value: 1, label: 'Grupo C', image: 'naranja' },
|
||||
{ id: 'resp', mode: 'target', label: 'Total', answerSlot: { from: 'any', count: 6 } },
|
||||
],
|
||||
},
|
||||
|
||||
// ── input-check ──
|
||||
{
|
||||
exerciseType: 'input-check',
|
||||
layout: 'row', answerPosition: 'right',
|
||||
text: "¿Cuántos hay en total?",
|
||||
answerSlot: { expects: 5 },
|
||||
grids: [
|
||||
{ id: 'g1', mode: 'static', value: 2, label: 'Grupo 1', image: 'naranja' },
|
||||
{ id: 'g2', mode: 'static', value: 3, label: 'Grupo 2' },
|
||||
],
|
||||
},
|
||||
|
||||
// ── mark-options ──
|
||||
{
|
||||
exerciseType: 'mark-options',
|
||||
layout: 'row',
|
||||
text: "¿Dónde hay menos? Marca con una X.",
|
||||
answerSlot: { correctId: 'koalas' },
|
||||
grids: [
|
||||
{ id: 'pandas', mode: 'static', value: 6, label: 'Pandas' },
|
||||
{ id: 'koalas', mode: 'static', value: 5, label: 'Koalas' },
|
||||
],
|
||||
},
|
||||
|
||||
// ── mark-options: dos grids estáticos, el alumno marca cuál tiene menos ── { exerciseType: 'mark-options', layout: 'row', text: "¿Dónde hay menos? Marca con una X.", answerSlot: { correctId: 'koalas' }, grids: [ { id: 'pandas', mode: 'static', value: 6, label: 'Pandas' }, { id: 'koalas', mode: 'static', value: 5, label: 'Koalas' }, ], },
|
||||
]);
|
||||
|
||||
// ── USER EXERCISES ──
|
||||
@@ -325,14 +494,14 @@ const growUserArrays = (exercises) => {
|
||||
};
|
||||
|
||||
// ── COMPLETE ──
|
||||
const onComplete = (_group, _idx, _result) => {};
|
||||
const onComplete = (_group, _idx, _result) => { };
|
||||
|
||||
// ── RESET ──
|
||||
const resetAll = () => { demoKeys.value = demoKeys.value.map(k => k + 1000); };
|
||||
const resetAll = () => { demoKeys.value = demoKeys.value.map(k => k + 1000); };
|
||||
const resetAllUser = () => { userKeys.value = userKeys.value.map(k => k + 1000); };
|
||||
|
||||
// ── CREATOR ──
|
||||
const defaultGrid = () => ({ value: '', label: '', needs: '', fruit: null });
|
||||
const defaultGrid = () => ({ value: '', label: '', needs: '', image: null });
|
||||
const defaultForm = () => ({
|
||||
text: '',
|
||||
exerciseType: 'move-blocks',
|
||||
@@ -343,9 +512,9 @@ const defaultForm = () => ({
|
||||
expectedAnswer: '',
|
||||
});
|
||||
|
||||
const form = ref(defaultForm());
|
||||
const draft = ref([]);
|
||||
const lastAdded = ref(null);
|
||||
const form = ref(defaultForm());
|
||||
const draft = ref([]);
|
||||
const lastAdded = ref(null);
|
||||
const pinnedPreview = ref(null);
|
||||
|
||||
const validGrids = computed(() =>
|
||||
@@ -360,7 +529,8 @@ const canAdd = computed(() => {
|
||||
return form.value.expectedAnswer !== '' && !isNaN(Number(form.value.expectedAnswer));
|
||||
if (form.value.exerciseType === 'sides')
|
||||
return validGrids.value.some(g => parseInt(g.needs) > 0);
|
||||
return validGrids.value.length >= 2; // mark-options
|
||||
if (form.value.exerciseType === 'mark-options') return validGrids.value.length >= 2;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Grid inputs — cascade: fill → next slot appears
|
||||
@@ -400,7 +570,7 @@ const previewExercise = computed(() => {
|
||||
mode: form.value.exerciseType === 'move-blocks' ? 'active' : 'static',
|
||||
value: parseInt(g.value),
|
||||
label: g.label || String(g.value),
|
||||
...(g.fruit ? { fruit: g.fruit } : {}),
|
||||
...(g.image ? { image: g.image } : {}),
|
||||
}));
|
||||
|
||||
if (form.value.exerciseType === 'move-blocks') {
|
||||
@@ -465,9 +635,10 @@ const previewExercise = computed(() => {
|
||||
// mark-options
|
||||
return {
|
||||
exerciseType: 'mark-options',
|
||||
hideBorders: form.value.hideBorders,
|
||||
layout: 'row',
|
||||
text: form.value.text,
|
||||
answerSlot: { correctId: srcGrids[0]?.id },
|
||||
answerSlot: { correctId: srcGrids[form.value.correctGridIdx]?.id ?? srcGrids[0]?.id },
|
||||
grids: srcGrids,
|
||||
};
|
||||
});
|
||||
@@ -691,8 +862,14 @@ const loadDraft = () => {
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.needs-arrow { color: #9ca3af; }
|
||||
.needs-hint { color: #9ca3af; font-size: 0.75rem; }
|
||||
.needs-arrow {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.needs-hint {
|
||||
color: #9ca3af;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.grid-num-input {
|
||||
width: 58px;
|
||||
@@ -711,7 +888,9 @@ const loadDraft = () => {
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.grid-num-input--sm { width: 46px; }
|
||||
.grid-num-input--sm {
|
||||
width: 46px;
|
||||
}
|
||||
|
||||
.grid-label-input {
|
||||
flex: 1;
|
||||
@@ -796,8 +975,14 @@ button {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
button:hover { background: #f0f0f0; }
|
||||
button:disabled { opacity: 0.5; cursor: default; }
|
||||
button:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
padding: 0.2rem 0.45rem;
|
||||
@@ -806,7 +991,9 @@ button:disabled { opacity: 0.5; cursor: default; }
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.btn-add-draft { align-self: flex-start; }
|
||||
.btn-add-draft {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.btn-load {
|
||||
background: #6366f1;
|
||||
@@ -814,7 +1001,9 @@ button:disabled { opacity: 0.5; cursor: default; }
|
||||
border-color: #6366f1;
|
||||
}
|
||||
|
||||
.btn-load:hover { background: #4f46e5; }
|
||||
.btn-load:hover {
|
||||
background: #4f46e5;
|
||||
}
|
||||
|
||||
.mode-toggle {
|
||||
display: flex;
|
||||
@@ -836,9 +1025,18 @@ button:disabled { opacity: 0.5; cursor: default; }
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mode-btn:hover { background: #f0f0f0; }
|
||||
.mode-btn--active { background: #6366f1; color: white; }
|
||||
.mode-btn--active:hover { background: #4f46e5; }
|
||||
.mode-btn:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.mode-btn--active {
|
||||
background: #6366f1;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mode-btn--active:hover {
|
||||
background: #4f46e5;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
@@ -864,18 +1062,34 @@ button:disabled { opacity: 0.5; cursor: default; }
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.draft-item:last-child { border-bottom: none; }
|
||||
.draft-item:hover { background: #fafbff; }
|
||||
.draft-item--pinned { background: #eef2ff; }
|
||||
.draft-nums { font-weight: 600; color: #374151; }
|
||||
.draft-dropzones { color: #9ca3af; font-size: 0.78rem; }
|
||||
.draft-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.draft-item:hover {
|
||||
background: #fafbff;
|
||||
}
|
||||
|
||||
.draft-item--pinned {
|
||||
background: #eef2ff;
|
||||
}
|
||||
|
||||
.draft-nums {
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.draft-dropzones {
|
||||
color: #9ca3af;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
/* ── Fruit picker (select) ── */
|
||||
.fruit-select-wrap {
|
||||
.image-select-wrap {
|
||||
padding: 2px 0 4px 26px;
|
||||
}
|
||||
|
||||
.fruit-select {
|
||||
.image-select {
|
||||
padding: 0.3rem 0.5rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
@@ -888,9 +1102,8 @@ button:disabled { opacity: 0.5; cursor: default; }
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.fruit-select:focus {
|
||||
.image-select:focus {
|
||||
outline: 2px solid #6366f1;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user