commit 3719f2608246f45315dfba142e10a52207aa2893 Author: Francesco Luzzana Date: Thu Feb 17 16:51:21 2022 +0100 init repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..148ff97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +venv/ + +*.pyc +__pycache__/ + +instance/ + +.pytest_cache/ +.coverage +htmlcov/ + +dist/ +build/ +*.egg-info/ \ No newline at end of file diff --git a/exquisite_branch/__init__.py b/exquisite_branch/__init__.py new file mode 100644 index 0000000..8d38243 --- /dev/null +++ b/exquisite_branch/__init__.py @@ -0,0 +1,41 @@ +import os +from flask import Flask + + +def create_app(test_config=None): + # create and configure the app + app = Flask(__name__, instance_relative_config=True) + app.config.from_mapping( + SECRET_KEY='dev', + DATABASE=os.path.join(app.instance_path, 'exquisite'), + ) + + if test_config is None: + # load the instance config, if it exists, when not testing + app.config.from_pyfile('config.py', silent=True) + else: + # load the test config if passed in + app.config.from_mapping(test_config) + + # ensure the instance folder exists + try: + os.makedirs(app.instance_path) + except OSError: + pass + + from . import db + db.init_app(app) + + from . import draw + app.register_blueprint(draw.bp) + + from . import share + app.register_blueprint(share.bp) + + from . import display + app.register_blueprint(display.bp) + + from . import home + app.register_blueprint(home.bp) + + return app diff --git a/exquisite_branch/db.py b/exquisite_branch/db.py new file mode 100644 index 0000000..ef6e138 --- /dev/null +++ b/exquisite_branch/db.py @@ -0,0 +1,43 @@ +import sqlite3 + +import click +from flask import current_app, g +from flask.cli import with_appcontext + + +def get_db(): + if 'db' not in g: + g.db = sqlite3.connect( + current_app.config['DATABASE'], + detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + + return g.db + + +def close_db(e=None): + db = g.pop('db', None) + + if db is not None: + db.close() + + +def init_db(): + db = get_db() + + with current_app.open_resource('schema.sql') as f: + db.executescript(f.read().decode('utf8')) + + +@click.command('init-db') +@with_appcontext +def init_db_command(): + """Clear the existing data and create new tables.""" + init_db() + click.echo('Initialized the database.') + + +def init_app(app): + app.teardown_appcontext(close_db) + app.cli.add_command(init_db_command) diff --git a/exquisite_branch/display.py b/exquisite_branch/display.py new file mode 100644 index 0000000..7216fd8 --- /dev/null +++ b/exquisite_branch/display.py @@ -0,0 +1,35 @@ +from flask import (Blueprint, flash, g, redirect, + render_template, request, session, url_for) + +from exquisite_branch.db import get_db + + +bp = Blueprint('display', __name__, url_prefix='/display') + + +@bp.route('/') +def display(): + db = get_db() + + branches = db.execute( + "SELECT content, branch, parent FROM branches" + ).fetchall() + + streams = [] + for branch in branches[::-1]: + if branch not in flatten(streams): + stream = [branch] + parent = branch['parent'] + while parent != 'NEW': + current = next( + (x for x in branches if x['branch'] == parent), None) + parent = current['parent'] + stream.append(current) + + streams.append(stream[::-1]) + + return render_template('display.html', branches=branches, streams=streams) + + +def flatten(t): + return [item for sublist in t for item in sublist] diff --git a/exquisite_branch/draw.py b/exquisite_branch/draw.py new file mode 100644 index 0000000..2f9e495 --- /dev/null +++ b/exquisite_branch/draw.py @@ -0,0 +1,85 @@ +from flask import (Blueprint, flash, g, redirect, + render_template, request, session, url_for) + +from exquisite_branch.db import get_db +from werkzeug.exceptions import abort + +from shortuuid import uuid + +bp = Blueprint('draw', __name__, url_prefix='/draw') + + +@bp.route('/', methods=('GET', 'POST')) +def draw(parent=None): + db = get_db() + + if request.method == 'POST': + content = request.form.get('content') + branch = request.form.get('branch') + + print(content) + + db.execute( + 'INSERT INTO branches (content, parent, branch) VALUES (?, ?, ?)', + (content, parent, branch,) + ) + db.commit() + return redirect(url_for('share.share', branch=branch)) + + branch = uuid() + + previous = db.execute( + "SELECT content, branch, parent FROM branches" + " WHERE branch = ?", + (parent,) + ).fetchone() + + if previous is None: + abort(404, f"Previous with id {parent} doesn't exist") + + return render_template('draw.html', parent=parent, content=previous['content'], branch=branch) + + +@bp.route('/last', methods=('GET', 'POST')) +def last(): + + branch = uuid() + db = get_db() + previous = db.execute( + 'SELECT * FROM branches ORDER BY id DESC LIMIT 1' + ).fetchone() + + parent = previous['branch'] + + if request.method == 'POST': + content = request.form['content'] + branch = request.form['branch'] + + db.execute( + 'INSERT INTO branches (content, parent, branch) VALUES (?, ?, ?)', + (content, parent, branch,) + ) + db.commit() + return redirect(url_for('share.share', branch=branch)) + + return render_template('draw.html', parent=parent, content=previous['content'], branch=branch) + + +@bp.route('/', methods=('GET', 'POST')) +def new(): + db = get_db() + branch = uuid() + parent = 'NEW' + + if request.method == 'POST': + content = request.form['content'] + branch = request.form['branch'] + + db.execute( + 'INSERT INTO branches (content, parent, branch) VALUES (?, ?, ?)', + (content, parent, branch,) + ) + db.commit() + return redirect(url_for('share.share', branch=branch)) + + return render_template('draw.html', parent=parent, branch=branch) diff --git a/exquisite_branch/home.py b/exquisite_branch/home.py new file mode 100644 index 0000000..a968c1f --- /dev/null +++ b/exquisite_branch/home.py @@ -0,0 +1,7 @@ +from flask import (Blueprint, render_template) +bp = Blueprint('home', __name__, url_prefix='/') + + +@bp.route('/') +def home(): + return render_template('home.html') diff --git a/exquisite_branch/schema.sql b/exquisite_branch/schema.sql new file mode 100644 index 0000000..bfff171 --- /dev/null +++ b/exquisite_branch/schema.sql @@ -0,0 +1,8 @@ +DROP TABLE IF EXISTS branches; + +CREATE TABLE branches ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + branch TEXT NOT NULL, + content TEXT NOT NULL, + parent TEXT NOT NULL +); \ No newline at end of file diff --git a/exquisite_branch/share.py b/exquisite_branch/share.py new file mode 100644 index 0000000..f0a24b0 --- /dev/null +++ b/exquisite_branch/share.py @@ -0,0 +1,10 @@ +from flask import (Blueprint, flash, g, redirect, + render_template, request, session, url_for) + + +bp = Blueprint('share', __name__, url_prefix='/share') + + +@bp.route('/') +def share(branch=None): + return render_template('share.html', branch=branch) diff --git a/exquisite_branch/static/css/draw.css b/exquisite_branch/static/css/draw.css new file mode 100644 index 0000000..b48e961 --- /dev/null +++ b/exquisite_branch/static/css/draw.css @@ -0,0 +1,14 @@ +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; +} + +canvas { + width: 500px; + height: 500px; + border: 1px solid currentColor; +} diff --git a/exquisite_branch/static/js/draw.js b/exquisite_branch/static/js/draw.js new file mode 100644 index 0000000..133d6bb --- /dev/null +++ b/exquisite_branch/static/js/draw.js @@ -0,0 +1,116 @@ +// Great resource from https://stackoverflow.com/a/40700068 +// Thank you ConnorFan + +var strokeWidth = 2; +var bufferSize; + +var svgElement = document.getElementById("svgElement"); +var rect = svgElement.getBoundingClientRect(); +var path = null; +var strPath; +var buffer = []; // Contains the last positions of the mouse cursor + +svgElement.addEventListener("mousedown", function (e) { + bufferSize = document.getElementById("cmbBufferSize").value; + path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + path.setAttribute("fill", "none"); + path.setAttribute("stroke", "#000"); + path.setAttribute("stroke-width", strokeWidth); + buffer = []; + var pt = getMousePosition(e); + appendToBuffer(pt); + strPath = "M" + pt.x + " " + pt.y; + path.setAttribute("d", strPath); + svgElement.appendChild(path); +}); + +svgElement.addEventListener("mousemove", function (e) { + if (path) { + appendToBuffer(getMousePosition(e)); + updateSvgPath(); + } +}); + +svgElement.addEventListener("mouseup", function () { + if (path) { + path = null; + } +}); + +var getMousePosition = function (e) { + return { + x: e.pageX - rect.left, + y: e.pageY - rect.top, + }; +}; + +var appendToBuffer = function (pt) { + buffer.push(pt); + while (buffer.length > bufferSize) { + buffer.shift(); + } +}; + +// Calculate the average point, starting at offset in the buffer +var getAveragePoint = function (offset) { + var len = buffer.length; + if (len % 2 === 1 || len >= bufferSize) { + var totalX = 0; + var totalY = 0; + var pt, i; + var count = 0; + for (i = offset; i < len; i++) { + count++; + pt = buffer[i]; + totalX += pt.x; + totalY += pt.y; + } + return { + x: totalX / count, + y: totalY / count, + }; + } + return null; +}; + +var updateSvgPath = function () { + var pt = getAveragePoint(0); + + if (pt) { + // Get the smoothed part of the path that will not change + strPath += " L" + pt.x + " " + pt.y; + + // Get the last part of the path (close to the current mouse position) + // This part will change if the mouse moves again + var tmpPath = ""; + for (var offset = 2; offset < buffer.length; offset += 2) { + pt = getAveragePoint(offset); + tmpPath += " L" + pt.x + " " + pt.y; + } + + // Set the complete current path coordinates + path.setAttribute("d", strPath + tmpPath); + } +}; + +// +// +// +// SAVE THE BRANCH + +const send = document.getElementById("send"); +send.addEventListener("click", (e) => saveSVG(e)); + +function saveSVG(e) { + let wrapper = document.createElement("div"); + wrapper.appendChild(svgElement); + + fetch(`${svgElement.dataset.parent}`, { + method: "POST", + body: JSON.stringify({ + content: wrapper.innerHTML, + parent: svgElement.dataset.parent, + branch: svgElement.dataset.branch, + }), + }); +} diff --git a/exquisite_branch/static/test.svg b/exquisite_branch/static/test.svg new file mode 100644 index 0000000..2804f96 --- /dev/null +++ b/exquisite_branch/static/test.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/exquisite_branch/templates/display.html b/exquisite_branch/templates/display.html new file mode 100644 index 0000000..06b1815 --- /dev/null +++ b/exquisite_branch/templates/display.html @@ -0,0 +1,26 @@ + + + + + + + Display + + +

