first commit

This commit is contained in:
Ozbolt Menegatti 2019-11-05 22:18:20 +01:00
commit 1699990c02
22 changed files with 633 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
node_modules
package-lock.json
__pycache__
build/*
!build/Makefile
!build/browserify.js

41
build/Makefile Normal file
View File

@ -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

11
build/browserify.js Normal file
View File

@ -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);

20
package.json Normal file
View File

@ -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"
}
}

BIN
res/line.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 B

13
res/main.html Normal file
View File

@ -0,0 +1,13 @@
<meta charset="utf-8"/>
<html>
<head>
<meta charset="UTF-8">
<title>PYVsms</title>
<link rel="stylesheet" href="main.css" />
</head>
<body>
<h1>My First App</h1>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>

192
res/main.less Normal file
View File

@ -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
}
}

4
src/browser/__init__.py Normal file
View File

@ -0,0 +1,4 @@
from browser.internal import Document, Window # type: ignore
document = Document()
window = Window()

15
src/browser/internal.py Normal file
View File

@ -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)

76
src/main.py Normal file
View File

@ -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("""<entry>
<head>
<status>A</status>
<headword>
<lemma>adolescenca</lemma>
</headword>
<grammar>
<category>samostalnik</category>
</grammar>
<comment></comment>
</head>
<body>
<senseList>
<sense>
<labelList>
<label>tehnika</label>
</labelList>
<definitionList>
<definition type="indicator">obdobje</definition>
</definitionList>
<translationContainerList>
<translationContainer cluster="1">
<translation>preizkus</translation>
<explanation>explainme!</explanation>
<tagsContainer>
<tag><type>podrocje</type><value>biologija</value></tag>
</tagsContainer>
</translationContainer>
<translationContainer cluster="1">
<translation>fsd</translation>
</translationContainer>
<translationContainer cluster="2">
<translation>preskus</translation>
</translationContainer>
<translationContainer cluster="2">
<translation>sdfsd</translation>
<tagsContainer>
<tag><type>podrocje</type><value>ozboltologija</value></tag>
</tagsContainer>
</translationContainer>
<translationContainer cluster="3">
<translation>fsd</translation>
</translationContainer>
</translationContainerList>
<exampleContainerList>
<exampleContainer>
<example>The test was interesting.</example>
<translationContainer>
<translation>Preizkus je bil zanimiv.</translation>
</translationContainer>
</exampleContainer>
</exampleContainerList>
</sense>
</senseList>
</body>
</entry>""")

9
src/message/__init__.py Normal file
View File

@ -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

3
src/message/message.py Normal file
View File

@ -0,0 +1,3 @@
class Message:
def update_model(self, model):
raise NotImplementedError("This message does not implement update_model method")

View File

@ -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)

1
src/model/__init__.py Normal file
View File

@ -0,0 +1 @@
from model.model import Model

17
src/model/entry.py Normal file
View File

@ -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")]

8
src/model/example.py Normal file
View File

@ -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 ""

25
src/model/model.py Normal file
View File

@ -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
# }

27
src/model/sense.py Normal file
View File

@ -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)

10
src/model/translation.py Normal file
View File

@ -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

17
src/snabbdom.py Normal file
View File

@ -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

27
src/update.py Normal file
View File

@ -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()

98
src/view.py Normal file
View File

@ -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)