diff --git a/community/lms/doctype/lms_sketch/livecode.py b/community/lms/doctype/lms_sketch/livecode.py new file mode 100644 index 00000000..1eb804ad --- /dev/null +++ b/community/lms/doctype/lms_sketch/livecode.py @@ -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 diff --git a/community/lms/doctype/lms_sketch/lms_sketch.py b/community/lms/doctype/lms_sketch/lms_sketch.py index 2ef8f989..21257e49 100644 --- a/community/lms/doctype/lms_sketch/lms_sketch.py +++ b/community/lms/doctype/lms_sketch/lms_sketch.py @@ -3,13 +3,44 @@ # 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"" + @frappe.whitelist() def save_sketch(name, title, code): if not name or name == "new": @@ -29,6 +60,7 @@ def save_sketch(name, title, code): } doc.title = title doc.code = code + doc.svg = '' doc.save() status = "updated" return { @@ -50,13 +82,13 @@ def get_recent_sketches(): """ sketches = frappe.get_all( "LMS Sketch", - fields=['name', 'title', 'owner', 'modified'], + fields='*', order_by='modified desc', page_length=100 ) for s in sketches: s['owner_name'] = get_userinfo(s['owner'])['full_name'] - return sketches + return [frappe.get_doc(doctype='LMS Sketch', **doc) for doc in sketches] def get_userinfo(email): """Returns the username and fullname of a user. diff --git a/community/www/sketches/index.html b/community/www/sketches/index.html index f67ef2a7..5da1037e 100644 --- a/community/www/sketches/index.html +++ b/community/www/sketches/index.html @@ -16,13 +16,32 @@ Create a New Sketch
+
{% for sketch in sketches %} -
- {{ sketch.modified }} - {{sketch.title}} - By {{sketch.owner_name}} +
+
+ + +
+
{% endfor %} +
{% endblock %} + +{% block style %} + {{super()}} + +{% endblock %} diff --git a/requirements.txt b/requirements.txt index 5ac1c812..6da7c51b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ -frappe \ No newline at end of file +frappe +websocket_client +drawSvg