This commit is contained in:
Kenneth Jao 2018-02-26 15:15:47 -05:00
commit 8cbcc247b4
5 changed files with 388 additions and 77 deletions

View File

@ -112,3 +112,5 @@ venv.bak/
# Other # Other
*.p *.p
*.txt
*.db

View File

@ -1,72 +1,219 @@
import pickle
from flask import Flask from flask import Flask
from flask import render_template, jsonify, request from flask import render_template, jsonify, request
import ulid from flask_sqlalchemy import SQLAlchemy
import time
app = Flask(__name__) app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db'
app.config.update( app.config.update(
DEBUG=True, DEBUG=True,
TEMPLATES_AUTO_RELOAD=True TEMPLATES_AUTO_RELOAD=True
) )
db = SQLAlchemy(app)
try:
with open("save.p", "rb") as f:
database = pickle.load(f)
# Can be commented out after non-id languages are all converted
for item in database['values']:
if not 'id' in item:
item['id'] = ulid.new().str
except (FileNotFoundError) as e:
database = {'languages': [],
'phonemes': [],
'values': []}
def saveDatabase(): class Frequency(db.Model):
# Save copy under separate name language_id = db.Column(db.Integer, db.ForeignKey('language.id'), primary_key=True)
with open("newestsave.p", "wb") as f: phoneme_id = db.Column(db.Integer, db.ForeignKey('phoneme.id'), primary_key=True)
pickle.dump(database, f) value = db.Column(db.Float(6), nullable=False)
phoneme = db.relationship('Phoneme')
class Language(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
source = db.Column(db.LargeBinary)
phonemes = db.relationship('Frequency')
class Phoneme(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(5), nullable=False, unique=True)
class Update(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
author = db.Column(db.String(30), nullable=False)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
date = db.Column(db.BigInteger, nullable=False,
default=int(time.time()*1000))
def generate_key():
pass
class Editor(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
description = db.Column(db.String(75), nullable=False)
authority = db.Column(db.Integer, nullable=False, default=1)
# 0: Full Access
# 1: Edit values and Add files
# 2: Edit values
# 3: No Access
token = db.Column(db.String(32), nullable=False, default=generate_key)
date = db.Column(db.BigInteger, nullable=False,
default=int(time.time()*1000))
def database():
final = {'values': []}
final['languages'] = [f.name for f in Language.query.all()]
final['phonemes'] = [f.name for f in Phoneme.query.all()]
for language in Language.query.all():
languageobject = {'id': language.id,
'name': language.name,
'source': language.source,
'phonemes': {}}
for frequency in language.phonemes:
languageobject['phonemes'][frequency.phoneme.name] = frequency.value
final['values'].append(languageobject)
return final
def phoneme_add(info):
"""Add or edit value associated with phoneme."""
# info = {
# language_id: language_id,
# phoneme: phoneme_name,
# value: phoneme_value
# }
phoneme = Phoneme.query.filter_by(name=info['phoneme']).first()
language = Language.query.filter_by(id=info['language_id']).first()
if phoneme:
link = Frequency.query.filter_by(
language_id=language.id,
phoneme_id=phoneme.id).first()
link.value = info['value']
else:
phoneme = Phoneme(name=info['phoneme'])
link = Frequency(value=info['value'])
link.phoneme = phoneme
language.phonemes.append(link)
db.session.add_all([phoneme, link])
def phoneme_remove(info):
"""Remove a phoneme from a language."""
# info = {
# language_id: language_id,
# phoneme_id: phoneme_id
# }
phoneme = Phoneme.query.filter_by(id=info['phoneme_id']).first()
language = Language.query.filter_by(id=info['language_id']).first()
frequency = Frequency.query.filter_by(
phoneme_id=info['phoneme_id'], language_id=info['language_id']).first()
if Frequency.query.filter_by(phoneme_id=info['phoneme_id']).count() == 1:
# Delete phoneme
db.session.delete(phoneme)
language.phonemes = [frequency for frequency in language.phonemes
if frequency.phoneme_id != info['phoneme_id']]
db.session.delete(frequency)
def language_name_edit(info):
"""Edit the name of a Language."""
# info = {
# language_id: language_id,
# language_name: name
# }
language = Language.query.filter_by(id=info['language_id']).first()
language.name = info['language_name']
def language_source_add(info):
"""Add or replace a source"""
# info = {
# language_id: language_id,
# language_source = source
# }
language = Language.query.filter_by(id=info['language_id']).first()
language.source = info['language_source']
patch_functions = {
"phoneme_add": phoneme_add, # Add and edit value
"phoneme_remove": phoneme_remove, # Remove association and/or phoneme
"language_name_edit": language_name_edit, # Change language name
"language_source_add": language_source_add # Add/edit source
}
# Render the client at the default URL # Render the client at the default URL
@app.route("/") @app.route("/")
def initial(): def initial():
return render_template('index.html') return render_template('index.html')
# GET method for files
@app.route("/server/<lang_id>")
def file_return(lang_id):
return Language.query.filter_by(id=lang_id).first().source
# Place for client to communicate with the server # Place for client to communicate with the server
@app.route("/server", methods=["GET", "POST", "PATCH"]) @app.route("/server", methods=["GET", "POST", "PATCH"])
# TODO add more methods
def backend(): def backend():
# GET method returns the latest database # # GET method returns the latest database
if request.method == "GET": # if request.method == "GET":
return jsonify(database) # return jsonify(database())
# POST method appends input to database['values'] # POST method appends input to database['values']
elif request.method == "POST": if request.method == "POST":
newlanguage = request.get_json() received = request.get_json()
newlanguage['id'] = ulid.new().str language = Language(name=received['name'], source=received['source'])
database['values'].append(newlanguage) db.session.add(language)
# Add new phonemes for phoneme, value in received['phonemes'].items():
newphonemes = list(newlanguage[ 'phonemes' ]) with db.session.no_autoflush:
uniquephonemes = list(set(newphonemes) - set(database[ 'phonemes' ])) search = Phoneme.query.filter_by(name=phoneme).first()
database['phonemes'] = database['phonemes'] + uniquephonemes if not search:
search = Phoneme(name=phoneme)
# Add new language db.session.add(search)
newlangname = {newlanguage['name']} link = Frequency(value=value, phoneme=search)
uniquelanguages = list(newlangname - set(database['languages'])) language.phonemes.append(link)
database['languages'] = database['languages'] + uniquelanguages db.session.add(link)
db.session.commit()
saveDatabase() # return jsonify(database())
return jsonify(database)
# PATCH method inputs edited language and returns updated database # PATCH method inputs edited language and returns updated database
elif request.method == "PATCH": elif request.method == "PATCH":
newlanguage = request.get_json() received = request.get_json()
database['values'] = [newlanguage if language['id'] == newlanguage['id'] else language for language in database['values']] patch_functions[received['action']](received['data'])
saveDatabase() db.session.commit()
return jsonify(database)
return jsonify(database())
# Manipulate Updates
@app.route("/updates", methods=["GET", "POST", "PATCH"])
def updates():
if request.method == "POST":
received = request.get_json()
update = Update(author=received['author'],
title=received['title'],
content=received['content'])
db.session.add(update)
elif request.method == "PATCH":
received = request.get_json()
update = Update.query.filter_by(id=received['id']).first()
update.name = received['author']
update.title = received['title']
update.content = received['content']
db.session.commit()
return jsonify([{"author": update.name,
"id": update.id,
"title": update.title,
"content": update.content,
"date": update.date}
for update in Update.query.all()])
else:
return
if __name__ == "__main__": if __name__ == "__main__":
app.run(host="0.0.0.0") app.run(host="0.0.0.0")

View File

@ -203,11 +203,6 @@ a {
grid-gap: 2vh; grid-gap: 2vh;
} }
#dataValues {
grid-template-columns: 1fr 1fr;
grid-template-rows: 2fr 5fr 2fr;
}
#home { #home {
display: block; display: block;
@ -265,6 +260,25 @@ a {
grid-row: 2; grid-row: 2;
} }
.row {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 100%;
grid-gap: 2vh;
}
#dataValues > div {
margin-bottom: 2vh;
}
#dataValues .row {
height: 20%;
}
#dataValues #dataTable {
height: 42%;
}
#langSelect { #langSelect {
grid-column: 1; grid-column: 1;
grid-row: 1; grid-row: 1;
@ -288,11 +302,6 @@ a {
margin: auto 0 auto 4vh; margin: auto 0 auto 4vh;
} }
#dataTable {
grid-column: 1 / 3;
grid-row: 2;
}
#dataTableCont { #dataTableCont {
display: grid; display: grid;
font-weight: 300; font-weight: 300;
@ -313,12 +322,21 @@ a {
padding: 0 5% 0 5%; padding: 0 5% 0 5%;
} }
#dataGraph1 { #dataTableCont input {
grid-column: 1; border: 1px solid rgba(0,0,0,0.3);
grid-row: 3; font-size: 100%;
width: 60%;
padding: 0;
font-family: 'Saira Condensed', sans-serif;
font-weight: 300;
} }
#dataGraph2 { #langGraph {
grid-column: 2; display: block;
grid-row: 3; }
#langGraph canvas {
grid-column: 1;
grid-row: 1;
} }

