From 6b195e88c0458ede5f3ca4a7d26a627367d78ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alonso=20C=C3=A1rdenas?= Date: Wed, 3 Jun 2026 01:01:35 -0400 Subject: [PATCH] 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. --- blocksystem/src/App.vue | 421 ++- blocksystem/src/components/Blocks.vue | 812 ++--- .../exercise-types/ComposeDecompose.vue | 470 +++ .../exercise-types/Connectlines.vue | 658 ++++ .../exercise-types/Picturechoose.vue | 314 ++ .../exercise-types/Subtractvisual.vue | 354 ++ .../components/exercise-types/Wheremore.vue | 386 +++ package-lock.json | 2853 +++++++++++++++++ 8 files changed, 5804 insertions(+), 464 deletions(-) create mode 100644 blocksystem/src/components/exercise-types/ComposeDecompose.vue create mode 100644 blocksystem/src/components/exercise-types/Connectlines.vue create mode 100644 blocksystem/src/components/exercise-types/Picturechoose.vue create mode 100644 blocksystem/src/components/exercise-types/Subtractvisual.vue create mode 100644 blocksystem/src/components/exercise-types/Wheremore.vue create mode 100644 package-lock.json diff --git a/blocksystem/src/App.vue b/blocksystem/src/App.vue index dd3026a..b53a2b8 100644 --- a/blocksystem/src/App.vue +++ b/blocksystem/src/App.vue @@ -59,8 +59,10 @@
- {{ ex.grids.filter(g => g.mode !== 'target').map(g => g.label || g.value).join(' + ') }} - → {{ ex.grids.filter(g => g.mode === 'target').map(g => g.label).join(', ') || 'sin banca' }} + {{ex.grids.filter(g => g.mode !== 'target').map(g => g.label || + g.value).join(' + ')}} + → {{ex.grids.filter(g => g.mode === 'target').map(g => g.label).join(',') + || 'sin banca' }}
@@ -79,23 +81,29 @@ @click="form.exerciseType = 'input-check'">Escribir número + -
+
- - - + + - - +
@@ -138,8 +146,8 @@ v-model="g.label" />
-
-
+ +
+ + +
+ -
+
{ }; // ── 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; } - \ No newline at end of file diff --git a/blocksystem/src/components/Blocks.vue b/blocksystem/src/components/Blocks.vue index d32cd5f..7632f42 100644 --- a/blocksystem/src/components/Blocks.vue +++ b/blocksystem/src/components/Blocks.vue @@ -1,5 +1,19 @@