From ba1bd529a8702f8cd305db9cc64f5975c1689c5b Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 13 Mar 2026 11:47:52 +0000 Subject: [PATCH] Auto commit: 2026-03-13T11:47:52.842Z --- core/__pycache__/urls.cpython-311.pyc | Bin 974 -> 1096 bytes core/__pycache__/views.cpython-311.pyc | Bin 4063 -> 5760 bytes core/templates/core/project_detail.html | 118 +++++++++++++++++++++++- core/urls.py | 4 +- core/views.py | 33 ++++++- update_html.py | 106 +++++++++++++++++++++ update_js.py | 114 +++++++++++++++++++++++ update_urls.py | 19 ++++ update_views.py | 37 ++++++++ 9 files changed, 425 insertions(+), 6 deletions(-) create mode 100644 update_html.py create mode 100644 update_js.py create mode 100644 update_urls.py create mode 100644 update_views.py diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 7c6ead78974103dec5bc35e5519e17307562db42..f47932c2db7028e8a2a584348aca64b11ce2b704 100644 GIT binary patch delta 390 zcmX@deu6_|IWI340}w>N*qphIiGkrUhyw#WP{wC5#)%qwGP#^loQw=9OerimT)Es) z+>8uN45^GMtf|Z?Y!g3-PHfI%ieb3w4w zEsn&@__EB@^5P<%$;C_o++si#Ap46&7$=`&k~Yb?z(6vZEY3W`Ei87GT^Y^Y){&P>lMEzmFG1oHeec_w!;ne*J@NX(2c%ST}T{P6u$GfJLAmGjJvw)&&F(YMW^Zpu}KZ4rHB%gwuV|Lk%Haq40acF_0Cvh z7^@FHY>Uv;74$*MzBCOq4=wFO3JtW7l)e;pWx-(}tHIPfs6il!Kfg_nNC^Ew3{RM<@{FfG>-?!3>GE`VBCI1_nU2gf zOf*mkbalFMrfH&yLI!6v>9uRkhqBCC zqNgL2@p-z9GJa1#lfl_sfz8))p?ah%`&asCr=zW3#WUki#xO~-4C4ZWs1H5 zmt;ETE}*yyT+YBai!~ZwC5-+V$V^Td={s;=H0r>q)nbmM zllI^+fp!830GG|cC3U&!Y*Gipn^}Fyj^w;dpp7I7!?#k#oRx-b z1`@OyTz|CYCmWkg>o`tauaa<9Pg+Tjvr6ewhDr7mI}#irv4Wwetx3XxM@au)1E)C; z=UGM1eMpIY;lDTd^}FA_SCj`!@?cpWT)kGI!`yYsQNruXdrE9qi50FDl~_sXE-T$R z_K6Z&zrEeD(_KUyk1w z)e547^G;(+ZfJef5rf2RbDCShe8Uk##B6t>aWLQ9=cP}DPlS)fbulMCQJM=&P<^w* zsc+nza3al{E4I@R-3e3#ZlI=u4&j^#+Bxwl;#BEx0$>W#i{}!O>VX?NaS{pX;={fN z^WR_o;c_uL2;-N&qB2%e#>&cAkv}_LfK(X50*VIlo aD+oSi5G}7o$wOUhsj$Z@hwHcz3jYfpt9Z`< delta 100 zcmZqBy)Un^oR^o20SF52Z_3Q#U|@I*;=lkCl=1lx!$ggPtbD-?n*5th81L~gicP*D kd~EVv5pgbSpc+OXE>_Regenerate + + +
+ + +
@@ -127,6 +136,26 @@ 'General': '#f5f5f5' // Grey }; + + const categoryEmojis = { + 'Strategy': '🎯', + 'Product': '📦', + 'Market': '🌍', + 'Operations': '⚙️', + 'Finance': '💰', + 'General': '💡' + }; + + function getNodeEmoji(category) { + let cat = category || 'General'; + for (let key in categoryEmojis) { + if (cat.toLowerCase().includes(key.toLowerCase())) { + return categoryEmojis[key]; + } + } + return '💡'; // default emoji + } + function getNodeColor(category) { let cat = category || 'General'; let color = '#e0e0e0'; // Default light grey @@ -148,7 +177,7 @@ {% for node in nodes %} { id: {{ node.pk }}, - label: "{{ node.title|escapejs }}", + label: getNodeEmoji("{{ node.category|escapejs }}") + " " + "{{ node.title|escapejs }}", title: "{{ node.title|escapejs }}
{{ node.summary|escapejs }}
Category: {{ node.category|escapejs }}", summary: "{{ node.summary|escapejs }}", category: "{{ node.category|escapejs }}", @@ -234,10 +263,22 @@ // Node clicked const nodeId = params.nodes[0]; const node = data.nodes.get(nodeId); + // remove emoji from label for editing + const cleanLabel = node.label.replace(/^.*? /, ''); detailsPanel.innerHTML = ` -
${node.label}
- ${node.category} -

