Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • hlgr/drawing-app
  • sweng-group-15/drawing-app
2 results
Show changes
Showing
with 2066 additions and 263 deletions
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 417.7 150.7" style="enable-background:new 0 0 417.7 150.7;" xml:space="preserve">
<style type="text/css">
.st0{fill:#003C70;}
</style>
<path class="st0" d="M28.4,68.2V30.3h7.4v37.9H28.4z"/>
<path class="st0" d="M46.7,44.3h0.1c1.8-2.4,5.2-3.8,8.3-3.8c3.3,0,6.2,1.4,7.8,3.8c2.2-2.2,5.6-3.8,8.8-3.8c6-0.1,9.1,3.7,9.2,9.9
v17.8h-7V51.7c0-3.1-1.1-6.4-4.5-6.4c-3.4,0-5.5,2.1-5.5,6.8v16.2h-7V51.7c0-3.8-1.6-6.4-4.6-6.4c-3.3,0-5.4,2.3-5.4,6.9v16h-7V41.1
h7V44.3z"/>
<path class="st0" d="M98.5,44.8c4,0,6,4.7,6,9.3c0,4.8-1.6,10.4-6.3,10.4c-3.8,0-6.5-3.8-6.5-9.5C91.8,48.7,94.1,44.8,98.5,44.8z
M91.8,41.1h-7v40.7h7V64.8c2,2.4,4.7,4,7.9,4c7.5,0,12.3-7.6,12.3-14.8c0-6-3.4-13.6-11.7-13.6c-3.2,0-6.6,1.6-8.4,4.4h-0.1V41.1z"
/>
<path class="st0" d="M122,51.4c-0.1-3.6,1.7-6.9,5.8-6.9c3.5,0,4.9,2.8,4.6,6.9H122z M139,55.3c0.5-8.1-2.9-14.8-11.1-14.8
c-7.5,0-13.5,5.3-13.5,13.7c0,8.9,6,14.7,13.9,14.7c3,0,6.8-0.9,10.3-3l-2-3.8c-1.7,1.2-4.5,2.2-7.1,2.2c-4.4,0-7.9-3.8-7.7-8.9H139
z"/>
<path class="st0" d="M149.4,45.9h0.1l1.4-2.1c0.8-1.1,2.4-3.3,4.5-3.3c1.6,0,3.3,0.9,4.8,2.4l-2.6,5c-1.2-0.6-1.9-0.9-3.3-0.9
c-2.4,0-4.8,2-4.8,7.9v13.4h-7V41.1h7V45.9z"/>
<path class="st0" d="M169.4,41.1v27.1h-7V41.1H169.4z M161.7,33c0-2.1,1.7-4.2,4.1-4.2c2.4,0,4.3,2.1,4.3,4.2c0,2.3-1.6,4.5-4.2,4.5
C163.4,37.5,161.7,35.3,161.7,33z"/>
<path class="st0" d="M188.5,52.5c0.5,8-3.1,11.5-5.9,11.5c-1.7,0-3.2-1.6-3.2-4c0-3.1,1.7-5.1,5.2-6.2L188.5,52.5z M188.4,64.2
c0,1.5,0.2,3,0.7,4h7.4c-0.8-1.9-1.1-4.4-1.1-6.6V50.5c0-8.5-6-10.1-10.8-10.1c-3.6,0-6.9,0.9-10.1,3.7l2.3,3.4c1.8-1.6,4-2.8,7-2.8
c2.3,0,4.4,1.6,4.8,4.1l-6.2,1.9c-6.1,1.8-9.8,4.7-9.8,9.9c0,5,3.4,8.2,7.5,8.2c2.4,0,4.8-1.7,6.8-3.3L188.4,64.2z"/>
<path class="st0" d="M199.8,68.2V28.4h7v39.8H199.8z"/>
<path class="st0" d="M253.1,37.1c-1.8-1-4.8-2.1-7.7-2.1c-7.6,0-13.1,5.5-13.1,14.1c0,9,6.1,14.3,13.3,14.3c2.9,0,5.5-0.8,7.4-1.9
l2.1,4.7c-2.3,1.4-6.2,2.6-9.8,2.6c-12.8,0-20.9-8.7-20.9-19.8c0-10.4,8-19.4,21-19.4c3.9,0,7.4,1.4,10,2.9L253.1,37.1z"/>
<path class="st0" d="M269.1,64.8c-5,0-6.4-5.7-6.4-10.5c0-4.5,1.6-9.9,6.4-9.9c4.9,0,6.4,5.4,6.4,9.9
C275.5,59.1,274.2,64.8,269.1,64.8z M269.1,68.9c8.3,0,13.9-6.1,13.9-14.5c0-8.8-6.7-13.9-13.9-13.9c-7.1,0-13.8,5.1-13.8,13.9
C255.3,62.7,260.8,68.9,269.1,68.9z"/>
<path class="st0" d="M286,68.2V28.4h7v39.8H286z"/>
<path class="st0" d="M297.4,68.2V28.4h7v39.8H297.4z"/>
<path class="st0" d="M314.9,51.4c-0.1-3.6,1.7-6.9,5.7-6.9c3.5,0,4.9,2.8,4.6,6.9H314.9z M331.9,55.3c0.5-8.1-2.9-14.8-11.1-14.8
c-7.5,0-13.5,5.3-13.5,13.7c0,8.9,6,14.7,13.9,14.7c3,0,6.8-0.9,10.3-3l-2-3.8c-1.7,1.2-4.5,2.2-7.1,2.2c-4.4,0-7.9-3.8-7.7-8.9
H331.9z"/>
<path class="st0" d="M372.2,51.4c-0.1-3.6,1.7-6.9,5.8-6.9c3.5,0,4.9,2.8,4.6,6.9H372.2z M389.2,55.3c0.5-8.1-2.9-14.8-11.1-14.8
c-7.5,0-13.5,5.3-13.5,13.7c0,8.9,6,14.7,13.9,14.7c3,0,6.8-0.9,10.3-3l-2-3.8c-1.7,1.2-4.5,2.2-7.1,2.2c-4.4,0-7.9-3.8-7.7-8.9
H389.2z"/>
<path class="st0" d="M348.3,44.8c4.2,0,6.4,4.4,6.4,9.1c0,5.4-2,10.6-6.7,10.6c-4.1,0-6.1-5.1-6.1-10.4
C342,48.9,343.8,44.8,348.3,44.8z M361.7,41.1h-7v3.3h-0.1c-1.5-2.4-4.6-4-7.9-4c-7.2,0-12.2,6.8-12.2,13.9
c0,8.7,4.6,14.5,11.6,14.5c4,0,6.8-2.3,8.5-4.6h0.1v3.6c0,6.3-3.6,9.2-8.2,9.2c-3.6,0-6.6-0.7-9.2-2.3l-1.4,4.4
c3.1,1.6,7.1,2.6,11.1,2.6c7.9,0,14.7-4,14.7-16V41.1z"/>
<path class="st0" d="M28.4,84.5h7.5v32.8h15.8v5.1H28.4V84.5z"/>
<path class="st0" d="M92.8,98.5c2.5-2.6,6-3.8,9.5-3.8c6.5,0,9.6,3.5,9.6,10.3v17.5h-7.1v-16.6c0-3.8-1.7-6.3-5.6-6.3
c-3.6,0-6.4,2.3-6.4,6.8v16.2h-7.1V95.3h7V98.5z"/>
<path class="st0" d="M130.2,99c4.4,0,6.7,4.4,6.7,9.1c0,5.4-2.1,10.6-7.1,10.6c-4.3,0-6.4-5.1-6.4-10.4S125.4,99,130.2,99z
M136.9,122.4h7.1V82.6h-7.1v15.9h-0.1c-1.7-2.4-4.9-3.8-8.5-3.8c-7.4,0-12.7,6.8-12.7,13.9c0,8.7,4.8,14.5,12.1,14.5
c4.3,0,7.2-2.3,9-4.6h0.1V122.4z"/>
<path class="st0" d="M162.3,119c-5.3,0-6.7-5.7-6.7-10.5c0-4.5,1.7-9.9,6.7-9.9c5.2,0,6.8,5.4,6.8,9.9
C169,113.3,167.6,119,162.3,119z M162.3,123.1c8.7,0,14.6-6.1,14.6-14.5c0-8.8-7.1-13.9-14.6-13.9c-7.4,0-14.5,5.1-14.5,13.9
C147.7,116.9,153.6,123.1,162.3,123.1z"/>
<path class="st0" d="M187.7,98.5c2.5-2.6,6-3.8,9.5-3.8c6.5,0,9.6,3.5,9.6,10.3v17.5h-7.1v-16.6c0-3.8-1.7-6.3-5.6-6.3
c-3.6,0-6.4,2.3-6.4,6.8v16.2h-7.1V95.3h7V98.5z"/>
<path class="st0" d="M67.2,119c-5.3,0-6.7-5.7-6.7-10.5c0-4.5,1.7-9.9,6.7-9.9c5.2,0,6.8,5.4,6.8,9.9C74,113.3,72.6,119,67.2,119z
M67.2,123.1c8.7,0,14.6-6.1,14.6-14.5c0-8.8-7.1-13.9-14.6-13.9c-7.4,0-14.5,5.1-14.5,13.9C52.7,116.9,58.6,123.1,67.2,123.1z"/>
</svg>
......@@ -4,45 +4,39 @@
<meta charset="UTF-8" />
<link rel="manifest" href="manifest.json" />
<link rel="shortcut icon" href="logo.png" />
<link rel="apple-touch-icon" href="logo.png" />
<link rel="stylesheet" href="styles.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<script>
if (navigator.serviceWorker) {
navigator.serviceWorker
.register("service-worker.js")
.then(
(registration) =>
console.log(
`Service worker registered on scope ${registration.scope}`,
),
(reason) =>
console.log(`Service worker failed to register ~ ${reason}`),
)
navigator.serviceWorker.register("service-worker.js").then(
(registration) =>
console.log(
`Service worker registered on scope ${registration.scope}`,
),
(reason) =>
console.log(`Service worker failed to register ~ ${reason}`),
)
}
</script>
</head>
<body>
<div style="display:none">
Your client ID: <input id="user-id" type="text" value="" readonly="" />
</div>
<div style="display:none">
Enter peer ID: <input id="peer-id" type="text" value="" />
<button id="peer-connect">Connect</button>
</div>
<div id="top-panel">
<div class="top-bar">
<div class="dropdown">
<button class="dropdown-peers"><i class="fa fa-bars"></i></button>
<button class="dropdown-peers"><i class="fas fa-bars"></i></button>
<div class="peers">
<ul id="connected-peers">
No peers are connected
</ul>
</div>
</div>
<div id="overall-status-icon" class="synchronised">
<img
id="overall-status-icon-img"
src="synchronised.svg"
alt="Synchronisation status icon"
/>
</div>
<input
id="room-id"
type="text"
......@@ -51,11 +45,11 @@
size="25"
color="gray"
/>
<button id="room-connect">Connect <i class="fa fa-link"></i></button>
<button id="room-connect">Connect <i class="fas fa-link"></i></button>
</div>
<div id="tools-panel">
<button id="pen-tool" class="selected">
<i class="fa fa-paint-brush"></i>
<button id="pen-tool" class="selectable-tool selected">
<i class="fas fa-paint-brush"></i>
</button>
<div id="pen-properties" class="properties">
<div class="pen-contents">
......@@ -67,11 +61,28 @@
<div id="brush-colour">
<h3>Brush colour</h3>
</div>
&nbsp;
<a href="#">
<a href="#" style="margin-left: 1%">
<div id="rectangle"></div>
</a>
</div>
<div class="pen-body">
<div id="brush-colour">
<h3>Brush size</h3>
</div>
&nbsp;
<div class="slide-size">
<input
type="range"
min="1"
max="10"
class="slider"
id="range"
/>
<p style="text-align: center; margin: 0px">
Size: <span id="value"></span>
</p>
</div>
</div>
</div>
</div>
<div id="palette" class="properties" style="padding-top: 150px">
......@@ -85,6 +96,7 @@
viewBox="0 10 100 70"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
style="@media only screen and (max-width: 600px) { viewBox: -50 10 200 100 }"
>
<defs>
<path
......@@ -164,22 +176,54 @@
</svg>
<div id="others">
<div id="other-palette">
<b>Others</b>
<b>Other colours</b>
</div>
<label id="colours">
<input id="other-colours" type="color" value="blue" />
<input id="other-colours" type="color" />
</label>
</div>
</div>
</div>
<button id="eraser-tool"><i class="fa fa-eraser"></i></button>
<div id="connected-room-info">
You are connected to a room: <span id="connected-room-id"></span>
<button id="eraser-tool" class="selectable-tool">
<i class="fas fa-eraser"></i>
</button>
<button id="dragging-tool" class="selectable-tool">
<i class="far fa-hand-paper"></i>
</button>
<button id="canvas-center" class="selectable-tool">
<i class="fas fa-crosshairs"></i>
</button>
<button id="recognition-mode" class="selectable-tool">
<i class="fas fa-square"></i>
</button>
<div class="spacer"></div>
<div id="status-info">
<button id="fast-undo-tool" class="disabled selectable-tool">
<i class="fas fa-fast-backward"></i>
</button>
<button id="undo-tool" class="disabled selectable-tool">
<i class="fas fa-step-backward"></i>
</button>
<div id="connected-room-info">
Room:&nbsp;
<span id="connected-room-id"></span>
</div>
<div id="user-avatar"></div>
<div id="imperial-logo">
<img
src="imperial.svg"
alt="Imperial College London Logo"
height="42px"
/>
</div>
</div>
</div>
</div>
<svg id="canvas"></svg>
<div id="canvas-container">
<svg id="canvas"></svg>
</div>
<script src="js/app.js"></script>
</body>
......
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="green" stroke="green" d="M8.213 16.984c.97-1.028 2.308-1.664 3.787-1.664s2.817.636 3.787 1.664l-3.787 4.016-3.787-4.016zm-1.747-1.854c1.417-1.502 3.373-2.431 5.534-2.431s4.118.929 5.534 2.431l2.33-2.472c-2.012-2.134-4.793-3.454-7.864-3.454s-5.852 1.32-7.864 3.455l2.33 2.471zm-4.078-4.325c2.46-2.609 5.859-4.222 9.612-4.222s7.152 1.613 9.612 4.222l2.388-2.533c-3.071-3.257-7.313-5.272-12-5.272s-8.929 2.015-12 5.272l2.388 2.533z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="red" d="M8.213 16.984c.97-1.028 2.308-1.664 3.787-1.664s2.817.636 3.787 1.664l-3.787 4.016-3.787-4.016zm3.787-6.78c2.387 0 4.648.876 6.461 2.485l-.969 1.028c-1.556-1.308-3.472-2.018-5.492-2.018-2.021 0-3.937.71-5.492 2.018l-.969-1.028c1.813-1.609 4.075-2.485 6.461-2.485zm0-1c-3.071 0-5.852 1.32-7.864 3.455l2.33 2.472c1.417-1.502 3.373-2.431 5.534-2.431s4.117.929 5.534 2.431l2.33-2.472c-2.012-2.135-4.793-3.455-7.864-3.455zm0-5.204c3.949 0 7.682 1.517 10.607 4.291l-1.021 1.083c-2.656-2.452-6.023-3.791-9.586-3.791s-6.93 1.339-9.586 3.791l-1.021-1.083c2.926-2.774 6.658-4.291 10.607-4.291zm0-1c-4.687 0-8.929 2.015-12 5.272l2.388 2.533c2.46-2.609 5.859-4.222 9.612-4.222 3.754 0 7.152 1.613 9.611 4.222l2.389-2.533c-3.071-3.257-7.313-5.272-12-5.272z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="orange" d="M8.213 16.984c.97-1.028 2.308-1.664 3.787-1.664s2.817.636 3.787 1.664l-3.787 4.016-3.787-4.016zm-1.747-1.854c1.417-1.502 3.373-2.431 5.534-2.431s4.118.929 5.534 2.431l2.33-2.472c-2.012-2.134-4.793-3.454-7.864-3.454s-5.852 1.32-7.864 3.455l2.33 2.471zm5.534-11.13c3.949 0 7.681 1.517 10.607 4.291l-1.021 1.083c-2.656-2.452-6.023-3.791-9.586-3.791s-6.93 1.339-9.586 3.791l-1.021-1.083c2.926-2.774 6.658-4.291 10.607-4.291zm0-1c-4.687 0-8.929 2.015-12 5.272l2.388 2.533c2.46-2.609 5.859-4.222 9.612-4.222s7.152 1.613 9.612 4.222l2.388-2.533c-3.071-3.257-7.313-5.272-12-5.272z"/></svg>
......@@ -3,6 +3,10 @@ body {
height: 100%;
}
#console {
color: white;
}
body {
background-color: black;
margin: 0;
......@@ -10,11 +14,21 @@ body {
flex-direction: column;
}
#canvas {
#canvas-container {
width: calc(100vw - 8px);
background-color: white;
border: 4px solid red;
flex-grow: 1;
position: relative;
overflow: hidden;
}
#canvas {
position: absolute;
left: -5000px;
top: -5000px;
width: 10000px;
height: 10000px;
}
#tools-panel {
......@@ -23,11 +37,18 @@ body {
align-items: center;
}
.spacer {
flex: 1;
}
#connected-room-info {
display: none;
color: white;
flex: 1;
text-align: right;
display: flex;
align-items: center;
background-color: #4f4f4fb7;
border-radius: 4px;
justify-content: center;
padding: 0.75em;
}
button.selected {
......@@ -60,7 +81,7 @@ button.selected {
cursor: pointer;
border-radius: 4px;
transition-duration: 0.4s;
height: 100%;
height: 36px;
width: 36px;
}
......@@ -71,7 +92,7 @@ button.selected {
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 999;
padding: 16px;
padding: 8px;
}
.peers a {
......@@ -92,15 +113,113 @@ button.selected {
margin: 0;
}
@keyframes connected-peers-slide {
from {
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0px);
}
}
#connected-peers li {
animation: connected-peers-slide 0.5s forwards;
border-bottom: 1px solid black;
padding: 4px 0;
opacity: 0;
display: flex;
align-items: center;
}
#connected-peers li:nth-child(2) {
animation-delay: 0.125s;
}
#connected-peers li:nth-child(3) {
animation-delay: 0.25s;
}
#connected-peers li:nth-child(4) {
animation-delay: 0.375s;
}
#connected-peers li:nth-child(5) {
animation-delay: 0.5s;
}
#connected-peers li:nth-child(6) {
animation-delay: 0.625s;
}
#connected-peers li:nth-child(7) {
animation-delay: 0.75s;
}
#connected-peers li:nth-child(8) {
animation-delay: 0.875s;
}
#connected-peers li:last-child {
border-bottom-width: 0px;
}
.peer-quality,
.peer-status {
width: 15px;
height: 15px;
margin-right: 5px;
}
.peer-status {
border-radius: 10px;
}
.peer-status::after {
font-size: 0.5em;
margin-left: 10px;
background-color: white;
border-radius: 50%;
}
.peer-status.upload::after {
content: "▲";
padding: 0.5px;
}
.peer-status.download::after {
content: "▼";
padding: 1px;
}
.peer-status.unsynced {
color: gray;
background-color: gray;
}
@keyframes peer-status-negotiating {
from {
color: gray;
background-color: gray;
}
50%,
to {
color: orange;
background-color: orange;
}
}
.peer-status.negotiating {
animation: peer-status-negotiating 0.5s step-end infinite;
background-color: orange;
color: orange;
}
.peer-status.synced {
color: green;
background-color: green;
}
.dropdown:hover .peers {
display: block;
}
......@@ -109,6 +228,36 @@ button.selected {
background-color: #4f4f4f;
}
#overall-status-icon {
pointer-events: none;
border-radius: 50%;
width: 16px;
height: 16px;
padding: 4px;
margin-left: -18px;
margin-top: -4px;
z-index: 0;
}
@keyframes overall-status-synchronising {
to {
transform: rotate(-360deg);
}
}
#overall-status-icon.synchronising {
background-color: orange;
animation: overall-status-synchronising 2s linear infinite;
}
#overall-status-icon.synchronised {
background-color: green;
}
#overall-status-icon-img {
height: inherit;
}
#room-id {
flex: 1;
padding: 10px;
......@@ -126,9 +275,13 @@ button.selected {
border: none;
cursor: pointer;
border-radius: 4px;
width: 78px;
height: 36px;
white-space: nowrap;
overflow: hidden;
}
#pen-tool {
.selectable-tool {
background-color: #2f2f2f;
color: white;
padding: 10px;
......@@ -136,27 +289,89 @@ button.selected {
border: none;
cursor: pointer;
border-radius: 50%;
margin-right: 8px;
}
#pen-tool:hover {
.selectable-tool:hover {
background-color: #4f4f4f !important;
transition-duration: 0.4s;
}
#pen-tool {
width: 39px;
height: 39px;
margin-right: 8px;
}
#pen-tool > i {
padding: 0 1.5px;
}
#eraser-tool {
background-color: #2f2f2f;
color: white;
padding: 10px;
font-size: 16px;
border: none;
cursor: pointer;
border-radius: 50%;
width: 39px;
height: 39px;
margin-right: 8px;
}
#eraser-tool:hover {
background-color: #4f4f4f !important;
transition-duration: 0.4s;
#eraser-tool > i {
padding: 0 1.5px;
}
#dragging-tool {
width: 39px;
height: 39px;
margin-right: 8px;
}
#dragging-tool > i {
padding: 0 2.5px;
}
#canvas-center {
width: 39px;
height: 39px;
margin-right: 8px;
}
#canvas-center > i {
padding: 0 1.5px;
}
#recognition-mode {
width: 39px;
height: 39px;
margin-right: 8px;
}
#recognition-mode > i {
padding: 0 2.5px;
}
#undo-tool {
width: 39px;
height: 39px;
margin-right: 8px;
}
#undo-tool.disabled {
display: none;
}
#undo-tool > i {
padding: 0 2.5px;
}
#fast-undo-tool {
width: 39px;
height: 39px;
margin-right: 8px;
}
#fast-undo-tool.disabled {
display: none;
}
#fast-undo-tool > i {
padding: 0 1.5px;
}
.properties {
......@@ -256,11 +471,85 @@ button.selected {
text-align: center;
}
.slide-size {
width: 90%;
height: 20%;
margin-left: 2.2%;
}
.slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 25px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
-webkit-transition: 0.2s;
transition: opacity 0.2s;
margin-top: 30px;
}
.slider:hover {
opacity: 1;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
background: #4f4f4f;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 25px;
height: 25px;
background: #4f4f4f;
cursor: pointer;
}
@media only screen and (max-width: 600px) {
.properties {
display: none;
position: fixed;
z-index: 1;
padding-top: 80px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
}
.palette-selector {
position: relative;
background-color: #fefefe;
margin: auto;
padding: 0;
border: 1px solid #888;
width: 80%;
height: 80%;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.1);
font-family: "Martel", serif;
}
.wheel-header {
padding: 2px 16px;
background-color: #2f2f2f;
color: white;
text-align: center;
}
}
#others {
margin-top: 2%;
display: flex;
align-items: center;
padding-left: 40%;
justify-content: center;
background-color: #3cbc8d;
color: white;
padding-top: 4px;
......@@ -288,8 +577,20 @@ button.selected {
transition-duration: 0.4s;
}
#other-colours {
visibility: hidden;
@supports (-webkit-overflow-scrolling: touch) {
#colours {
visibility: hidden;
}
#other-colours {
visibility: visible;
}
}
@supports not (-webkit-overflow-scrolling: touch) {
#other-colours {
visibility: hidden;
}
}
@font-face {
......@@ -309,3 +610,32 @@ button.selected {
url("./assets/fonts/martel-v4-latin/martel-v4-latin-regular.svg#Martel")
format("svg");
}
.avatar {
margin-right: 5px;
}
#user-avatar {
color: white;
display: flex;
align-items: center;
background-color: #4f4f4fb7;
border-radius: 4px;
margin-left: 0.75em;
padding: 0 0.75em 0 0;
}
#status-info {
display: flex;
align-items: center;
justify-content: right;
}
#imperial-logo {
background-color: white;
display: flex;
align-items: center;
border-radius: 4px;
margin-left: 0.75em;
padding: 0;
}
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path stroke="white" fill="white" d="M23 12c0 1.042-.154 2.045-.425 3h-2.101c.335-.94.526-1.947.526-3 0-4.962-4.037-9-9-9-1.706 0-3.296.484-4.654 1.314l1.857 2.686h-6.994l2.152-7 1.85 2.673c1.683-1.049 3.658-1.673 5.789-1.673 6.074 0 11 4.925 11 11zm-6.354 7.692c-1.357.826-2.944 1.308-4.646 1.308-4.963 0-9-4.038-9-9 0-1.053.191-2.06.525-3h-2.1c-.271.955-.425 1.958-.425 3 0 6.075 4.925 11 11 11 2.127 0 4.099-.621 5.78-1.667l1.853 2.667 2.152-6.989h-6.994l1.855 2.681zm.354-10.283l-1.421-1.409-5.105 5.183-2.078-2.183-1.396 1.435 3.5 3.565 6.5-6.591z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="white" stroke="white" d="M18.312 5.595l1.296-1.527c.727.698 1.354 1.495 1.869 2.369l-1.776.931c-.39-.648-.853-1.246-1.389-1.773zm3.844 2.179l-1.787.937c.255.647.435 1.33.533 2.039h2.021c-.117-1.043-.378-2.042-.767-2.976zm-14.81-3.46c1.358-.83 2.948-1.314 4.654-1.314 1.919 0 3.695.608 5.157 1.636l1.298-1.529c-1.814-1.319-4.04-2.107-6.455-2.107-2.131 0-4.106.624-5.789 1.673l-1.85-2.673-2.152 7h6.994l-1.857-2.686zm13.642 7.936c-.027.962-.206 1.885-.514 2.75h2.101c.249-.878.401-1.798.425-2.75h-2.012zm-15.3 6.155l-1.295 1.527c-.727-.698-1.355-1.495-1.869-2.369l1.775-.931c.39.648.853 1.246 1.389 1.773zm-3.844-2.179l1.787-.937c-.254-.647-.434-1.33-.533-2.039h-2.022c.119 1.043.379 2.042.768 2.976zm14.81 3.46c-1.357.83-2.947 1.314-4.654 1.314-1.918 0-3.695-.608-5.156-1.636l-1.299 1.529c1.814 1.319 4.041 2.107 6.455 2.107 2.131 0 4.107-.624 5.789-1.673l1.85 2.673 2.152-7h-6.994l1.857 2.686zm-13.642-7.936c.027-.962.207-1.885.513-2.75h-2.1c-.249.878-.402 1.798-.425 2.75h2.012z"/></svg>
File added
This diff is collapsed.
import "array-flat-polyfill"
// Local canvas rendering.
// Emit input events and receive draw calls seperately - these must be piped
// together externally if desired.
import { line, curveLinear } from "d3-shape"
import { line, curveCatmullRom, curveLinear } from "d3-shape"
import { canvas } from "./elements.js"
const SVG_URL = "http://www.w3.org/2000/svg"
// TODO: switch to curve interpolation that respects mouse points based on velocity
const lineFn = line()
import * as HTML from "./elements.js"
export const LAST_RECOGNIZED_PATH_ID = "LSP"
const curve = curveCatmullRom.alpha(1.0)
const smoothLine = line()
.x((d) => d[0])
.y((d) => d[1])
.curve(curve)
const straightLine = line()
.x((d) => d[0])
.y((d) => d[1])
.curve(curveLinear)
const pathGroupElems = new Map()
let stroke_colour = "blue"
export const MIN_STROKE_RADIUS = 0.1
export const MAX_STROKE_RADIUS = 3.9
const MAX_RADIUS_DELTA = 0.05
const getStrokeRadius = (pressure) => {
return MIN_STROKE_RADIUS + pressure * (MAX_STROKE_RADIUS - MIN_STROKE_RADIUS)
// Interpolate a path so that:
// - The radius delta between two adjacent points is capped at
// MAX_RADIUS_DELTA.
// If paths are too choppy, try decreasing these constants.
const smoothPath = ([...path]) => {
// Apply MAX_RADIUS_DELTA.
for (let i = 1; i < path.length; i++) {
const dx = path[i][0] - path[i - 1][0]
const dy = path[i][1] - path[i - 1][1]
const dw = path[i][2] - path[i - 1][2]
const segmentsToSplit = Math.ceil(dw / MAX_RADIUS_DELTA)
const newPoints = []
for (let j = 1; j < segmentsToSplit; j++) {
newPoints.push([
path[i - 1][0] + (dx / segmentsToSplit) * j,
path[i - 1][1] + (dy / segmentsToSplit) * j,
path[i - 1][2] + (dw / segmentsToSplit) * j,
path[i - 1][3],
])
}
path.splice(i, 0, ...newPoints)
i += newPoints.length
}
return path
}
export const input = new EventTarget()
const createPathElem = (d, width) => {
const pathGroupElem = document.createElementNS(SVG_URL, "path")
pathGroupElem.setAttribute("stroke-width", width)
pathGroupElem.setAttribute("d", d)
return pathGroupElem
const createSvgElem = (tagName) => document.createElementNS(SVG_URL, tagName)
const interpolate = (point0, point1, fraction) => [
point0[0] + fraction * (point1[0] - point0[0]),
point0[1] + fraction * (point1[1] - point0[1]),
point0[2] + fraction * (point1[2] - point0[2]),
point0[3],
]
const ensurePathGroupElem = (id) => {
let groupElem = pathGroupElems.get(id)
if (groupElem == null) {
groupElem = createSvgElem("g")
groupElem.setAttribute("fill", "none")
groupElem.setAttribute("stroke-linecap", "round")
groupElem.setAttribute("pointer-events", "none")
HTML.canvas.appendChild(groupElem)
pathGroupElems.set(id, groupElem)
}
groupElem.innerHTML = ""
return groupElem
}
export const renderPath = (id, points) => {
points = points.filter(([x]) => x != null)
let colour = ""
// Split up points into completely non-erased segments.
let segments = [[]]
for (const point of points) {
colour = point[3]
if (point[4] != false) {
segments[segments.length - 1].push(point)
} else {
segments.push([])
}
const renderSubpath = (subpath, pathSmooth) => {
const pathElem = createSvgElem("path")
pathElem.setAttribute("stroke", subpath[0][3])
pathElem.setAttribute("stroke-width", subpath[0][2] * 2)
pathElem.setAttribute(
"d",
pathSmooth ? smoothLine(subpath) : straightLine(subpath),
)
return pathElem
}
const isValidPoint = (point) => {
return point != null && point[0] != null
}
const POINT_ERASE_LIMIT = 0.0001
const pointWasErased = (pointEraseIntervals) => {
return (
pointEraseIntervals.length > 0 &&
pointEraseIntervals[0][0] <= POINT_ERASE_LIMIT
)
}
const needToDrawLastPoint = (points, pathEraseIntervals) => {
if (points.length < 2) return true
const penultimatePointIndex = points.length - 2
const pointEraseIntervals = pathEraseIntervals[penultimatePointIndex] || null
if (
pointEraseIntervals != null &&
pointEraseIntervals.length > 0 &&
pointEraseIntervals.some((interval) => interval[1] >= 1 - POINT_ERASE_LIMIT)
) {
return false
}
return true
}
const applyErasureIntervals = (points, pathEraseIntervals) => {
if (points.length == 0) {
return []
}
segments = segments.filter((a) => a.length > 0)
let pathGroupElem = pathGroupElems.get(id)
const subpaths = []
if (segments.length == 0) {
if (pathGroupElem != null) {
canvas.removeChild(pathGroupElem)
pathGroupElems.delete(id)
let subpath = []
for (let i = 0; i < points.length; i++) {
const point = points[i]
if (!isValidPoint(point)) {
continue
}
return
const nextPoint = points[i + 1]
const pointEraseIntervals = pathEraseIntervals[i] || null
if (pointEraseIntervals == null) {
subpath.push(point)
continue
} else if (nextPoint == null) {
if (JSON.stringify(pointEraseIntervals) != "[[0,0]]") subpath.push(point)
continue
}
if (!pointWasErased(pointEraseIntervals) || subpath.length) {
subpath.push(point)
}
for (const pointEraseInterval of pointEraseIntervals) {
const eraseIntervalBounds = pointEraseInterval.map((f) =>
interpolate(point, nextPoint, f),
)
const [endOfDrawnSegment, startOfNewSegment] = eraseIntervalBounds
if (pointEraseInterval[0] > POINT_ERASE_LIMIT) {
subpath.push(endOfDrawnSegment)
}
subpaths.push(subpath.slice())
if (pointEraseInterval[1] < 1 - POINT_ERASE_LIMIT) {
subpath = [startOfNewSegment]
} else {
subpath = []
}
}
}
if (needToDrawLastPoint(points, pathEraseIntervals)) {
subpaths.push(subpath.slice())
}
if (pathGroupElem == null) {
pathGroupElem = document.createElementNS(SVG_URL, "g")
pathGroupElem.setAttribute("stroke", colour)
pathGroupElem.setAttribute("fill", "none")
pathGroupElem.setAttribute("stroke-linecap", "round")
pathGroupElem.setAttribute("pointer-events", "none")
canvas.appendChild(pathGroupElem)
pathGroupElems.set(id, pathGroupElem)
return subpaths.filter((subpath) => subpath.length > 0)
}
export const splitOnPressures = ([...path]) => {
const subpaths = []
let w = path[0][2]
for (let i = 1; i < path.length; i++) {
if (path[i][2] != w) {
subpaths.push([...path.splice(0, i), path[0]])
w = path[0][2]
i = 1
}
}
subpaths.push(path)
return subpaths
}
export const renderPath = (id, points, pathEraseIntervals) => {
let rectShapeStartIndex = -1
const pointsWithIntervals = Object.keys(pathEraseIntervals).length
pathGroupElem.innerHTML = ""
for (const subpath of segments) {
if (subpath.length == 1) {
const circleElem = document.createElementNS(SVG_URL, "circle")
circleElem.setAttribute("stroke", "none")
circleElem.setAttribute("fill", colour)
circleElem.setAttribute("cx", subpath[0][0])
circleElem.setAttribute("cy", subpath[0][1])
circleElem.setAttribute("r", getStrokeRadius(subpath[0][2]))
pathGroupElem.appendChild(circleElem)
} else {
// Further split up segments based on thickness.
const subpath_ = subpath.slice()
let w = subpath_[0][2]
for (let i = 1; i < subpath_.length; i++) {
if (subpath_[i][2] != w) {
const d = lineFn([...subpath_.splice(0, i), subpath_[0]])
pathGroupElem.appendChild(createPathElem(d, getStrokeRadius(w) * 2))
w = subpath_[0][2]
i = 1
}
// Rect recognition hint shape: pure rect with no erasure
if (pointsWithIntervals == 0 && points.length == 5) {
rectShapeStartIndex = 0
// Recognised rect shape: rect after completely erased raw data
} else if (pointsWithIntervals > 0 && points.length > 5) {
// Check that the preceding raw data is completely erased
for (let i = 0; i < points.length - 5; i++) {
if (
!pathEraseIntervals[i] ||
pathEraseIntervals[i].length != 1 ||
pathEraseIntervals[i][0][0] != 0 ||
pathEraseIntervals[i][0][1] != 1
) {
break
}
const d = lineFn(subpath_)
pathGroupElem.appendChild(createPathElem(d, getStrokeRadius(w) * 2))
}
rectShapeStartIndex = points.length - 5
}
// Only draw the path smooth if it is not a recognised rect shape, i.e. the last five points form a cycle
const pathSmooth = !(
rectShapeStartIndex >= 0 &&
points[rectShapeStartIndex][0] == points[rectShapeStartIndex + 4][0] &&
points[rectShapeStartIndex][1] == points[rectShapeStartIndex + 4][1] &&
points[rectShapeStartIndex][2] == points[rectShapeStartIndex + 4][2] &&
points[rectShapeStartIndex][3] == points[rectShapeStartIndex + 4][3]
)
let subpaths = applyErasureIntervals(points, pathEraseIntervals)
if (subpaths.length < 1 && id != LAST_RECOGNIZED_PATH_ID) {
const pathGroupElem = pathGroupElems.get(id)
if (pathGroupElem) {
pathGroupElems.delete(id)
HTML.canvas.removeChild(pathGroupElem)
}
return
}
subpaths = subpaths.map(smoothPath)
subpaths = subpaths.flatMap(splitOnPressures)
const subpathElems = subpaths.map((subpath) =>
renderSubpath(subpath, pathSmooth),
)
const pathGroupElem = ensurePathGroupElem(id)
subpathElems.forEach((subpathElem) => pathGroupElem.appendChild(subpathElem))
}
export const clear = () => {
......@@ -104,12 +255,13 @@ export const clear = () => {
}
// Necessary since buttons property is non standard on iOS versions < 13.2
function checkValidPointerEvent(e) {
const isValidPointerEvent = (e, name) => {
if (name === "strokeend") return true
return e.buttons & 1 || e.pointerType === "touch"
}
const dispatchPointerEvent = (name) => (e) => {
if (checkValidPointerEvent(e)) {
if (isValidPointerEvent(e, name)) {
input.dispatchEvent(new CustomEvent(name, { detail: e }))
}
}
......@@ -118,14 +270,8 @@ const dispatchPointerEvent = (name) => (e) => {
canvas.addEventListener("pointerdown", dispatchPointerEvent("strokestart"))
canvas.addEventListener("pointerenter", dispatchPointerEvent("strokestart"))
canvas.addEventListener("pointerup", dispatchPointerEvent("strokeend"))
canvas.addEventListener("onmouseup", dispatchPointerEvent("strokeend"))
canvas.addEventListener("mouseup", dispatchPointerEvent("strokeend"))
canvas.addEventListener("pointerleave", dispatchPointerEvent("strokeend"))
canvas.addEventListener("pointermove", dispatchPointerEvent("strokemove"))
canvas.addEventListener("touchmove", (e) => e.preventDefault())
export function setStrokeColour(colour) {
stroke_colour = colour
}
export function getStrokeColour() {
return stroke_colour
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Subproject commit 139ab6e2cc9d6f0501fa958f3c813df0fcc81310
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Subproject commit 545380d3c27797799f3013ad112f9f17c64bc8c7
Subproject commit ce4a2ebe160804ed84f7b6fc3bd10c91e766bdcd