Merge branch 'main' of https://github.com/frappe/community into main
This commit is contained in:
0
community/lms/doctype/lms_sketch/__init__.py
Normal file
0
community/lms/doctype/lms_sketch/__init__.py
Normal file
44
community/lms/doctype/lms_sketch/livecode.py
Normal file
44
community/lms/doctype/lms_sketch/livecode.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"""Utilities to work with livecode service.
|
||||||
|
"""
|
||||||
|
import websocket
|
||||||
|
import json
|
||||||
|
import drawSvg as draw
|
||||||
|
|
||||||
|
def livecode_to_svg(livecode_ws_url, code, *, timeout=1):
|
||||||
|
"""Renders the code as svg.
|
||||||
|
"""
|
||||||
|
print("livecode_to_svg")
|
||||||
|
ws = websocket.WebSocket()
|
||||||
|
ws.settimeout(timeout)
|
||||||
|
ws.connect(livecode_ws_url)
|
||||||
|
|
||||||
|
msg = {
|
||||||
|
"msgtype": "exec",
|
||||||
|
"runtime": "python-canvas",
|
||||||
|
"code": code
|
||||||
|
}
|
||||||
|
ws.send(json.dumps(msg))
|
||||||
|
|
||||||
|
messages = _read_messages(ws)
|
||||||
|
commands = [m['cmd'] for m in messages if m['msgtype'] == 'draw']
|
||||||
|
img = draw_image(commands)
|
||||||
|
return img.asSvg()
|
||||||
|
|
||||||
|
def _read_messages(ws):
|
||||||
|
messages = []
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
msg = ws.recv()
|
||||||
|
if not msg:
|
||||||
|
break
|
||||||
|
messages.append(json.loads(msg))
|
||||||
|
except websocket.WebSocketTimeoutException:
|
||||||
|
pass
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def draw_image(commands):
|
||||||
|
img = draw.Drawing(300, 300, fill='none', stroke='black')
|
||||||
|
for c in commands:
|
||||||
|
if c['function'] == 'circle':
|
||||||
|
img.append(draw.Circle(cx=c['x'], cy=c['y'], r=c['d']/2))
|
||||||
|
return img
|
||||||
8
community/lms/doctype/lms_sketch/lms_sketch.js
Normal file
8
community/lms/doctype/lms_sketch/lms_sketch.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2021, FOSS United and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('LMS Sketch', {
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
62
community/lms/doctype/lms_sketch/lms_sketch.json
Normal file
62
community/lms/doctype/lms_sketch/lms_sketch.json
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "format:SKETCH-{#}",
|
||||||
|
"creation": "2021-03-09 16:31:50.523524",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"title",
|
||||||
|
"runtime",
|
||||||
|
"code",
|
||||||
|
"svg"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "runtime",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Runtime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "code",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "svg",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "SVG",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-03-12 08:42:56.671658",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Sketch",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1,
|
||||||
|
"track_views": 1
|
||||||
|
}
|
||||||
103
community/lms/doctype/lms_sketch/lms_sketch.py
Normal file
103
community/lms/doctype/lms_sketch/lms_sketch.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, FOSS United and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import hashlib
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from . import livecode
|
||||||
|
|
||||||
|
class LMSSketch(Document):
|
||||||
|
def get_owner_name(self):
|
||||||
|
return get_userinfo(self.owner)['full_name']
|
||||||
|
|
||||||
|
def get_livecode_url(self):
|
||||||
|
doc = frappe.get_cached_doc("LMS Settings")
|
||||||
|
return doc.livecode_url
|
||||||
|
|
||||||
|
def get_livecode_ws_url(self):
|
||||||
|
url = urlparse(self.get_livecode_url())
|
||||||
|
protocol = "wss" if url.scheme == "https" else "ws"
|
||||||
|
return protocol + "://" + url.netloc + "/livecode"
|
||||||
|
|
||||||
|
def to_svg(self):
|
||||||
|
return self.svg or self.render_svg()
|
||||||
|
|
||||||
|
def render_svg(self):
|
||||||
|
h = hashlib.md5(self.code.encode('utf-8')).hexdigest()
|
||||||
|
cache = frappe.cache()
|
||||||
|
key = "sketch-" + h
|
||||||
|
value = cache.get(key)
|
||||||
|
if value:
|
||||||
|
value = value.decode('utf-8')
|
||||||
|
else:
|
||||||
|
ws_url = self.get_livecode_ws_url()
|
||||||
|
value = livecode.livecode_to_svg(ws_url, self.code)
|
||||||
|
cache.set(key, value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<LMSSketch {self.name}>"
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def save_sketch(name, title, code):
|
||||||
|
if not name or name == "new":
|
||||||
|
doc = frappe.new_doc('LMS Sketch')
|
||||||
|
doc.title = title
|
||||||
|
doc.code = code
|
||||||
|
doc.runtime = 'python-canvas'
|
||||||
|
doc.insert()
|
||||||
|
status = "created"
|
||||||
|
else:
|
||||||
|
doc = frappe.get_doc("LMS Sketch", name)
|
||||||
|
|
||||||
|
if doc.owner != frappe.session.user:
|
||||||
|
return {
|
||||||
|
"ok": False,
|
||||||
|
"error": "Permission Denied"
|
||||||
|
}
|
||||||
|
doc.title = title
|
||||||
|
doc.code = code
|
||||||
|
doc.svg = ''
|
||||||
|
doc.save()
|
||||||
|
status = "updated"
|
||||||
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"status": status,
|
||||||
|
"name": doc.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_recent_sketches():
|
||||||
|
"""Returns the recent sketches.
|
||||||
|
|
||||||
|
The return value will be a list of dicts with each entry containing
|
||||||
|
the following fields:
|
||||||
|
- name
|
||||||
|
- title
|
||||||
|
- owner
|
||||||
|
- owner_name
|
||||||
|
- modified
|
||||||
|
"""
|
||||||
|
sketches = frappe.get_all(
|
||||||
|
"LMS Sketch",
|
||||||
|
fields='*',
|
||||||
|
order_by='modified desc',
|
||||||
|
page_length=100
|
||||||
|
)
|
||||||
|
for s in sketches:
|
||||||
|
s['owner_name'] = get_userinfo(s['owner'])['full_name']
|
||||||
|
return [frappe.get_doc(doctype='LMS Sketch', **doc) for doc in sketches]
|
||||||
|
|
||||||
|
def get_userinfo(email):
|
||||||
|
"""Returns the username and fullname of a user.
|
||||||
|
|
||||||
|
Please note that the email could be "Administrator" or "Guest"
|
||||||
|
as a special case to denote the system admin and guest user respectively.
|
||||||
|
"""
|
||||||
|
user = frappe.get_doc("User", email)
|
||||||
|
return {
|
||||||
|
"full_name": user.full_name,
|
||||||
|
"username": user.username
|
||||||
|
}
|
||||||
10
community/lms/doctype/lms_sketch/test_lms_sketch.py
Normal file
10
community/lms/doctype/lms_sketch/test_lms_sketch.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, FOSS United and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestLMSSketch(unittest.TestCase):
|
||||||
|
pass
|
||||||
5
community/public/build.json
Normal file
5
community/public/build.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"css/lms.css": [
|
||||||
|
"public/css/lms.css"
|
||||||
|
]
|
||||||
|
}
|
||||||
64
community/public/css/lms.css
Normal file
64
community/public/css/lms.css
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
.canvas-wrapper {
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-editor canvas {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
height: 300px;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.canvas-wrapper .output {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-editor .code {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
min-height: 330px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
.canvas-editor .output {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
background: #eee;
|
||||||
|
padding: 10px;
|
||||||
|
clear: both;
|
||||||
|
color: #212529;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-editor h2 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-editor .run {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-editor .col-sm {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sketch-header h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sketch-header {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "templates/base.html" %}
|
{% extends "templates/base.html" %}
|
||||||
|
{% from "www/macros/livecode.html" import LiveCodeEditor with context %}
|
||||||
{% block title %}{{topic.title}} ({{course.title}}){% endblock %}
|
{% block title %}{{topic.title}} ({{course.title}}){% endblock %}
|
||||||
{% block head_include %}
|
{% block head_include %}
|
||||||
<meta name="description" content="Topic {{topic.title}} of the course {{course.title}}" />
|
<meta name="description" content="Topic {{topic.title}} of the course {{course.title}}" />
|
||||||
@@ -7,6 +8,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.css">
|
<link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.css">
|
||||||
|
<link rel="stylesheet" href="/assets/css/lms.css">
|
||||||
|
|
||||||
<script src="{{ livecode_url }}/static/codemirror/lib/codemirror.js"></script>
|
<script src="{{ livecode_url }}/static/codemirror/lib/codemirror.js"></script>
|
||||||
<script src="{{ livecode_url }}/static/codemirror/mode/python/python.js"></script>
|
<script src="{{ livecode_url }}/static/codemirror/mode/python/python.js"></script>
|
||||||
@@ -42,7 +44,7 @@
|
|||||||
{% if s.type == "text" %}
|
{% if s.type == "text" %}
|
||||||
{{ render_section_text(s) }}
|
{{ render_section_text(s) }}
|
||||||
{% elif s.type == "code" %}
|
{% elif s.type == "code" %}
|
||||||
{{ render_section_code(s) }}
|
{{ LiveCodeEditor(s.name, s.code) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div>Unknown section type: {{s.type}}</div>
|
<div>Unknown section type: {{s.type}}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -52,28 +54,6 @@
|
|||||||
{{ s.contents | markdown }}
|
{{ s.contents | markdown }}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro render_section_code(s) %}
|
|
||||||
<div class="canvas-editor row no-gutters" id="editor-{{s.name}}">
|
|
||||||
<div class="col-sm">
|
|
||||||
<div class="heading">
|
|
||||||
<button class="run">Run</button>
|
|
||||||
<h2>Editor</h2>
|
|
||||||
</div>
|
|
||||||
<textarea class="code">{{s.code}}</textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm">
|
|
||||||
<div class="heading">
|
|
||||||
<h2>Output</h2>
|
|
||||||
</div>
|
|
||||||
<div class="canvas-wrapper">
|
|
||||||
<canvas class="canvas" width="300" height="300">Dashed box</canvas>
|
|
||||||
<pre class="output"></pre>
|
|
||||||
</div>
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{%- block script %}
|
{%- block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script type="text/javascript" src="{{ livecode_url }}/static/livecode.js"></script>
|
<script type="text/javascript" src="{{ livecode_url }}/static/livecode.js"></script>
|
||||||
@@ -89,73 +69,3 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{%- block style %}
|
|
||||||
{{ super() }}
|
|
||||||
|
|
||||||
<style type="text/css">
|
|
||||||
|
|
||||||
.canvas-wrapper {
|
|
||||||
position: relative;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-editor canvas {
|
|
||||||
position: relative;
|
|
||||||
z-index: 0;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
height: 300px;
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
.canvas-wrapper .output {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
width: 100%;
|
|
||||||
left: 0px;
|
|
||||||
top: 0px;
|
|
||||||
background-color: rgba(255, 255, 255, 0);
|
|
||||||
margin: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-editor .code {
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px;
|
|
||||||
min-height: 330px;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
.canvas-editor .output {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
background: #eee;
|
|
||||||
padding: 10px;
|
|
||||||
clear: both;
|
|
||||||
color: #212529;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-editor h2 {
|
|
||||||
font-size: 1.2em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin: 0px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-editor .run {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-editor .col-sm {
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .canvas-editor canvas {
|
|
||||||
float: left;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
padding: 5px;
|
|
||||||
margin: 10px;
|
|
||||||
} */
|
|
||||||
|
|
||||||
</style>
|
|
||||||
{%- endblock %}
|
|
||||||
|
|||||||
40
community/www/macros/livecode.html
Normal file
40
community/www/macros/livecode.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
{% macro LiveCodeEditor(name, code) %}
|
||||||
|
<div class="canvas-editor row no-gutters" id="editor-{{name}}">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="heading">
|
||||||
|
<button class="run">Run</button>
|
||||||
|
<h2>Editor</h2>
|
||||||
|
</div>
|
||||||
|
<textarea class="code">{{code}}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="heading">
|
||||||
|
<h2>Output</h2>
|
||||||
|
</div>
|
||||||
|
<div class="canvas-wrapper">
|
||||||
|
<canvas class="canvas" width="300" height="300"></canvas>
|
||||||
|
<pre class="output"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{% macro LiveCodeEditorJS(name, code) %}
|
||||||
|
<script type="text/javascript" src="{{ livecode_url }}/static/livecode.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var livecodeEditors = [];
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
$(".canvas-editor").each((i, e) => {
|
||||||
|
var editor = new LiveCodeEditor(e, {
|
||||||
|
runtime: "python-canvas",
|
||||||
|
base_url: "{{ livecode_url }}",
|
||||||
|
codemirror: true
|
||||||
|
})
|
||||||
|
livecodeEditors.push(editor);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endmacro %}
|
||||||
47
community/www/sketches/index.html
Normal file
47
community/www/sketches/index.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{% extends "templates/base.html" %}
|
||||||
|
{% from "www/macros/livecode.html" import LiveCodeEditor, LiveCodeEditorJS %}
|
||||||
|
|
||||||
|
{% block title %}Sketches{% endblock %}
|
||||||
|
{% block head_include %}
|
||||||
|
<meta name="description" content="Sketches" />
|
||||||
|
<meta name="keywords" content="sketches" />
|
||||||
|
<link rel="stylesheet" href="/assets/css/lms.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="top-section" style="padding: 1rem 0rem;">
|
||||||
|
<div class='container pb-5'>
|
||||||
|
<h1>Recent Sketches</h1>
|
||||||
|
|
||||||
|
<a href="/sketches/sketch?sketch=new">Create a New Sketch</a>
|
||||||
|
</div>
|
||||||
|
<div class='container'>
|
||||||
|
<div class="row row-cols-1 row-cols-xl-5 row-cols-lg-4 row-cols-md-3 row-cols-sm-2 ">
|
||||||
|
{% for sketch in sketches %}
|
||||||
|
<div class="col mb-4">
|
||||||
|
<div class="card" style="width: 200px;">
|
||||||
|
<div class="card-img-top">
|
||||||
|
<a href="/sketches/sketch?sketch={{sketch.name}}">
|
||||||
|
{{ sketch.to_svg() }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
By {{sketch.get_owner_name()}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block style %}
|
||||||
|
{{super()}}
|
||||||
|
<style type="text/css">
|
||||||
|
svg {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
7
community/www/sketches/index.py
Normal file
7
community/www/sketches/index.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import frappe
|
||||||
|
from ...lms.doctype.lms_sketch.lms_sketch import get_recent_sketches
|
||||||
|
|
||||||
|
def get_context(context):
|
||||||
|
context.no_cache = 1
|
||||||
|
context.sketches = get_recent_sketches()
|
||||||
|
|
||||||
109
community/www/sketches/sketch.html
Normal file
109
community/www/sketches/sketch.html
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
{% extends "templates/base.html" %}
|
||||||
|
{% from "www/macros/livecode.html" import LiveCodeEditor, LiveCodeEditorJS with context %}
|
||||||
|
|
||||||
|
{% block title %}{{sketch.title}}{% endblock %}
|
||||||
|
{% block head_include %}
|
||||||
|
<meta name="description" content="Sketch {{sketch.title}}" />
|
||||||
|
<meta name="keywords" content="sketch {{sketch.title}}" />
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.css">
|
||||||
|
<link rel="stylesheet" href="/assets/css/lms.css">
|
||||||
|
|
||||||
|
<script src="{{ livecode_url }}/static/codemirror/lib/codemirror.js"></script>
|
||||||
|
<script src="{{ livecode_url }}/static/codemirror/mode/python/python.js"></script>
|
||||||
|
<script src="{{ livecode_url }}/static/codemirror/keymap/sublime.js"></script>
|
||||||
|
|
||||||
|
<script src="{{ livecode_url }}/static/codemirror/addon/edit/matchbrackets.js"></script>
|
||||||
|
<script src="{{ livecode_url }}/static/codemirror/addon/comment/comment.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="top-section" style="padding: 1rem 0rem;">
|
||||||
|
<div class='container pb-5'>
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item" aria-current="page"><a href="/sketches">Sketches</a></li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="sketch-header">
|
||||||
|
{% if editable %}
|
||||||
|
<input type="button" class="pull-right" id="sketch-save" value="Save"/>
|
||||||
|
<h1 class="sketch-title">
|
||||||
|
<input type="text" name="title" id="sketch-title" value="{{ sketch.title }}" />
|
||||||
|
</h1>
|
||||||
|
{% else %}
|
||||||
|
<h1 class="sketch-title">{{sketch.title}}</h1>
|
||||||
|
{% endif %}
|
||||||
|
<div class="sketch-owner-wrapper">By <span class="sketch-owner">{{sketch.get_owner_name()}}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if sketch.is_new() and not editable %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
Please login to save this sketch.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="sketch-editor">
|
||||||
|
{{LiveCodeEditor(sketch.name, sketch.code) }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{%- block script %}
|
||||||
|
{{ super() }}
|
||||||
|
{{ LiveCodeEditorJS() }}
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var sketch_name = {{ sketch.name | tojson }};
|
||||||
|
|
||||||
|
function saveSketch() {
|
||||||
|
var title = $("#sketch-title").val()
|
||||||
|
var code = livecodeEditors[0].codemirror.doc.getValue()
|
||||||
|
frappe.call('community.lms.doctype.lms_sketch.lms_sketch.save_sketch', {
|
||||||
|
name: sketch_name,
|
||||||
|
title: title,
|
||||||
|
code: code
|
||||||
|
})
|
||||||
|
.then(r => {
|
||||||
|
var msg = r.message;
|
||||||
|
if (!msg.ok) {
|
||||||
|
var error = msg.error || "Save failed."
|
||||||
|
frappe.msgprint({
|
||||||
|
"title": "Error",
|
||||||
|
"indicator": "red",
|
||||||
|
"message": error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (msg.status == "created") {
|
||||||
|
var path = "/sketches/sketch?sketch=" + msg.name;
|
||||||
|
var url = window.location.protocol + "//" + window.location.host + path
|
||||||
|
window.history.pushState({path: url}, '', url);
|
||||||
|
sketch_name = name;
|
||||||
|
|
||||||
|
frappe.msgprint({
|
||||||
|
"title": "Notification",
|
||||||
|
"indicator": "green",
|
||||||
|
"message": "New sketch has been saved!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (msg.status == "updated") {
|
||||||
|
frappe.msgprint({
|
||||||
|
"title": "Notification",
|
||||||
|
"indicator": "green",
|
||||||
|
"message": "The sketch has been saved!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
$("#sketch-save").click(function() {
|
||||||
|
saveSketch();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{%- endblock %}
|
||||||
|
|
||||||
41
community/www/sketches/sketch.py
Normal file
41
community/www/sketches/sketch.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def get_context(context):
|
||||||
|
context.no_cache = 1
|
||||||
|
course_name = get_queryparam("sketch", '/sketches')
|
||||||
|
context.sketch = get_sketch(course_name)
|
||||||
|
context.livecode_url = get_livecode_url()
|
||||||
|
context.editable = is_editable(context.sketch, frappe.session.user)
|
||||||
|
|
||||||
|
def is_editable(sketch, user):
|
||||||
|
if sketch.is_new():
|
||||||
|
# new sketches can be editable by any logged in user
|
||||||
|
return user != "Guest"
|
||||||
|
else:
|
||||||
|
# existing sketches are editable by the owner
|
||||||
|
return sketch.owner == user
|
||||||
|
|
||||||
|
def get_livecode_url():
|
||||||
|
doc = frappe.get_doc("LMS Settings")
|
||||||
|
return doc.livecode_url
|
||||||
|
|
||||||
|
def get_queryparam(name, redirect_when_not_found):
|
||||||
|
try:
|
||||||
|
return frappe.form_dict[name]
|
||||||
|
except KeyError:
|
||||||
|
frappe.local.flags.redirect_location = redirect_when_not_found
|
||||||
|
raise frappe.Redirect
|
||||||
|
|
||||||
|
def get_sketch(name):
|
||||||
|
if name == 'new':
|
||||||
|
sketch = frappe.new_doc('LMS Sketch')
|
||||||
|
sketch.name = "new"
|
||||||
|
sketch.title = "New Sketch"
|
||||||
|
sketch.code = "circle(100, 100, 50)"
|
||||||
|
return sketch
|
||||||
|
|
||||||
|
try:
|
||||||
|
return frappe.get_doc('LMS Sketch', name)
|
||||||
|
except frappe.exceptions.DoesNotExistError:
|
||||||
|
raise frappe.NotFound
|
||||||
|
|
||||||
@@ -1 +1,3 @@
|
|||||||
frappe
|
frappe
|
||||||
|
websocket_client
|
||||||
|
drawSvg
|
||||||
|
|||||||
Reference in New Issue
Block a user