Summary:
${node.summary}

+
+
${cleanLabel}
+ ${node.category} +

Summary:
${node.summary}

+ +
+
+ + + + + +
`; } else if (params.edges.length > 0) { // Edge clicked @@ -260,6 +301,75 @@ } }); + + function editNodeUI(nodeId) { + document.getElementById(`node-view-${nodeId}`).classList.add('d-none'); + document.getElementById(`node-edit-${nodeId}`).classList.remove('d-none'); + } + + function cancelEditNode(nodeId) { + document.getElementById(`node-edit-${nodeId}`).classList.add('d-none'); + document.getElementById(`node-view-${nodeId}`).classList.remove('d-none'); + } + + function saveNode(nodeId, event) { + const title = document.getElementById(`edit-node-title-${nodeId}`).value; + const category = document.getElementById(`edit-node-category-${nodeId}`).value; + const summary = document.getElementById(`edit-node-summary-${nodeId}`).value; + + const saveBtn = event.target; + saveBtn.disabled = true; + saveBtn.innerText = 'Saving...'; + + let csrfToken = ''; + const csrfInput = document.querySelector('[name=csrfmiddlewaretoken]'); + if (csrfInput) { + csrfToken = csrfInput.value; + } else { + // fallback, get from form if present elsewhere + console.warn("CSRF token not found via input name. Ensure it exists on the page."); + } + + const url = "{% url 'edit_node' project.pk 0 %}".replace('/0/', '/' + nodeId + '/'); + + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify({ title: title, category: category, summary: summary }) + }) + .then(response => response.json()) + .then(res => { + if (res.status === 'success') { + const node = res.node; + const emoji = getNodeEmoji(node.category); + const color = getNodeColor(node.category); + data.nodes.update({ + id: node.id, + label: emoji + " " + node.title, + title: `${node.title}
${node.summary}
Category: ${node.category}`, + summary: node.summary, + category: node.category, + color: color + }); + + // re-render details + network.emit('click', { nodes: [nodeId], edges: [] }); + } else { + alert('Error saving node: ' + res.message); + saveBtn.disabled = false; + saveBtn.innerText = 'Save'; + } + }) + .catch(err => { + alert('Error saving node'); + saveBtn.disabled = false; + saveBtn.innerText = 'Save'; + }); + } + // AI Chat Handler document.getElementById('ai-submit').addEventListener('click', function() { const inputField = document.getElementById('ai-input'); diff --git a/core/urls.py b/core/urls.py index 36fa0ba..3379124 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import home, project_list, project_detail, create_project, signup, regenerate_mindmap +from .views import home, project_list, project_detail, create_project, signup, regenerate_mindmap, edit_node from .ai_views import ai_chat urlpatterns = [ @@ -9,5 +9,7 @@ urlpatterns = [ path("projects//", project_detail, name="project_detail"), path("projects//regenerate/", regenerate_mindmap, name="regenerate_mindmap"), path("projects//ai/", ai_chat, name="ai_chat"), + path('projects//node//edit/', edit_node, name='edit_node'), + path("signup/", signup, name="signup"), ] \ No newline at end of file diff --git a/core/views.py b/core/views.py index 4a2e209..5ae6070 100644 --- a/core/views.py +++ b/core/views.py @@ -54,4 +54,35 @@ def regenerate_mindmap(request, pk): project.nodes.all().delete() project.connections.all().delete() generate_initial_mindmap(project) - return redirect('project_detail', pk=project.pk) \ No newline at end of file + return redirect('project_detail', pk=project.pk) +import json +from django.http import JsonResponse +from django.views.decorators.http import require_POST + +@login_required +@require_POST +def edit_node(request, pk, node_id): + project = get_object_or_404(Project, pk=pk, user=request.user) + try: + node = project.nodes.get(pk=node_id) + data = json.loads(request.body) + title = data.get('title', '').strip() + summary = data.get('summary', '').strip() + category = data.get('category', '').strip() + + if title: + node.title = title + if summary: + node.summary = summary + if category: + node.category = category + + node.save() + return JsonResponse({'status': 'success', 'node': { + 'id': node.pk, + 'title': node.title, + 'summary': node.summary, + 'category': node.category + }}) + except Exception as e: + return JsonResponse({'status': 'error', 'message': str(e)}, status=400) diff --git a/update_html.py b/update_html.py new file mode 100644 index 0000000..536c61a --- /dev/null +++ b/update_html.py @@ -0,0 +1,106 @@ +import os + +path = "core/templates/core/project_detail.html" +with open(path, "r") as f: + html = f.read() + +# 1. Add top bar buttons (Layout, Zoom, etc) +buttons_to_add = """ + +
+ + +
""" + +html = html.replace('', + buttons_to_add + '\n ') + +# 2. Add emoji logic +emoji_script = """ + const categoryEmojis = { + 'Strategy': '🎯', + 'Product': '📦', + 'Market': '🌍', + 'Operations': '⚙️', + 'Finance': '💰', + 'General': '💡' + }; + + function getNodeEmoji(category) { + let cat = category || 'General'; + for (let key in categoryEmojis) { + if (cat.toLowerCase().includes(key.toLowerCase())) { + return categoryEmojis[key]; + } + } + return '💡'; // default emoji + } +""" + +html = html.replace('function getNodeColor(category) {', emoji_script + '\n function getNodeColor(category) {') + +# update node array to include emoji +html = html.replace('label: "{{ node.title|escapejs }}",', 'label: getNodeEmoji("{{ node.category|escapejs }}") + " " + "{{ node.title|escapejs }}",') + +# 3. Add script functions for zoom and layout +scripts_to_add = """ + function zoomIn() { + if (network) { + const scale = network.getScale() * 1.5; + network.moveTo({ scale: scale, animation: { duration: 300, easingFunction: 'easeInOutQuad' } }); + } + } + + function zoomOut() { + if (network) { + const scale = network.getScale() / 1.5; + network.moveTo({ scale: scale, animation: { duration: 300, easingFunction: 'easeInOutQuad' } }); + } + } + + function changeLayout(layoutType) { + if (layoutType === 'hierarchical') { + network.setOptions({ + layout: { + hierarchical: { + enabled: true, + direction: 'UD', + sortMethod: 'directed', + nodeSpacing: 200, + levelSeparation: 150 + } + }, + physics: { + hierarchicalRepulsion: { + nodeDistance: 200 + } + } + }); + } else { + network.setOptions({ + layout: { hierarchical: { enabled: false } }, + physics: { + solver: 'forceAtlas2Based', + forceAtlas2Based: { + gravitationalConstant: -100, + centralGravity: 0.01, + springLength: 150, + springConstant: 0.08 + }, + maxVelocity: 50, + minVelocity: 0.1, + timestep: 0.5, + stabilization: { iterations: 150 } + } + }); + } + } +""" +hTml = html.replace('// Export Map function', scripts_to_add + '\n // Export Map function') + +with open(path, "w") as f: + f.write(html) +print("Updated HTML!") diff --git a/update_js.py b/update_js.py new file mode 100644 index 0000000..3da7414 --- /dev/null +++ b/update_js.py @@ -0,0 +1,114 @@ +import os + +with open("core/templates/core/project_detail.html", "r") as f: + html = f.read() + +# Replace the node clicked block +old_block = """ + const nodeId = params.nodes[0]; + const node = data.nodes.get(nodeId); + detailsPanel.innerHTML = ` +
${node.label}
+ ${node.category} +

