Add MVP of SVG image
This commit is contained in:
parent
0b150146b1
commit
0f5ad57aec
268
chatapp.svg
Normal file
268
chatapp.svg
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="300"
|
||||||
|
height="500"
|
||||||
|
viewBox="300 0 300 500"
|
||||||
|
onload="start_chat('Ven')"
|
||||||
|
overflow="auto"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<symbol id="left_chat_message"><text style="text-align: left"></text></symbol>
|
||||||
|
<symbol id="right_chat_message"><text style="text-align: right"></text></symbol>
|
||||||
|
</defs>
|
||||||
|
<script type="application/ecmascript">
|
||||||
|
<![CDATA[
|
||||||
|
const svgns = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
|
// must be the same as the SVG dimensions
|
||||||
|
const width = 300;
|
||||||
|
const height = 500;
|
||||||
|
|
||||||
|
const line_height = 5;
|
||||||
|
const max_visible_lines = 10;
|
||||||
|
const messages_y_offset = 20;
|
||||||
|
const typing_speed = 70;
|
||||||
|
|
||||||
|
var chat_lines_count = 0;
|
||||||
|
var conversation_count = 0;
|
||||||
|
|
||||||
|
/// SVG 1.1 doesn't do proper text splitting into several lines.
|
||||||
|
/// we need to do it ourselves.
|
||||||
|
function split_text_into_lines(text, upper_line_length) {
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
while(text.length) {
|
||||||
|
if(text.length < upper_line_length) {
|
||||||
|
result.push(text);
|
||||||
|
break; // we are done
|
||||||
|
}
|
||||||
|
|
||||||
|
let found_split_point = false;
|
||||||
|
for(let i = upper_line_length; i; i--) {
|
||||||
|
if(text[i] == ' ') {
|
||||||
|
result.push(text.slice(0, i));
|
||||||
|
text = text.slice(i+1);
|
||||||
|
found_split_point = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!found_split_point) {
|
||||||
|
// no <space> found. Split at character boundary instead
|
||||||
|
result.push(text.slice(0, upper_line_length));
|
||||||
|
text = text.slice(upper_line_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A class holding a chat message.
|
||||||
|
///
|
||||||
|
/// ChatMessages's are owned by a Dialog.
|
||||||
|
function ChatMessage(message_text, is_myself) {
|
||||||
|
let lines = split_text_into_lines(message_text, 14);
|
||||||
|
let tspans = [];
|
||||||
|
|
||||||
|
let container = document.getElementById('messages');
|
||||||
|
|
||||||
|
/// Render the chat message on the screen
|
||||||
|
this.draw = function() {
|
||||||
|
let text = document.createElementNS(svgns, 'text');
|
||||||
|
let x = (is_myself)?10 :50;
|
||||||
|
text.setAttribute('x', `${x}%`);
|
||||||
|
|
||||||
|
lines.forEach(function(line) {
|
||||||
|
let y = tspan_y_pos(chat_lines_count++);
|
||||||
|
let tspan = document.createElementNS(svgns, 'tspan');
|
||||||
|
tspan.setAttribute('x', `${x}%`);
|
||||||
|
tspan.setAttribute('y', `${y}%`); // important: y is lower text baseline
|
||||||
|
tspan.setAttribute('z', 10);
|
||||||
|
tspan.appendChild(document.createTextNode(line));
|
||||||
|
tspan.setAttribute('fill', 'green');
|
||||||
|
tspan.setAttribute('width', '5%');
|
||||||
|
|
||||||
|
text.appendChild(tspan);
|
||||||
|
tspans.push(tspan);
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move a chat message on the screen (to simulate scrolling)
|
||||||
|
this.shift_y_pos = function(by, lower_visibility_boundary) {
|
||||||
|
tspans.forEach(tspan => {
|
||||||
|
let is_already_hidden = tspan.getAttribute('visibility') == 'hidden';
|
||||||
|
let old = parseInt(tspan.getAttribute('y').split('%')[0]);
|
||||||
|
let new_value = old + by;
|
||||||
|
tspan.setAttribute('y', `${new_value}%`);
|
||||||
|
|
||||||
|
let visibility;
|
||||||
|
if(new_value < lower_visibility_boundary) {
|
||||||
|
visibility = 'hidden';
|
||||||
|
if(!is_already_hidden) {
|
||||||
|
chat_lines_count--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
visibility = 'visible';
|
||||||
|
if(is_already_hidden) {
|
||||||
|
chat_lines_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tspan.setAttribute('visibility', visibility);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to determine the position of a chat-tspan
|
||||||
|
function tspan_y_pos(message_index) {
|
||||||
|
return messages_y_offset + message_index * line_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Promise-based version of setTimeout
|
||||||
|
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
/// toggle viewport between message list and contact list
|
||||||
|
/// That means: show either the left or right side of the SVG
|
||||||
|
function swipe_viewport() {
|
||||||
|
let svg = document.getElementsByTagName('svg')[0];
|
||||||
|
let coords = svg.getAttribute('viewBox').split(' ');
|
||||||
|
let x = parseInt(coords.shift());
|
||||||
|
if(x == 0) {
|
||||||
|
var step = 5;
|
||||||
|
var end = width;
|
||||||
|
} else {
|
||||||
|
var step = -5;
|
||||||
|
var end = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewBox_suffix = coords.join(' '); // only 3 elements
|
||||||
|
|
||||||
|
async function animate(resolve, reject) {
|
||||||
|
while(x!=end) {
|
||||||
|
await wait(1);
|
||||||
|
x += step;
|
||||||
|
svg.setAttribute('viewBox', `${x} ${viewBox_suffix}`);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
new Promise(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAllChildren(parentNode) {
|
||||||
|
while(parentNode.firstChild) {
|
||||||
|
parentNode.removeChild(parentNode.lastChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function start_chat(who) {
|
||||||
|
let indicator = document.getElementById('contact_indicator');
|
||||||
|
indicator.childNodes[0].data = `Kontakt: ${who}`;
|
||||||
|
removeAllChildren(document.getElementById('messages'));
|
||||||
|
chat_lines_count = 0;
|
||||||
|
swipe_viewport();
|
||||||
|
|
||||||
|
switch(who) {
|
||||||
|
case 'Ven':
|
||||||
|
dialog_ven();
|
||||||
|
break;
|
||||||
|
case 'Mor':
|
||||||
|
dialog_mor();
|
||||||
|
break;
|
||||||
|
case 'Kæreste':
|
||||||
|
dialog_kaereste();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
alert(`Unknown contact: ${who}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Dialog(chat_partner) {
|
||||||
|
let conversation_id = ++conversation_count;
|
||||||
|
this.messages = [];
|
||||||
|
|
||||||
|
async function post_message(dialog, message, is_myself) {
|
||||||
|
if(dialog.messages.length) await wait(message.length * typing_speed); // first message should be instant
|
||||||
|
|
||||||
|
if(conversation_id != conversation_count) {
|
||||||
|
return; // Do not add messages to old conversation
|
||||||
|
}
|
||||||
|
|
||||||
|
let chat_message = new ChatMessage(message, is_myself);
|
||||||
|
chat_message.draw();
|
||||||
|
dialog.messages.push(chat_message);
|
||||||
|
|
||||||
|
while(chat_lines_count > max_visible_lines) {
|
||||||
|
dialog.messages.forEach(msg => msg.shift_y_pos(-line_height, messages_y_offset));
|
||||||
|
await wait(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.me = async function(message) {
|
||||||
|
await post_message(this, message, true);
|
||||||
|
}
|
||||||
|
this.you = async function(message) {
|
||||||
|
await post_message(this, message, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dialog_ven() {
|
||||||
|
let d = new Dialog('Ven');
|
||||||
|
await d.me("hej");
|
||||||
|
await d.you("tak for sidst!");
|
||||||
|
await d.you('Har du hørt om den nye EU lov "ChatControl"?');
|
||||||
|
await d.me("nej, det har jeg ikke");
|
||||||
|
await d.me("hvad handler det om?");
|
||||||
|
await d.you('EU comissionen planlægger at læse alle chatbeskeder i EUen');
|
||||||
|
await d.me('Ja, men vi krypterer jo vores beskeder? Tough luck!');
|
||||||
|
await d.you('Det tager de højde for. Aflytningen vil ske før krypteringen på din lokale telefon!');
|
||||||
|
await d.me('Det lyder overhoved ikke rart. Hvorfor vil de gøre det?');
|
||||||
|
await d.you('De siger at det er for at beskytte børn på nettet.');
|
||||||
|
await d.you('Men det giver ikke meget mening');
|
||||||
|
await d.me('Alle vores beskeder skal scannes på grund af børn på nettet? Det lyder dumt!');
|
||||||
|
await d.you('Her kan du finde mere informationer om det:');
|
||||||
|
await d.you('https://chatcontrol.eu');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dialog_mor() {
|
||||||
|
let d = new Dialog('Mor');
|
||||||
|
await d.you("Jeg har fundet nogle gamle familiebilleder fra vores ferie 10 år siden");
|
||||||
|
await d.you("Her spiller du på stranden");
|
||||||
|
await d.you("(naked stick figure image here)");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dialog_kaereste() {
|
||||||
|
let d = new Dialog('Kæreste');
|
||||||
|
await d.me('hej smukkeste');
|
||||||
|
await d.you('hej, jeg har lige tænkt på dig!');
|
||||||
|
await d.you('og derfor har jeg lavet an sexy billede');
|
||||||
|
await d.you('(silly naked stick figure image here)')
|
||||||
|
}
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- [left] contact name view -->
|
||||||
|
<rect x="0" y="0" width="100%" height="10%" fill="yellow" />
|
||||||
|
<polyline points="0,20 20,0 20,40" fill="blue" onclick="swipe_viewport()" />
|
||||||
|
<text x="50" y="30" id="contact_indicator">loading...</text>
|
||||||
|
|
||||||
|
<!-- [left] messages view -->
|
||||||
|
<rect x="0" y="10%" width="100%" height="90%" style="stroke: green; stroke-width: 10px" fill="blue" />
|
||||||
|
<rect id="messages_background" x="5%" y="15%" z="2" width="90%" height="85%" fill="pink" />
|
||||||
|
<g id="messages"></g>
|
||||||
|
|
||||||
|
<!-- [right] contact list -->
|
||||||
|
<rect x="100%" y="0" width="100%" height="10%" fill="yellow" />
|
||||||
|
<text x="120%" y="30">Dine kontakter</text>
|
||||||
|
<rect x="100%" y="10%" width="100%" height="100%" style="stroke: green; stroke-width: 10px" fill="aqua" />
|
||||||
|
|
||||||
|
<text x="105%" y="15%" onclick="start_chat('Ven')">Ven</text>
|
||||||
|
<text x="105%" y="20%" onclick="start_chat('Mor')">Mor</text>
|
||||||
|
<text x="105%" y="25%" onclick="start_chat('Kæreste')">Kæreste❤</text>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8 KiB |
Loading…
Reference in a new issue