first commit
This commit is contained in:
commit
1699990c02
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
node_modules
|
||||
package-lock.json
|
||||
__pycache__
|
||||
|
||||
build/*
|
||||
!build/Makefile
|
||||
!build/browserify.js
|
||||
|
||||
|
41
build/Makefile
Normal file
41
build/Makefile
Normal 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
11
build/browserify.js
Normal 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
20
package.json
Normal 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
BIN
res/line.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 B |
13
res/main.html
Normal file
13
res/main.html
Normal 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
192
res/main.less
Normal 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
4
src/browser/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from browser.internal import Document, Window # type: ignore
|
||||
|
||||
document = Document()
|
||||
window = Window()
|
15
src/browser/internal.py
Normal file
15
src/browser/internal.py
Normal 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
76
src/main.py
Normal 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
9
src/message/__init__.py
Normal 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
3
src/message/message.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Message:
|
||||
def update_model(self, model):
|
||||
raise NotImplementedError("This message does not implement update_model method")
|
10
src/message/simple_messages.py
Normal file
10
src/message/simple_messages.py
Normal 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
1
src/model/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from model.model import Model
|
17
src/model/entry.py
Normal file
17
src/model/entry.py
Normal 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
8
src/model/example.py
Normal 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
25
src/model/model.py
Normal 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
27
src/model/sense.py
Normal 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
10
src/model/translation.py
Normal 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
17
src/snabbdom.py
Normal 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
27
src/update.py
Normal 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
98
src/view.py
Normal 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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user