main branch
parent
1c7b0deea3
commit
f9caed169b
@ -1,5 +0,0 @@
|
|||||||
TODO: comment the code
|
|
||||||
|
|
||||||
TODO: remove new from homepage?
|
|
||||||
or
|
|
||||||
TODO: add per-new level in the xquisite branches (this makes more sense!)
|
|
@ -1,52 +0,0 @@
|
|||||||
|
|
||||||
.container{
|
|
||||||
display: block;
|
|
||||||
padding: 2000px;
|
|
||||||
}
|
|
||||||
.streams {
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
position: relative;
|
|
||||||
width: 500px;
|
|
||||||
height: 500px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stream {
|
|
||||||
white-space: nowrap;
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
width: 500px;
|
|
||||||
height: 500px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svg-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.author {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
bottom: 50px;
|
|
||||||
font-size: 1rem;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.branches {
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.branch {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.branch svg {
|
|
||||||
border-top: 1px solid currentColor;
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
svg {
|
|
||||||
width: 500px;
|
|
||||||
height: 500px;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
#svg-container {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#previous {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
transform: translateX(-100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#previous svg {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#previous::after {
|
|
||||||
content: "";
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
|
||||||
position: absolute;
|
|
||||||
transform: translateX(-25%);
|
|
||||||
background-color: var(--background);
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
:root {
|
:root {
|
||||||
--background: #edd;
|
--background: #2a9d8f;
|
||||||
--color: black;
|
--color: black;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
@ -1,114 +0,0 @@
|
|||||||
// 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", "currentColor");
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
svgElement.addEventListener("mouseleave", 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 form = document.querySelector("form");
|
|
||||||
|
|
||||||
form.addEventListener("submit", () => {
|
|
||||||
let wrapper = document.createElement("div");
|
|
||||||
wrapper.appendChild(svgElement);
|
|
||||||
form["content"].value = wrapper.innerHTML;
|
|
||||||
return true;
|
|
||||||
});
|
|
@ -1,49 +1,44 @@
|
|||||||
{%extends 'base.html' %} {%block head %}
|
<%inherit file="base_mako.html" />
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{url_for('static', filename='css/display.css')}}" />
|
|
||||||
<title>Display</title>
|
<%block name="head">
|
||||||
{%endblock%} {%block nav%}
|
<link rel="stylesheet" href="${url_for('static', filename='css/display.css')}">
|
||||||
<a href="{{url_for('home.home')}}">Home</a>
|
</%block>
|
||||||
<a href="{{url_for('draw.last')}}">Draw</a>
|
|
||||||
|
<header>
|
||||||
{%endblock%} {%block contents%}
|
<h1>Exquisite Excerpts</h1>
|
||||||
|
<p>Here texts branch and follow their own path. Click on an excerpt to create a new branch and continue toward new meanings. </p>
|
||||||
<h1>Exquisite Branch</h1>
|
</header>
|
||||||
|
|
||||||
<div class="streams">
|
<main class="container">
|
||||||
{% for stream in streams %}
|
|
||||||
<div
|
<% from random import random %>
|
||||||
class="stream"
|
<% from collections import defaultdict %>
|
||||||
style="color: rgb({{ range(0, 255) | random }},{{ range(0, 255) | random }},{{ range(0, 255) | random }}); transform: translate({{ range(-5, 5) | random }}px, {{ range(-5, 5) | random }}px)"
|
<% transform = {'NEW': ''} %>
|
||||||
>
|
<% visited = defaultdict(int) %>
|
||||||
{% for branch in stream %}
|
|
||||||
<div class="svg-container">
|
<div class="stream">
|
||||||
{{branch['content'] | safe}}
|
|
||||||
<span
|
% for branch in branches:
|
||||||
class="author"
|
|
||||||
style="transform: translate({{ range(-50, 50) | random }}px, {{ range(-10, 10) | random }}px"
|
<% visited[branch['parent']] += 1 %>
|
||||||
>
|
|
||||||
{{ branch['username']}}
|
|
||||||
</span>
|
% if visited[branch['parent']] > 1:
|
||||||
</div>
|
<% steer = (random() - 0.5) * 0.25 %>
|
||||||
{%endfor%}
|
% else:
|
||||||
</div>
|
<% steer = 0 %>
|
||||||
{%endfor%}
|
% endif
|
||||||
</div>
|
|
||||||
|
<% transform[branch['branch']] = f'{transform[branch["parent"]]} rotate({random() * 0.04 + steer}turn) translateX(100%)' %>
|
||||||
{{colors}}
|
|
||||||
|
<div class="text-container" style="transform: ${transform[branch['parent']]}">
|
||||||
<h2>Branches</h2>
|
<a href="${url_for('write.write', parent=branch['branch'], _external=True, _scheme='http')}" target="__blank">
|
||||||
<div class="branches">
|
${branch['content']}
|
||||||
{% for stream in streams %}
|
</a>
|
||||||
<div class="branch">
|
<span class="author">${branch['username']}</span>
|
||||||
{% for branch in stream %}
|
|
||||||
<div class="svg-container">
|
|
||||||
{{branch['content'] | safe}}
|
|
||||||
<span class="author"> {{ branch['username']}} </span>
|
|
||||||
</div>
|
</div>
|
||||||
{%endfor%}
|
|
||||||
|
% endfor
|
||||||
</div>
|
</div>
|
||||||
{%endfor%}
|
</main>
|
||||||
</div>
|
|
||||||
{%endblock%}
|
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<%inherit file="base_mako.html" />
|
|
||||||
|
|
||||||
|
|
||||||
<%block name="head">
|
|
||||||
<link rel="stylesheet" href="${url_for('static', filename='css/display_mako.css')}">
|
|
||||||
</%block>
|
|
||||||
|
|
||||||
|
|
||||||
<main class="container">
|
|
||||||
|
|
||||||
|
|
||||||
<% from random import random %>
|
|
||||||
<% from collections import defaultdict %>
|
|
||||||
<% transform = {'NEW': ''} %>
|
|
||||||
<% visited = defaultdict(int) %>
|
|
||||||
|
|
||||||
<div class="stream">
|
|
||||||
|
|
||||||
% for branch in branches:
|
|
||||||
|
|
||||||
|
|
||||||
<% visited[branch['parent']] += 1 %>
|
|
||||||
|
|
||||||
|
|
||||||
% if visited[branch['parent']] > 1:
|
|
||||||
<% steer = (random() - 0.5) * 0.25 %>
|
|
||||||
% else:
|
|
||||||
<% steer = 0 %>
|
|
||||||
% endif
|
|
||||||
|
|
||||||
|
|
||||||
<% transform[branch['branch']] = f'{transform[branch["parent"]]} rotate({random() * 0.04 + steer}turn) translateX(100%)' %>
|
|
||||||
|
|
||||||
<div class="svg-container" style="transform: ${transform[branch['parent']]}">
|
|
||||||
<a href="${url_for('draw.draw', parent=branch['branch'], _external=True, _scheme='https')}" target="__blank">
|
|
||||||
${branch['content']}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
% endfor
|
|
||||||
</div>
|
|
||||||
</main>
|
|
@ -1,21 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<%inherit file="base_mako.html" />
|
|
||||||
|
|
||||||
<%block name="head">
|
|
||||||
<link rel="stylesheet" href="${url_for('static', filename='css/display_mako.css')}">
|
|
||||||
</%block>
|
|
||||||
|
|
||||||
<% from random import random %>
|
|
||||||
<% offset = 1 / (len(streams) - 1)%>
|
|
||||||
% for stream in streams:
|
|
||||||
<div class="stream">
|
|
||||||
<% transform = f'rotate({offset * loop.index}turn) translateX(100%) ' %>
|
|
||||||
% for branch in stream:
|
|
||||||
<% transform = transform + ' rotate(' + str((random() * 2 - 1) * 0.02) + 'turn) translateX(100%)'%>
|
|
||||||
<div class="svg-container" style="transform: ${transform}">${branch['content']}</div>
|
|
||||||
% endfor
|
|
||||||
</div>
|
|
||||||
% endfor
|
|
@ -1,51 +0,0 @@
|
|||||||
{%extends 'base.html' %} {%block head %}
|
|
||||||
<title>Draw</title>
|
|
||||||
<link rel="stylesheet" href="{{url_for('static', filename='css/draw.css')}}" />
|
|
||||||
<script src="{{url_for('static', filename='js/draw.js')}}" defer></script>
|
|
||||||
|
|
||||||
{%endblock%} {%block nav%}
|
|
||||||
<a href="{{url_for('home.home')}}">Home</a>
|
|
||||||
<a href="{{url_for('display.display')}}">Results</a>
|
|
||||||
|
|
||||||
{%endblock%} {%block contents%}
|
|
||||||
|
|
||||||
<h1>Draw</h1>
|
|
||||||
|
|
||||||
<div id="divSmoothingFactor">
|
|
||||||
<label for="cmbBufferSize">Smoothing</label>
|
|
||||||
<select id="cmbBufferSize">
|
|
||||||
<option value="1">1 - No smoothing</option>
|
|
||||||
<option value="4">4 - Sharp curves</option>
|
|
||||||
<option value="8" selected="selected">8 - Smooth curves</option>
|
|
||||||
<option value="12">12 - Very smooth curves</option>
|
|
||||||
<option value="16">16 - Super smooth curves</option>
|
|
||||||
<option value="20">20 - Hyper smooth curves</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="svg-container">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
version="1.1"
|
|
||||||
id="svgElement"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
width="500px"
|
|
||||||
height="500px"
|
|
||||||
viewBox="0 0 500 500"
|
|
||||||
enable-background="new 0 0 500 500"
|
|
||||||
xml:space="preserve"
|
|
||||||
data-parent="{{parent or None}}"
|
|
||||||
data-branch="{{branch}}"
|
|
||||||
></svg>
|
|
||||||
<div id="previous">{% if content %} {{content|safe}} {% endif %}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="POST">
|
|
||||||
<input type="hidden" name="branch" value="{{branch}}" />
|
|
||||||
<input type="hidden" name="content" />
|
|
||||||
<input type="text" name="username" placeholder="Name" />
|
|
||||||
<input type="submit" />
|
|
||||||
</form>
|
|
||||||
{%endblock%}
|
|
@ -0,0 +1,27 @@
|
|||||||
|
{%extends 'base.html' %} {%block head %}
|
||||||
|
<title>Draw</title>
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='css/write.css')}}" />
|
||||||
|
<script src="{{url_for('static', filename='js/draw.js')}}" defer></script>
|
||||||
|
|
||||||
|
{%endblock%} {%block nav%}
|
||||||
|
<a href="{{url_for('home.home')}}">Home</a>
|
||||||
|
<a href="{{url_for('display.display')}}">Results</a>
|
||||||
|
|
||||||
|
{%endblock%} {%block contents%}
|
||||||
|
|
||||||
|
<!-- mmm i dont remember what i was using these two things for! -->
|
||||||
|
<!-- data-parent="{{parent or None}}" -->
|
||||||
|
<!-- data-branch="{{branch}}" -->
|
||||||
|
<!-- Maybe to create links in the display page? -->
|
||||||
|
|
||||||
|
<h1>Write</h1>
|
||||||
|
<div id="previous">{% if content %} {{content|safe}} {% endif %}</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="branch" value="{{branch}}" />
|
||||||
|
<textarea name="content" ></textarea>
|
||||||
|
<input type="text" name="username" placeholder="Name" />
|
||||||
|
<input type="submit" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{%endblock%}
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 180 KiB |
@ -0,0 +1,61 @@
|
|||||||
|
# Exquisite Excerpts
|
||||||
|
|
||||||
|
A branching version of the [exquisite corpse](https://en.wikipedia.org/wiki/Exquisite_corpse) game, forked from the [exquisite branch](https://git.xpub.nl/kamo/exquisite-branch) drawing app developed for [SI17](https://issue.xpub.nl/17/).
|
||||||
|
|
||||||
|
Write something, upload it and send the link to someone else: they will continue from your excerpt. With a catch: if you send to just one person the chain will continue linearly, but send it to more people and things will start branching in different directions.
|
||||||
|
|
||||||
|
Could be a writing machine to work on the collective pubblication for the graduation, inspired by what [Kim wrote here](https://pad.xpub.nl/p/gradcollectivexpub).
|
||||||
|
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Clone the repository.
|
||||||
|
```
|
||||||
|
git clone https://git.xpub.nl/kamo/exex.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a virtual environment.
|
||||||
|
```
|
||||||
|
python3 -m venv venv
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the requirements with `pip` and the `requirements.txt` file.
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Create an `.env` file in the root folder of the project with the following variables:
|
||||||
|
```
|
||||||
|
DEBUG=True
|
||||||
|
FLASK_ENV=development
|
||||||
|
FLASK_APP=exquisite_branch
|
||||||
|
```
|
||||||
|
|
||||||
|
Before running the app for the first time, be sure to initialize the database. __Watch out:__ this will delete the previous instance of the database along with its contents!
|
||||||
|
```
|
||||||
|
flask init-db
|
||||||
|
```
|
||||||
|
After initializing the database you can run the flask application
|
||||||
|
```
|
||||||
|
flask run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Exex saves contents in a database, and join them together in a branching version of the exquisite corpse.
|
||||||
|
|
||||||
|
The original exquisite corpse data structure is something similar to a linked list, where every drawing is connected to the previous one. Contents in _exex_ are saved in a database with the same principle.
|
||||||
|
|
||||||
|
![Diagram with the situationship of the entries](linked.svg)
|
||||||
|
|
||||||
|
Each entry in the database has the following properties:
|
||||||
|
|
||||||
|
- `id`: a unique identifier for every entry
|
||||||
|
- `branch`: a random name for the excerpt
|
||||||
|
- `parent`: the random name of the previous excerpt
|
||||||
|
- `content`: the actual content of the writings
|
||||||
|
- `username`: the author of the excerpt
|
||||||
|
|
||||||
|
When generating the display page, every entry look up its `parent` property to position itself after that.
|
||||||
|
|
||||||
|
Mhh should rewrite this better bc is super convoluted ahah.
|
Loading…
Reference in New Issue