diff --git a/flowchart/package.json b/flowchart/package.json index e30f57b..8996253 100644 --- a/flowchart/package.json +++ b/flowchart/package.json @@ -16,6 +16,8 @@ "svelte": "^5.55.4", "svelte-check": "^4.4.6", "typescript": "~6.0.2", - "vite": "^8.0.10" + "vite": "^8.0.10", + "dagre": "^0.8.5", + "@types/dagre": "^0.7.52" } } diff --git a/flowchart/src/App.svelte b/flowchart/src/App.svelte index ebd035c..2975af8 100644 --- a/flowchart/src/App.svelte +++ b/flowchart/src/App.svelte @@ -5,6 +5,8 @@ import Properties from './lib/Properties.svelte'; import Canvas from './lib/Canvas.svelte'; import { fcState } from './lib/flowchartState.svelte'; + import { parseFlowchartText, exportToFlowchartText } from './lib/parser'; + import { applyAutoLayout } from './lib/layout'; import { onMount } from 'svelte'; onMount(() => { @@ -21,26 +23,42 @@ window.addEventListener('message', (event) => { if (event.data?.type === 'FLOWCHART_LOAD') { try { - const payload = JSON.parse(event.data.payload); + const payload = typeof event.data.payload === 'string' ? JSON.parse(event.data.payload) : event.data.payload; + function processData(data: any) { + if (typeof data === 'string' && !data.trim().startsWith('{')) { + const { shapes, arrows } = parseFlowchartText(data); + return applyAutoLayout(shapes, arrows); + } + return data; + } + // Legacy format (just data object) - if (!payload.initialData && !payload.draftData && payload.shapes) { - fcState.initialData = payload; - fcState.loadData(payload); + if (!payload.initialData && !payload.draftData && (payload.shapes || typeof payload === 'string')) { + const data = processData(payload); + fcState.initialData = data; + fcState.loadData(data); } else { // New format { initialData, draftData } if (payload.initialData) { - fcState.initialData = payload.initialData; + fcState.initialData = processData(payload.initialData); } if (payload.draftData) { - fcState.loadData(payload.draftData); + fcState.loadData(processData(payload.draftData)); } else if (payload.initialData) { - fcState.loadData(payload.initialData); + fcState.loadData(processData(payload.initialData)); } } } catch(e) { console.error("Invalid data format", e); } + } else if (event.data?.type === 'FLOWCHART_GET_TEXT') { + const text = exportToFlowchartText(fcState.shapes, fcState.arrows); + window.parent.postMessage({ + type: 'FLOWCHART_TEXT_RESPONSE', + requestId: event.data.requestId, + payload: text + }, '*'); } }); diff --git a/flowchart/src/lib/Properties.svelte b/flowchart/src/lib/Properties.svelte index d48f8b9..37fa185 100644 --- a/flowchart/src/lib/Properties.svelte +++ b/flowchart/src/lib/Properties.svelte @@ -55,7 +55,7 @@ Warna Isi
{#each predefinedColors as color} - + {/each}
@@ -87,7 +87,7 @@ Warna Garis
{#each predefinedColors as color} - diff --git a/flowchart/src/lib/Topbar.svelte b/flowchart/src/lib/Topbar.svelte index ee7163f..120489e 100644 --- a/flowchart/src/lib/Topbar.svelte +++ b/flowchart/src/lib/Topbar.svelte @@ -1,5 +1,7 @@
+ + {#if storageKey}
@@ -92,6 +141,47 @@ border: none; } + .floating-action-btn { + position: absolute; + top: 64px; + left: 12px; + z-index: 10; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: #10b981; + color: white; + border: none; + border-radius: 20px; + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + } + + .floating-action-btn:hover:not(:disabled) { + background: #059669; + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4); + } + + .floating-action-btn:active:not(:disabled) { + transform: translateY(0); + } + + .floating-action-btn:disabled { + background: #9ca3af; + cursor: not-allowed; + box-shadow: none; + } + + .btn-icon { + font-size: 1rem; + line-height: 1; + } + .storage-indicator-inline { position: absolute; bottom: 1rem; diff --git a/routes/lessons.py b/routes/lessons.py index 1dc5fc6..8499f28 100644 --- a/routes/lessons.py +++ b/routes/lessons.py @@ -73,6 +73,7 @@ def api_lesson(filename): velxio_circuit = parsed_data.get('velxio_circuit', '') expected_serial_output = parsed_data.get('expected_serial_output', '') expected_wiring = parsed_data.get('expected_wiring', '') + expected_flowchart = parsed_data.get('expected_flowchart', '') evaluation_config_raw = parsed_data.get('evaluation_config', '') evaluation_config = {} @@ -134,6 +135,7 @@ def api_lesson(filename): 'velxio_circuit': velxio_circuit, 'expected_serial_output': expected_serial_output, 'expected_wiring': expected_wiring, + 'expected_flowchart': expected_flowchart, 'evaluation_config': evaluation_config, 'solution_code': solution_code, 'solution_circuit': solution_circuit, diff --git a/services/lesson_service.py b/services/lesson_service.py index ee02ce1..0583e59 100644 --- a/services/lesson_service.py +++ b/services/lesson_service.py @@ -363,6 +363,9 @@ def render_markdown_content(file_path): expected_wiring, lesson_content = _extract_section( lesson_content, '---EXPECTED_WIRING---', '---END_EXPECTED_WIRING---') + expected_flowchart, lesson_content = _extract_section( + lesson_content, '---EXPECTED_FLOWCHART---', '---END_EXPECTED_FLOWCHART---') + evaluation_config, lesson_content = _extract_section( lesson_content, '---EVALUATION_CONFIG---', '---END_EVALUATION_CONFIG---') @@ -393,6 +396,7 @@ def render_markdown_content(file_path): 'expected_output': expected_output, 'expected_output_python': expected_output_python, 'expected_circuit_output': expected_circuit_output, + 'expected_flowchart': expected_flowchart, 'lesson_info': lesson_info_html, 'initial_code': initial_code, 'solution_code': solution_code, @@ -403,7 +407,7 @@ def render_markdown_content(file_path): 'initial_code_c': initial_code_c, 'initial_python': initial_python, 'initial_circuit': initial_circuit, - 'initial_flowchart': initial_flowchart, + 'initial_flowchart': initial_flowchart_str or initial_flowchart, 'initial_quiz': initial_quiz, 'initial_code_arduino': initial_code_arduino, 'velxio_circuit': velxio_circuit,