commit 1699990c020bd8047b896797425c3831f270287b Author: Ozbolt Menegatti Date: Tue Nov 5 22:18:20 2019 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5366a45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules +package-lock.json +__pycache__ + +build/* +!build/Makefile +!build/browserify.js + + diff --git a/build/Makefile b/build/Makefile new file mode 100644 index 0000000..8d199a2 --- /dev/null +++ b/build/Makefile @@ -0,0 +1,41 @@ +SRC_FOLDER=$(CURDIR)/../src +RES_FOLDER=$(CURDIR)/../res + +TS_BUILD=tsbuild +TS_FLAGS=-da -n -m -od ../build/$(TS_BUILD) # has to be relative for some reason + +LESS_FILE=$(RES_FOLDER)/main.less +PY_FILE=$(SRC_FOLDER)/main.py +HTML_FILE=$(RES_FOLDER)/main.html + +.PHONY: all transcrypt $(LESS_FILE) $(PY_FILE) $(HTML_FILE) + + +# mypy: +# mypy src/main.py + +all: main.css bundle.js main.html + +transcrypt: $(PY_FILE) $(TS_BUILD) + # ln -s $(TS_BUILD) $(SRC_FOLDER)/__target__ + transcrypt $(TS_FLAGS) $< + # rm $(SRC_FOLDER)/__target__ + +bundle.js: transcrypt + node browserify.js $(TS_BUILD)/main.js > bundle.js + +main.css: $(LESS_FILE) + lessc $< > $@ + +$(TS_BUILD): + mkdir -p $(TS_BUILD) + +main.html: $(HTML_FILE) + cp $(RES_FOLDER)/main.html ./ + +clean: + rm -r $(TS_BUILD) + rm bundle.js main.html main.css + + + diff --git a/build/browserify.js b/build/browserify.js new file mode 100644 index 0000000..0b88098 --- /dev/null +++ b/build/browserify.js @@ -0,0 +1,11 @@ +var browserify = require('browserify'); +var esmify = require('esmify'); + +var bundler = browserify({ + debug: true, + paths: ["../node_modules/snabbdom/"], + plugin: [esmify] +}); + +bundler.add(process.argv[2]); +bundler.bundle().pipe(process.stdout); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c109905 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "vsms-py", + "version": "0.0.1", + "description": "Trying out using transcrypt!", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Ozbolt Menegatti", + "license": "MPL2", + "devDependencies": { + "sorcery": "^0.10.0", + "sourceify": "^1.0.0", + "tinyify": "^2.5.2" + }, + "dependencies": { + "browserify": "^16.5.0", + "esmify": "^2.1.1", + "snabbdom": "^0.7.3" + } +} diff --git a/res/line.png b/res/line.png new file mode 100644 index 0000000..fa3e96c Binary files /dev/null and b/res/line.png differ diff --git a/res/main.html b/res/main.html new file mode 100644 index 0000000..50a860d --- /dev/null +++ b/res/main.html @@ -0,0 +1,13 @@ + + + + + PYVsms + + + +

My First App

+
+ + + diff --git a/res/main.less b/res/main.less new file mode 100644 index 0000000..02ec05f --- /dev/null +++ b/res/main.less @@ -0,0 +1,192 @@ +@title-color: #cc3366; +@line-image: "./line.png"; + +.translation-element-margin { + margin: 0 0.3em; + padding: 0; +} + +.translation-button { + width: 1.5em; + height: 1.5em; + border: 1px solid #ccc; +} + +.elm-div { + clear: both; + overflow: auto; + margin-bottom: 1em; +} + +#entry { + #entry-header { + display: block; + margin-bottom: 1em; + + #headword { + font-size: 1.2em; + color: @title-color; + font-weight: bold; + } + + #grammar { + color: #666666; + font-style: italic; + margin-left: 1em; + } + + #comment { + background-color: #e9d76d; + padding: 0.5em; + float: right; + cursor: pointer; + + &:before { + content: "Opomba: " + } + } + } + + #sense-container { + border-top: 1px dotted #999999; + padding-top: 0.5em; + margin-top: 0.5em; + + .sense-num { + float: left; + } + + .sense { + margin-left: 2em; + + .sense-definition { + display: inline; + cursor: pointer; + margin: 0 0.2em; + + &:hover { + background-color: #CCCCCC; + } + &:after { + content: ")"; + } + &:before { + content: "("; + } + + } + + .sense-label { + display: inline; + cursor: pointer; + margin: 0 0.2em; + color: lightgreen; + font-size: 0.8em; + + &:hover { + background-color: #CCCCCC; + } + } + + .translation-div { + display: inline; + + .translation-tags { + display: inline; + color: green; + font-size: 0.6em; + } + + .translation-text { + cursor: pointer; + display: inline; + color: blue; + .translation-element-margin(); + + } + + select { + .translation-element-margin(); + .translation-button(); + + border: 1px solid #ccc; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + background-size: 100%; + background-repeat: repeat-y; + color: rgba(0,0,0,0); + background-image: data-uri(@line-image); //url(line.png); + } + + &:hover { + background-color: #CCCCCC; + } + } + + .translation-add { + .translation-element-margin(); + .translation-button(); + } + } + + .example { + .example-dot, .example-rest { + float: left; + } + + .example-arrow, .example-dot { + margin-right: 1em; + } + } + } +} + +#xml-status { + margin-left: 3em; + height: 1em; + margin-bottom: -1em; + font-size: 1em; +} + +.xml-status-bad { color: red; } +.xml-status-good { color: green; } +.xml-status-wait { color: yellow; } + + +#log { + overflow: scroll; + border: 2px solid red; + height: 10em; + + :nth-last-child(odd) { + background-color: lightgray; + } +} + +// sweet modal edit entry + +.tag-div { + .tag-key { + width: 10em; + display: inline-block + } + + .tag-input { + margin-left: 1em; + width: 9em; + } + + select { + width: 9em; + } +} + +#cluster-group-div { + margin-top: 1em; + span { + width: 10em; + display: inline-block + } +} diff --git a/src/browser/__init__.py b/src/browser/__init__.py new file mode 100644 index 0000000..1347603 --- /dev/null +++ b/src/browser/__init__.py @@ -0,0 +1,4 @@ +from browser.internal import Document, Window # type: ignore + +document = Document() +window = Window() \ No newline at end of file diff --git a/src/browser/internal.py b/src/browser/internal.py new file mode 100644 index 0000000..3f41733 --- /dev/null +++ b/src/browser/internal.py @@ -0,0 +1,15 @@ +# type: ignore + + +class Document: + def __getattr__(self, attr): + return lambda x: document[attr](x) + + +class Window: + def __getattr__(self, attr): + return lambda x: window[attr](x) + + + + diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..fbbbed9 --- /dev/null +++ b/src/main.py @@ -0,0 +1,76 @@ +from snabbdom import h, patch + + +from browser import document +from model import Model +from view import View +from update import update + + +model = Model() + + +def main(xml_str): + model.import_xml(xml_str) + update.set_view(View(document.getElementById("app"))) + update.set_model(model) + update.update_model() + + +main(""" + + A + + adolescenca + + + samostalnik + + + + + + + + + + + obdobje + + + + preizkus + explainme! + + podrocjebiologija + + + + fsd + + + preskus + + + sdfsd + + podrocjeozboltologija + + + + fsd + + + + + The test was interesting. + + Preizkus je bil zanimiv. + + + + + + + """) + diff --git a/src/message/__init__.py b/src/message/__init__.py new file mode 100644 index 0000000..8e459ef --- /dev/null +++ b/src/message/__init__.py @@ -0,0 +1,9 @@ +from message.simple_messages import ListItemClick + + +def msg(message_class): + from update import update + def callback(arg): + message_instance = message_class(arg) + update.schedule(message_instance) + return callback \ No newline at end of file diff --git a/src/message/message.py b/src/message/message.py new file mode 100644 index 0000000..970a1e2 --- /dev/null +++ b/src/message/message.py @@ -0,0 +1,3 @@ +class Message: + def update_model(self, model): + raise NotImplementedError("This message does not implement update_model method") \ No newline at end of file diff --git a/src/message/simple_messages.py b/src/message/simple_messages.py new file mode 100644 index 0000000..6a77a47 --- /dev/null +++ b/src/message/simple_messages.py @@ -0,0 +1,10 @@ +from message.message import Message + + +class ListItemClick(Message): + def __init__(self, num): + self.num = num + + def update_model(self, model): + print(self.num) + model.names.splice(self.num, 1) diff --git a/src/model/__init__.py b/src/model/__init__.py new file mode 100644 index 0000000..96c2aa4 --- /dev/null +++ b/src/model/__init__.py @@ -0,0 +1 @@ +from model.model import Model \ No newline at end of file diff --git a/src/model/entry.py b/src/model/entry.py new file mode 100644 index 0000000..13af1a7 --- /dev/null +++ b/src/model/entry.py @@ -0,0 +1,17 @@ +from model.sense import Sense + +class Entry: + def __init__(self, entry_xml): + status = entry_xml.querySelector("head status") + headword = entry_xml.querySelector("head headword lemma") + grammar = entry_xml.querySelector("head grammar category") + comment = entry_xml.querySelector("head comment") + + self.status = status.textContent if status else "" + self.headword = headword.textContent if headword else "" + self.grammar = grammar.textContent if grammar else "" + self.comment = comment.textContent if comment else "" + + self.senses = [Sense(sense_xml) for sense_xml in + entry_xml.querySelectorAll("body senseList sense")] + diff --git a/src/model/example.py b/src/model/example.py new file mode 100644 index 0000000..a6e9865 --- /dev/null +++ b/src/model/example.py @@ -0,0 +1,8 @@ +class Example: + def __init__(self, example_xml): + example = example_xml.querySelector("example") + translation = example_xml.querySelector("translationContainer translation") + + self.example = example.textContent if example else "" + self.translation = translation.textContent if translation else "" + diff --git a/src/model/model.py b/src/model/model.py new file mode 100644 index 0000000..b5f7de0 --- /dev/null +++ b/src/model/model.py @@ -0,0 +1,25 @@ +from model.entry import Entry + + +class Model: + def __init__(self, names): + self.entry = None + self.log = [] + self.names = ["Ozbolt", "Katarina"] + + + def import_xml(self, xml_text): + parser = __new__(DOMParser()) + xmlDoc = parser.parseFromString(xml_text, "text/xml") + self.entry = Entry(xmlDoc.querySelector("entry")) + + +# type alias Model = +# { shown: Bool +# , editable: Bool +# , entry: Maybe Entry +# , newXml: Bool +# , xml: Maybe String +# , xmlObject: Maybe Xml2.Value +# , log: List LogEntry +# } diff --git a/src/model/sense.py b/src/model/sense.py new file mode 100644 index 0000000..e46c0bc --- /dev/null +++ b/src/model/sense.py @@ -0,0 +1,27 @@ +from model.example import Example +from model.translation import Translation + +class Sense: + def __init__(self, sense_xml): + label = sense_xml.querySelector("labelList label") + definition = sense_xml.querySelector("definitionList definition") + + self.label = label.textContent if label else "" + self.definition = definition.textContent if definition else "" + + self.examples = [Example(example_xml) for example_xml in + sense_xml.querySelectorAll("exampleContainerList exampleContainer")] + + translations = [] + max_num_cluster = 0 + + for translation_xml in sense_xml.querySelectorAll("translationContainerList translationContainer"): + num_cluster = int(translation_xml.getAttribute("cluster")) + max_num_cluster = max(max_num_cluster, num_cluster) + translations.append((num_cluster, Translation(translation_xml))) + + self.translations = [[] for _ in range(max_num_cluster)] + for clusterNum, translation in translations: + self.translations[clusterNum - 1].append(translation) + + diff --git a/src/model/translation.py b/src/model/translation.py new file mode 100644 index 0000000..7a70fe6 --- /dev/null +++ b/src/model/translation.py @@ -0,0 +1,10 @@ +class Translation: + def __init__(self, translation_xml): + translation = translation_xml.querySelector("translation") + self.translation = translation.textContent if translation else "" + + self.tags = {} + for tag_xml in translation_xml.querySelectorAll("tagsContainer tag"): + t_type = tag_xml.querySelector("type").textContent + t_value = tag_xml.querySelector("value").textContent + self.tags[t_type] = t_value diff --git a/src/snabbdom.py b/src/snabbdom.py new file mode 100644 index 0000000..aad9ea9 --- /dev/null +++ b/src/snabbdom.py @@ -0,0 +1,17 @@ +__pragma__ ('noanno') +__pragma__ ('js', """ +var snabbdom = require('snabbdom.js'); + +var s_patch = snabbdom.init([ // Init patch function with chosen modules + require('snabbdom/modules/class').default, // makes it easy to toggle classes + require('snabbdom/modules/props').default, // for setting properties on DOM elements + require('snabbdom/modules/style').default, // handles styling on elements with support for animations + require('snabbdom/modules/eventlisteners').default, // attaches event listeners +]); +var s_h = require('snabbdom/h').default; // helper function for creating vnodes +""", None) + +# export the symbols + +h = s_h +patch = s_patch \ No newline at end of file diff --git a/src/update.py b/src/update.py new file mode 100644 index 0000000..c99cade --- /dev/null +++ b/src/update.py @@ -0,0 +1,27 @@ +class Update: + def __init__(self): + self.message_queue = [] + self.model = None + self.view = None + + def update_model(self): + for msg in self.message_queue: + msg.update_model(self.model) + + self.message_queue = [] + self.view.view(self.model) + + def schedule(self, msg): + self.message_queue.append(msg) + # for now, directly clearing message queue + self.update_model() + + def set_model(self, model): + self.model = model + + def set_view(self, view): + self.view = view + + +update = Update() + diff --git a/src/view.py b/src/view.py new file mode 100644 index 0000000..e02cc48 --- /dev/null +++ b/src/view.py @@ -0,0 +1,98 @@ +from snabbdom import h, patch +from message import ListItemClick, msg + + +class View: + def __init__(self, container): + self.vdom = h('div', {}, "Loading...") + self.model = None + patch(container, self.vdom) + + def view(self, model): + self.model = model + new_vdom = self._view() + patch(self.vdom, new_vdom) + self.vdom = new_vdom + + def _view(self): + return View.view_entry(self.model.entry) + + def view_list_elements(self): + def callback(num): + return msg(lambda: ListItemClick(num)) + + list_elements = [ + h('li', {"on": {"click": callback(idx)}}, name) + for idx, name in enumerate(self.model.names) + ] + return h('ol', {}, list_elements) + + @staticmethod + def view_entry(entry): + view_sense_list = [View.view_sense(sense, idx) for idx, sense in enumerate(entry.senses)] + + return h("div#entry", {}, [ + h("div#entry-status", {}, entry.status), + h("div#entry-header", {}, [ + h("span#headword", {}, entry.headWord), + h("span#grammar", {}, entry.grammar), + h("span#comment", {}, entry.comment)]), + h("div#sense-container", {}, view_sense_list)]) + + @staticmethod + def view_sense(sense, senseNum): + examples = [View.view_example(example) for example in sense.examples] + + return h("div.elm-div", {}, [ + h("div.sense-num", {}, str(senseNum + 1)), + h("div.sense", {}, [ + h("span.sense-label", {}, sense.label), + h("span.sense-definition", {}, sense.definition), + h("div", {}, View.view_translations(sense.translations)), + h("div", {}, examples), + h("input#translation-add", + {"attr": {"type": "button", "value": "+", "title": "Dodaj prevedek / HUN"}}, + [])])]) + + @staticmethod + def view_example(example): + return h("div.example", {}, [ + h("div.example-dot", {}, "▣"), + h("div.example-rest", {}, [ + h("span.example-text", {}, example.example), + h("div.example-translation", {}, [ + h("span.example-arrow", {}, "↪"), + h("span", {}, example.translation)])])]) + + @staticmethod + def view_translations(translations): + joiner = lambda: h("span.translation-semicolon", {}, ";") + result = [] + + for cluster in translations: + result.extend([View.view_one_translation(t) for t in cluster]) + result.append(joiner()) + + result.pop() + return result + + @staticmethod + def view_one_translation(translation): + elements = [] + + if translation.tags: + tags = h("div.translation-tags", {}, [ + h("span", {"attr": {"title": key}}, value) + for key, value in translation.tags.items()]) + elements.append(tags) + + elements.append(h("span.translation-text", {}, translation.translation)) + #elements.append(h("select.translation-select", {}, [ + # h("option", {"style": {"color": "black"}, {"attr": {"value": "edit", "title": "Spremeni"}}}, "✎"), + # h("option", {"style": {"color": "black"}, {"attr": {"value": "right", "title": "Desno"}}}, "→"), + # h("option", {"style": {"color": "black"}, {"attr": {"value": "left", "title": "Levo"}}}, "←"), + # h("option", {"style": {"color": "black"}, {"attr": {"value": "bin", "title": "Odstrani"}}}, "🗑")])) + + return h("div.translation-div", {}, elements) + +