View File

@ -1,6 +1,8 @@
var navSelect = "home"; var navSelect = "home";
var serverURL = window.location.origin; var serverURL = window.location.origin;
var data; var data;
var languageChart;
var dataOpen = false;
// var trelloInfo = {}; // var trelloInfo = {};
@ -22,6 +24,28 @@ var dropOp = {
var dropOpStore = {}; var dropOpStore = {};
// Left This so that the post function can be reused
// function temporary(data) {
// for(var i = 0; i < data.length; i++) {
// $.ajax({
// url: serverURL + '/server',
// type: 'POST',
// data: JSON.stringify(data[i]),
// dataType: "json",
// contentType: 'application/json;charset=UTF-8'
// })
// .then(
// function success(data) {
// console.log(data);
// },
// function error(e) {
// console.log(e);
// }
// );
// }
// }
function createNav() { function createNav() {
for (var i = 0; i < navi.length; i++) { // Create navigation tabs. for (var i = 0; i < navi.length; i++) { // Create navigation tabs.
var side = document.getElementById("sidebar"); var side = document.getElementById("sidebar");
@ -50,11 +74,7 @@ function updateMain(op) { // Updates the actual page.
setTimeout(function() { setTimeout(function() {
console.log(op); console.log(op);
document.getElementById(navSelect).style.display = "none"; document.getElementById(navSelect).style.display = "none";
if(op === "home") { document.getElementById(op).style.display = "block";
document.getElementById(op).style.display = "block";
} else {
document.getElementById(op).style.display = "grid";
}
setTimeout(function() { setTimeout(function() {
document.getElementById(op).style.opacity = "1"; document.getElementById(op).style.opacity = "1";
}, 30); }, 30);
@ -113,8 +133,10 @@ function generateDropOp() { // For options that change based on data.
var langInfo = language(dropOpStore["langSelect"]); var langInfo = language(dropOpStore["langSelect"]);
var info = document.getElementById("langInfoCont"); var info = document.getElementById("langInfoCont");
var dataBox = document.getElementById("dataTableCont"); var dataBox = document.getElementById("dataTableCont");
var graph = document.querySelectorAll("#langGraph > canvas")[0];
info.style.opacity = "0"; info.style.opacity = "0";
dataBox.style.opacity = "0"; dataBox.style.opacity = "0";
graph.style.opacity = "0";
setTimeout(function() { setTimeout(function() {
while (info.firstChild) { while (info.firstChild) {
info.removeChild(info.firstChild); info.removeChild(info.firstChild);
@ -124,16 +146,17 @@ function generateDropOp() { // For options that change based on data.
var a = document.createElement("a"); var a = document.createElement("a");
p.appendChild(document.createTextNode("Type: " + (langInfo.type || "N/A"))); p.appendChild(document.createTextNode("Type: " + (langInfo.type || "N/A")));
p2.appendChild(document.createTextNode("Source: ")); p2.appendChild(document.createTextNode("Source: "));
if(langInfo.source.length > 0) { if(langInfo.source === null) {
p2.appendChild(document.createTextNode("N/A"));
} else if(langInfo.source.length > 0) {
a.href = langInfo.source; a.href = langInfo.source;
srcText = (langInfo.source.length > 60) ? langInfo.source.substring(0, 57) + "..." : langInfo.source; srcText = (langInfo.source.length > 60) ? langInfo.source.substring(0, 57) + "..." : langInfo.source;
a.appendChild(document.createTextNode(srcText)); a.appendChild(document.createTextNode(srcText));
p2.appendChild(a); p2.appendChild(a);
} else {
p2.appendChild(document.createTextNode("N/A"));
} }
info.appendChild(p); info.appendChild(p);
info.appendChild(p2); info.appendChild(p2);
// Generate data box material. // Generate data box material.
var phonemes = Object.keys(langInfo.phonemes); var phonemes = Object.keys(langInfo.phonemes);
@ -165,16 +188,134 @@ function generateDropOp() { // For options that change based on data.
dataBox.children[tableNum].appendChild(pT2); dataBox.children[tableNum].appendChild(pT2);
} }
p1.appendChild(document.createTextNode(phonemes[i])); p1.appendChild(document.createTextNode(phonemes[i]));
p2.appendChild(document.createTextNode(langInfo.phonemes[phonemes[i]])); p2.appendChild(document.createTextNode(langInfo.phonemes[phonemes[i]]));
p2.className = "dataEdit";
p2.onclick = function(event) {
if(this === event.target) return;
closeEditInput();
dataOpen = true;
var input = document.createElement("input");
var value = this.childNodes[0].nodeValue;
this.removeChild(this.childNodes[0]);
this.appendChild(input);
input.value = value;
input.id = "dataOpen";
input.focus();
}
dataBox.children[tableNum].appendChild(p1); dataBox.children[tableNum].appendChild(p1);
dataBox.children[tableNum].appendChild(p2); dataBox.children[tableNum].appendChild(p2);
} }
var graphData = Object.entries(langInfo.phonemes).sort(function(a,b) {
return b[1] - a[1];
});
graphData = [graphData.map(function(a,b) {
return a[0];
}), graphData.map(function(a,b) {
return a[1];
})];
// Generate graphs.
var ctx = graph.getContext("2d");
try {
languageChart.destroy();
} catch(err) {}
languageChart = new Chart(ctx, {
type: 'bar',
data: {
labels: graphData[0],
datasets: [{
label: "Phoneme Prevalence",
data: graphData[1],
backgroundColor: 'rgba(244, 121, 34, 0.7)',
borderColor: 'rgba(246, 112, 18, 1)',
borderWidth: 2
}]
},
options: {
legend: {
labels: {
fontFamily: "'Open Sans Condensed', sans-serif",
fontSize: 20
}
},
scales: {
yAxes: [{
scaleLabel: {
display: true,
labelString: "Phoneme (%)",
fontFamily: "'Open Sans Condensed', sans-serif",
fontSize: 20,
padding: 4
},
ticks: {
fontFamily: "'Open Sans Condensed', sans-serif",
fontSize: 20,
callback: function(value) {
return value + "%";
}
}
}],
xAxes: [{
scaleLabel: {
display: true,
labelString: "Percent Prevalence",
fontFamily: "'Open Sans Condensed', sans-serif",
fontSize: 20,
padding: 4
},
ticks: {
fontFamily: "'Open Sans Condensed', sans-serif",
fontSize: 20
}
}],
}
}
});
info.style.opacity = "1"; info.style.opacity = "1";
dataBox.style.opacity = "1"; dataBox.style.opacity = "1";
graph.style.opacity = "1";
}, 300); }, 300);
}].concat(["Select language..."].concat(data.languages)); }].concat(["Select language..."].concat(data.languages));
} }
function closeEditInput() {
try {
var input = document.getElementById("dataOpen");
var p = input.parentNode;
var patchData = {
action: 'phoneme_add',
data: {
language_id: language(dropOpStore["langSelect"]).id,
phoneme: p.previousSibling.innerText,
value: input.value
}
};
$.ajax({
url: serverURL + '/server',
type: 'PATCH',
dataType: "json",
contentType: 'application/json;charset=UTF-8',
data: JSON.stringify(patchData)
})
.then(
function success(incoming) {
p.appendChild(document.createTextNode(input.value));
p.removeChild(input);
},
function error(e) {
console.log(e);
}
);
dataOpen = false;
} catch(err) {}
}
document.addEventListener("click", function(event) {
if(event.target.className !== "dataEdit") {
closeEditInput();
}
});
function createDrop() { function createDrop() {
var dropButtons = document.getElementsByClassName("dropdown"); var dropButtons = document.getElementsByClassName("dropdown");
for (var i = 0; i < dropButtons.length; i++) { for (var i = 0; i < dropButtons.length; i++) {

View File

@ -27,14 +27,16 @@
<div id="home" class="optionContainer"> <div id="home" class="optionContainer">
</div> </div>
<div id="dataValues" class="optionContainer"> <div id="dataValues" class="optionContainer">
<div id="langSelect" class="card"> <div class="row">
<h2>Language</h2> <div id="langSelect" class="card">
<div option="langSelect" class="dropdown transition"> <h2>Language</h2>
<div option="langSelect" class="dropdown transition">
</div>
</div> </div>
</div> <div id="dataInfo" class="card">
<div id="dataInfo" class="card"> <h2>Info</h2>
<h2>Info</h2> <div id="langInfoCont" class="transition">
<div id="langInfoCont" class="transition"> </div>
</div> </div>
</div> </div>
<div id="dataTable" class="card"> <div id="dataTable" class="card">
@ -42,8 +44,9 @@
<div id="dataTableCont" class="transition"> <div id="dataTableCont" class="transition">
</div> </div>
</div> </div>
<canvas id="dataGraph1" class="card"></canvas> <div id="langGraph" class="card">
<canvas id="dataGraph2" class="card"></canvas> <canvas class="transition"></canvas>
</div>
</div> </div>
<div id="files" class="optionContainer"> <div id="files" class="optionContainer">
<div class="temp card"> <div class="temp card">