Exquisite Branch

+

Entries

+ {% for branch in branches %} {{branch['content']}} {%endfor%} +

Branches

+
    + {% for stream in streams %} + +
  • + {% for branch in stream %} + {{branch['content']}} + {%endfor%} +
  • + + {%endfor%} +
+ + diff --git a/exquisite_branch/templates/draw.html b/exquisite_branch/templates/draw.html new file mode 100644 index 0000000..bbe8d88 --- /dev/null +++ b/exquisite_branch/templates/draw.html @@ -0,0 +1,62 @@ + + + + + + + Draw + + + + + + +

Draw

+
+ + +
+ + + + + +

Previous content

+ + {% if content %} {{content}} {% endif %} + +

Parent

+ + {%if parent %} {{parent}} {%endif %} + +

Branch

+ {%if branch %} {{branch}} {%endif %}` + +
+ + + +
+ + diff --git a/exquisite_branch/templates/home.html b/exquisite_branch/templates/home.html new file mode 100644 index 0000000..648e6bc --- /dev/null +++ b/exquisite_branch/templates/home.html @@ -0,0 +1,15 @@ + + + + + + + Exquisite Branch + + +

Exquisite Branch

+ Start new
+ Continue from last
+ Display results + + diff --git a/exquisite_branch/templates/share.html b/exquisite_branch/templates/share.html new file mode 100644 index 0000000..1f21146 --- /dev/null +++ b/exquisite_branch/templates/share.html @@ -0,0 +1,15 @@ + + + + + + + Share + + + Send this link to your friends: + {{url_for('draw.draw', parent=branch)}} + +