Summary:
${node.summary}

+ `; +""" + +new_block = """ + const nodeId = params.nodes[0]; + const node = data.nodes.get(nodeId); + // remove emoji from label for editing + const cleanLabel = node.label.replace(/^.*? /, ''); + detailsPanel.innerHTML = ` +
+
${cleanLabel}
+ ${node.category} +

Summary:
${node.summary}

+ +
+
+ + + + + +
+ `; +""" + +html = html.replace(old_block, new_block) + +new_scripts = """ + function editNodeUI(nodeId) { + document.getElementById(`node-view-${nodeId}`).classList.add('d-none'); + document.getElementById(`node-edit-${nodeId}`).classList.remove('d-none'); + } + + function cancelEditNode(nodeId) { + document.getElementById(`node-edit-${nodeId}`).classList.add('d-none'); + document.getElementById(`node-view-${nodeId}`).classList.remove('d-none'); + } + + function saveNode(nodeId, event) { + const title = document.getElementById(`edit-node-title-${nodeId}`).value; + const category = document.getElementById(`edit-node-category-${nodeId}`).value; + const summary = document.getElementById(`edit-node-summary-${nodeId}`).value; + + const saveBtn = event.target; + saveBtn.disabled = true; + saveBtn.innerText = 'Saving...'; + + let csrfToken = ''; + const csrfInput = document.querySelector('[name=csrfmiddlewaretoken]'); + if (csrfInput) { + csrfToken = csrfInput.value; + } else { + // fallback, get from form if present elsewhere + console.warn("CSRF token not found via input name. Ensure it exists on the page."); + } + + const url = "{% url 'edit_node' project.pk 0 %}".replace('/0/', '/' + nodeId + '/'); + + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify({ title: title, category: category, summary: summary }) + }) + .then(response => response.json()) + .then(res => { + if (res.status === 'success') { + const node = res.node; + const emoji = getNodeEmoji(node.category); + const color = getNodeColor(node.category); + data.nodes.update({ + id: node.id, + label: emoji + " " + node.title, + title: `${node.title}
${node.summary}
Category: ${node.category}`, + summary: node.summary, + category: node.category, + color: color + }); + + // re-render details + network.emit('click', { nodes: [nodeId], edges: [] }); + } else { + alert('Error saving node: ' + res.message); + saveBtn.disabled = false; + saveBtn.innerText = 'Save'; + } + }) + .catch(err => { + alert('Error saving node'); + saveBtn.disabled = false; + saveBtn.innerText = 'Save'; + }); + } +""" + +html = html.replace(' // AI Chat Handler', new_scripts + '\n // AI Chat Handler') + +with open("core/templates/core/project_detail.html", "w") as f: + f.write(html) diff --git a/update_urls.py b/update_urls.py new file mode 100644 index 0000000..6059b08 --- /dev/null +++ b/update_urls.py @@ -0,0 +1,19 @@ +import os + +with open("core/urls.py", "r") as f: + content = f.read() + +content = content.replace( + 'from .views import home, project_list, project_detail, create_project, signup, regenerate_mindmap', + 'from .views import home, project_list, project_detail, create_project, signup, regenerate_mindmap, edit_node' +) + +new_path = " path('projects//node//edit/', edit_node, name='edit_node'),\n" +content = content.replace( + ' path("projects//ai/", ai_chat, name="ai_chat"),', + ' path("projects//ai/", ai_chat, name="ai_chat"),\n' + new_path +) + +with open("core/urls.py", "w") as f: + f.write(content) + diff --git a/update_views.py b/update_views.py new file mode 100644 index 0000000..9198000 --- /dev/null +++ b/update_views.py @@ -0,0 +1,37 @@ +import os + +with open("core/views.py", "a") as f: + f.write(""" +import json +from django.http import JsonResponse +from django.views.decorators.http import require_POST + +@login_required +@require_POST +def edit_node(request, pk, node_id): + project = get_object_or_404(Project, pk=pk, user=request.user) + try: + node = project.nodes.get(pk=node_id) + data = json.loads(request.body) + title = data.get('title', '').strip() + summary = data.get('summary', '').strip() + category = data.get('category', '').strip() + + if title: + node.title = title + if summary: + node.summary = summary + if category: + node.category = category + + node.save() + return JsonResponse({'status': 'success', 'node': { + 'id': node.pk, + 'title': node.title, + 'summary': node.summary, + 'category': node.category + }}) + except Exception as e: + return JsonResponse({'status': 'error', 'message': str(e)}, status=400) +""") +print("Updated views.py!")