Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
D
drawing-app
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Operate
Environments
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
sweng-group-15
drawing-app
Commits
6e4359ca
Commit
6e4359ca
authored
5 years ago
by
Nayeem Rahman
Browse files
Options
Downloads
Patches
Plain Diff
Split canvas.js from app.js
parent
22531aba
No related branches found
No related tags found
1 merge request
!29
Split canvas.js from app.js
Pipeline
#101870
passed
5 years ago
Stage: fetch
Stage: deps
Stage: check
Stage: build
Stage: test
Changes
2
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
src/app.js
+91
-221
91 additions, 221 deletions
src/app.js
src/canvas.js
+123
-0
123 additions, 0 deletions
src/canvas.js
with
214 additions
and
221 deletions
src/app.js
+
91
−
221
View file @
6e4359ca
import
{
line
,
curveLinear
}
from
"
d3-shape
"
// Room connection and synchronisation.
// Translate local canvas input events to draw messages and send to the room.
// Get back room updates and invoke the local canvas renderer.
import
*
as
canvas
from
"
./canvas.js
"
import
*
as
HTML
from
"
./elements.js
"
import
{
connect
}
from
"
./room.js
"
const
TEST_ROOM
=
"
imperial
"
// TODO: switch to curve interpolation that respects mouse points based on velocity
const
lineFn
=
line
()
.
x
((
d
)
=>
d
[
0
])
.
y
((
d
)
=>
d
[
1
])
.
curve
(
curveLinear
)
let
room
=
null
const
tools
=
{
PEN
:
"
pen
"
,
ERASER
:
"
eraser
"
,
}
const
STROKECOLOUR
=
"
blue
"
const
STROKERADIUS
=
2
const
ERASERRADIUS
=
STROKERADIUS
*
2
const
addOrUpdatePathElem
=
(
pathElems
,
id
,
points
)
=>
{
let
pathElem
=
pathElems
.
get
(
id
)
if
(
pathElem
==
null
)
{
pathElem
=
document
.
createElementNS
(
"
http://www.w3.org/2000/svg
"
,
"
g
"
)
pathElem
.
setAttribute
(
"
stroke
"
,
STROKECOLOUR
)
pathElem
.
setAttribute
(
"
stroke-width
"
,
STROKERADIUS
*
2
)
pathElem
.
setAttribute
(
"
fill
"
,
"
none
"
)
pathElem
.
setAttribute
(
"
pointer-events
"
,
"
none
"
)
pathElem
.
setAttribute
(
"
marker-start
"
,
"
url(#dot)
"
)
pathElem
.
setAttribute
(
"
marker-end
"
,
"
url(#dot)
"
)
HTML
.
canvas
.
appendChild
(
pathElem
)
pathElems
.
set
(
id
,
pathElem
)
}
pathElem
.
innerHTML
=
""
if
(
points
.
length
==
0
)
{
return
pathElem
}
// Push a fake path split to generate the last path
points
.
push
([
-
1
,
-
1
,
false
])
let
subpath
=
[]
for
(
const
point
of
points
)
{
if
(
point
[
0
]
===
undefined
)
{
continue
}
if
(
point
[
2
]
===
false
)
{
if
(
subpath
.
length
==
1
)
{
const
subpathElem
=
document
.
createElementNS
(
"
http://www.w3.org/2000/svg
"
,
"
circle
"
,
)
subpathElem
.
setAttribute
(
"
stroke
"
,
"
none
"
)
subpathElem
.
setAttribute
(
"
fill
"
,
STROKECOLOUR
)
subpathElem
.
setAttribute
(
"
cx
"
,
subpath
[
0
][
0
])
subpathElem
.
setAttribute
(
"
cy
"
,
subpath
[
0
][
1
])
subpathElem
.
setAttribute
(
"
r
"
,
STROKERADIUS
)
pathElem
.
appendChild
(
subpathElem
)
}
else
if
(
subpath
.
length
>
0
)
{
const
subpathElem
=
document
.
createElementNS
(
"
http://www.w3.org/2000/svg
"
,
"
path
"
,
)
subpathElem
.
setAttribute
(
"
d
"
,
lineFn
(
subpath
))
pathElem
.
appendChild
(
subpathElem
)
}
subpath
=
[]
continue
}
subpath
.
push
(
point
)
}
return
pathElem
}
const
getDistance
=
(
a
,
b
)
=>
{
return
Math
.
sqrt
(
(
a
[
0
]
-
b
[
0
])
*
(
a
[
0
]
-
b
[
0
])
+
(
a
[
1
]
-
b
[
1
])
*
(
a
[
1
]
-
b
[
1
]),
)
}
const
onRoomConnect
=
(
room_
)
=>
{
room
=
room_
function
setElemVisible
(
elem
,
visible
=
true
)
{
if
(
!
(
elem
&&
elem
.
style
))
return
elem
.
style
.
display
=
visible
?
"
block
"
:
"
none
"
}
function
showConnectedRoom
(
roomID
)
{
HTML
.
connectedRoomID
.
textContent
=
roomID
setElemVisible
(
HTML
.
connectedRoomInfoContainer
)
}
function
setBlankUIState
()
{
while
(
HTML
.
canvas
.
children
[
1
])
{
HTML
.
canvas
.
removeChild
(
HTML
.
canvas
.
children
[
1
])
}
HTML
.
connectedPeers
.
innerHTML
=
"
No peers are connected
"
}
function
handleRoomConnectClick
()
{
const
selectedRoomID
=
HTML
.
roomIDElem
.
value
if
(
!
selectedRoomID
)
return
setBlankUIState
()
connectToARoom
(
selectedRoomID
)
}
function
handleRoomConnectionEstablished
(
room
)
{
showConnectedRoom
(
room
.
name
)
HTML
.
connectedRoomID
.
textContent
=
room
.
name
HTML
.
connectedRoomInfoContainer
.
style
.
display
=
"
block
"
HTML
.
userIDElem
.
value
=
room
.
ownID
||
""
room
.
addEventListener
(
"
allocateOwnID
"
,
({
detail
:
id
})
=>
{
...
...
@@ -152,133 +43,112 @@ function handleRoomConnectionEstablished(room) {
}
})
const
pathElems
=
new
Map
()
room
.
addEventListener
(
"
addOrUpdatePath
"
,
({
detail
:
{
id
,
points
}
})
=>
{
addOrUpdatePathElem
(
pathElems
,
id
,
points
)
canvas
.
renderPath
(
id
,
points
)
})
}
let
currentTool
=
tools
.
PEN
const
pathIDsByPointerID
=
new
Map
()
const
tryRoomConnect
=
async
(
roomID
)
=>
{
return
await
connect
(
roomID
)
.
then
(
onRoomConnect
)
.
catch
((
err
)
=>
alert
(
`Error connecting to a room:\n
${
err
}
`
))
}
const
canvasOnPointerEnter
=
(
e
)
=>
{
if
(
~
e
.
buttons
&
1
)
{
return
}
const
ERASER_RADIUS
=
canvas
.
STROKE_RADIUS
*
2
const
tools
=
{
PEN
:
Symbol
(
"
pen
"
),
ERASER
:
Symbol
(
"
eraser
"
),
}
let
currentTool
=
tools
.
PEN
const
pathIDsByPointerID
=
new
Map
()
const
mousePos
=
[
e
.
offsetX
,
e
.
offsetY
]
HTML
.
penButton
.
addEventListener
(
"
click
"
,
()
=>
{
currentTool
=
tools
.
PEN
HTML
.
penButton
.
classList
.
add
(
"
selected
"
)
HTML
.
eraserButton
.
classList
.
remove
(
"
selected
"
)
})
if
(
currentTool
==
tools
.
PEN
)
{
pathIDsByPointerID
.
set
(
e
.
pointerId
,
room
.
addPath
(
mousePos
))
}
else
if
(
currentTool
==
tools
.
ERASER
)
{
room
.
getPaths
().
forEach
((
points
,
pathID
)
=>
{
points
.
forEach
((
point
,
i
)
=>
{
if
(
getDistance
(
mousePos
,
point
)
<=
ERASERRADIUS
)
{
room
.
erasePoint
(
pathID
,
i
)
}
})
})
}
}
HTML
.
eraserButton
.
addEventListener
(
"
click
"
,
()
=>
{
currentTool
=
tools
.
ERASER
HTML
.
penButton
.
classList
.
remove
(
"
selected
"
)
HTML
.
eraserButton
.
classList
.
add
(
"
selected
"
)
})
const
canvasOnPointerLeave
=
(
e
)
=>
{
pathIDsByPointerID
.
delete
(
e
.
pointerId
)
HTML
.
peerButton
.
addEventListener
(
"
click
"
,
()
=>
{
const
peerID
=
HTML
.
peerIDElem
.
value
if
(
room
==
null
||
peerID
==
""
)
{
return
}
room
.
inviteUser
(
peerID
)
HTML
.
peerIDElem
.
value
=
""
})
const
canvasOnPointerMove
=
(
e
)
=>
{
if
(
~
e
.
buttons
&
1
)
{
return
}
const
mousePos
=
[
e
.
offsetX
,
e
.
offsetY
]
if
(
currentTool
==
tools
.
PEN
)
{
room
.
extendPath
(
pathIDsByPointerID
.
get
(
e
.
pointerId
),
mousePos
)
}
else
if
(
currentTool
==
tools
.
ERASER
)
{
room
.
getPaths
().
forEach
((
points
,
pathID
)
=>
{
points
.
forEach
((
point
,
i
)
=>
{
if
(
getDistance
(
mousePos
,
point
)
<=
ERASERRADIUS
)
{
room
.
erasePoint
(
pathID
,
i
)
}
})
})
}
HTML
.
roomConnectButton
.
addEventListener
(
"
click
"
,
()
=>
{
const
selectedRoomID
=
HTML
.
roomIDElem
.
value
if
(
!
selectedRoomID
||
selectedRoomID
==
room
.
name
)
{
return
}
const
peerButtonOnClick
=
()
=>
{
const
peerID
=
HTML
.
peerIDElem
.
value
if
(
peerID
==
""
)
{
return
}
room
.
inviteUser
(
peerID
)
HTML
.
peerIDElem
.
value
=
""
if
(
room
!=
null
)
{
room
.
disconnect
()
room
=
null
}
c
onst
penButtonOnClick
=
()
=>
{
currentTool
=
tools
.
PEN
c
anvas
.
clear
()
HTML
.
connectedPeers
.
innerHTML
=
"
No peers are connected
"
HTML
.
penButton
.
classList
.
add
(
"
selected
"
)
HTML
.
eraserButton
.
classList
.
remove
(
"
selected
"
)
}
tryRoomConnect
(
selectedRoomID
)
})
const
eraserButtonOnClick
=
()
=>
{
currentTool
=
tools
.
ERASER
const
getDistance
=
(
a
,
b
)
=>
{
return
Math
.
sqrt
(
(
a
[
0
]
-
b
[
0
])
*
(
a
[
0
]
-
b
[
0
])
+
(
a
[
1
]
-
b
[
1
])
*
(
a
[
1
]
-
b
[
1
]),
)
}
HTML
.
penButton
.
classList
.
remove
(
"
selected
"
)
HTML
.
eraserButton
.
classList
.
add
(
"
selected
"
)
const
erasePoint
=
([
x
,
y
])
=>
{
if
(
room
==
null
)
{
return
}
room
.
getPaths
().
forEach
((
points
,
pathID
)
=>
{
points
.
forEach
((
point
,
i
)
=>
{
if
(
getDistance
([
x
,
y
],
point
)
<=
ERASER_RADIUS
)
{
room
.
erasePoint
(
pathID
,
i
)
}
})
})
}
HTML
.
canvas
.
addEventListener
(
"
pointerdown
"
,
canvasOnPointerEnter
)
HTML
.
canvas
.
addEventListener
(
"
pointerenter
"
,
canvasOnPointerEnter
)
HTML
.
canvas
.
addEventListener
(
"
pointerup
"
,
canvasOnPointerLeave
)
HTML
.
canvas
.
addEventListener
(
"
pointerleave
"
,
canvasOnPointerLeave
)
HTML
.
canvas
.
addEventListener
(
"
pointermove
"
,
canvasOnPointerMove
)
HTML
.
penButton
.
addEventListener
(
"
click
"
,
penButtonOnClick
)
HTML
.
eraserButton
.
addEventListener
(
"
click
"
,
eraserButtonOnClick
)
HTML
.
peerButton
.
addEventListener
(
"
click
"
,
peerButtonOnClick
)
HTML
.
roomConnectButton
.
removeEventListener
(
"
click
"
,
handleRoomConnectClick
)
const
roomConnectButtonOnClick
=
()
=>
{
const
selectedRoomID
=
HTML
.
roomIDElem
.
value
if
(
!
selectedRoomID
||
selectedRoomID
==
room
.
name
)
{
return
}
HTML
.
canvas
.
removeEventListener
(
"
pointerdown
"
,
canvasOnPointerEnter
)
HTML
.
canvas
.
removeEventListener
(
"
pointerenter
"
,
canvasOnPointerEnter
)
HTML
.
canvas
.
removeEventListener
(
"
pointerup
"
,
canvasOnPointerLeave
)
HTML
.
canvas
.
removeEventListener
(
"
pointerleave
"
,
canvasOnPointerLeave
)
HTML
.
canvas
.
removeEventListener
(
"
pointermove
"
,
canvasOnPointerMove
)
HTML
.
peerButton
.
removeEventListener
(
"
click
"
,
peerButtonOnClick
)
HTML
.
penButton
.
removeEventListener
(
"
click
"
,
penButtonOnClick
)
HTML
.
eraserButton
.
removeEventListener
(
"
click
"
,
eraserButtonOnClick
)
HTML
.
roomConnectButton
.
removeEventListener
(
"
click
"
,
roomConnectButtonOnClick
,
)
canvas
.
input
.
addEventListener
(
"
strokestart
"
,
({
detail
:
e
})
=>
{
if
(
room
==
null
)
{
return
}
room
.
disconnect
()
room
=
null
const
mousePos
=
[
e
.
offsetX
,
e
.
offsetY
]
handleRoomConnectClick
()
if
(
currentTool
==
tools
.
PEN
)
{
pathIDsByPointerID
.
set
(
e
.
pointerId
,
room
.
addPath
(
mousePos
))
}
else
if
(
currentTool
==
tools
.
ERASER
)
{
erasePoint
(
mousePos
)
}
HTML
.
roomConnectButton
.
addEventListener
(
"
click
"
,
roomConnectButtonOnClick
)
}
})
function
handleRoomConnectionError
(
err
)
{
alert
(
`Error connecting to a room:\n
${
err
}
`
)
}
canvas
.
input
.
addEventListener
(
"
strokeend
"
,
({
detail
:
e
})
=>
{
pathIDsByPointerID
.
delete
(
e
.
pointerId
)
}
)
function
connectToARoom
(
roomID
)
{
connect
(
roomID
)
.
then
(
handleRoomConnectionEstablished
)
.
catch
(
handleRoomConnectionError
)
}
canvas
.
input
.
addEventListener
(
"
strokemove
"
,
({
detail
:
e
})
=>
{
if
(
room
==
null
)
{
return
}
HTML
.
canvas
.
addEventListener
(
"
touchmove
"
,
(
e
)
=>
e
.
preventDefault
())
const
mousePos
=
[
e
.
offsetX
,
e
.
offsetY
]
HTML
.
roomConnectButton
.
addEventListener
(
"
click
"
,
handleRoomConnectClick
,
{
once
:
true
,
if
(
currentTool
==
tools
.
PEN
)
{
room
.
extendPath
(
pathIDsByPointerID
.
get
(
e
.
pointerId
),
mousePos
)
}
else
if
(
currentTool
==
tools
.
ERASER
)
{
erasePoint
(
mousePos
)
}
})
connectToARoom
(
TEST_ROOM
)
tryRoomConnect
(
TEST_ROOM
)
This diff is collapsed.
Click to expand it.
src/canvas.js
0 → 100644
+
123
−
0
View file @
6e4359ca
// 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
{
canvas
}
from
"
./elements.js
"
// TODO: switch to curve interpolation that respects mouse points based on velocity
const
lineFn
=
line
()
.
x
((
d
)
=>
d
[
0
])
.
y
((
d
)
=>
d
[
1
])
.
curve
(
curveLinear
)
const
pathElems
=
new
Map
()
export
const
STROKE_COLOUR
=
"
blue
"
export
const
STROKE_RADIUS
=
2
export
const
input
=
new
EventTarget
()
export
const
renderPath
=
(
id
,
points
)
=>
{
let
pathElem
=
pathElems
.
get
(
id
)
if
(
pathElem
==
null
)
{
pathElem
=
document
.
createElementNS
(
"
http://www.w3.org/2000/svg
"
,
"
g
"
)
pathElem
.
setAttribute
(
"
stroke
"
,
STROKE_COLOUR
)
pathElem
.
setAttribute
(
"
stroke-width
"
,
STROKE_RADIUS
*
2
)
pathElem
.
setAttribute
(
"
fill
"
,
"
none
"
)
pathElem
.
setAttribute
(
"
pointer-events
"
,
"
none
"
)
pathElem
.
setAttribute
(
"
marker-start
"
,
"
url(#dot)
"
)
pathElem
.
setAttribute
(
"
marker-end
"
,
"
url(#dot)
"
)
canvas
.
appendChild
(
pathElem
)
pathElems
.
set
(
id
,
pathElem
)
}
pathElem
.
innerHTML
=
""
if
(
points
.
length
==
0
)
{
return
pathElem
}
// Push a fake path split to generate the last path
points
.
push
([
-
1
,
-
1
,
false
])
let
subpath
=
[]
for
(
const
point
of
points
)
{
if
(
point
[
0
]
===
undefined
)
{
continue
}
if
(
point
[
2
]
===
false
)
{
if
(
subpath
.
length
==
1
)
{
const
subpathElem
=
document
.
createElementNS
(
"
http://www.w3.org/2000/svg
"
,
"
circle
"
,
)
subpathElem
.
setAttribute
(
"
stroke
"
,
"
none
"
)
subpathElem
.
setAttribute
(
"
fill
"
,
STROKE_COLOUR
)
subpathElem
.
setAttribute
(
"
cx
"
,
subpath
[
0
][
0
])
subpathElem
.
setAttribute
(
"
cy
"
,
subpath
[
0
][
1
])
subpathElem
.
setAttribute
(
"
r
"
,
STROKE_RADIUS
)
pathElem
.
appendChild
(
subpathElem
)
}
else
if
(
subpath
.
length
>
0
)
{
const
subpathElem
=
document
.
createElementNS
(
"
http://www.w3.org/2000/svg
"
,
"
path
"
,
)
subpathElem
.
setAttribute
(
"
d
"
,
lineFn
(
subpath
))
pathElem
.
appendChild
(
subpathElem
)
}
subpath
=
[]
continue
}
subpath
.
push
(
point
)
}
return
pathElem
}
export
const
clear
=
()
=>
{
while
(
canvas
.
children
[
1
])
{
canvas
.
removeChild
(
canvas
.
children
[
1
])
}
}
// Note that the PointerEvent is passed as the detail in these 'stroke events'.
canvas
.
addEventListener
(
"
pointerdown
"
,
(
e
)
=>
{
if
(
e
.
buttons
&
1
)
{
input
.
dispatchEvent
(
new
CustomEvent
(
"
strokestart
"
,
{
detail
:
e
}))
}
})
canvas
.
addEventListener
(
"
pointerenter
"
,
(
e
)
=>
{
if
(
e
.
buttons
&
1
)
{
input
.
dispatchEvent
(
new
CustomEvent
(
"
strokestart
"
,
{
detail
:
e
}))
}
})
canvas
.
addEventListener
(
"
pointerup
"
,
(
e
)
=>
{
if
(
e
.
buttons
&
1
)
{
input
.
dispatchEvent
(
new
CustomEvent
(
"
strokeend
"
,
{
detail
:
e
}))
}
})
canvas
.
addEventListener
(
"
pointerleave
"
,
(
e
)
=>
{
if
(
e
.
buttons
&
1
)
{
input
.
dispatchEvent
(
new
CustomEvent
(
"
strokeend
"
,
{
detail
:
e
}))
}
})
canvas
.
addEventListener
(
"
pointermove
"
,
(
e
)
=>
{
if
(
e
.
buttons
&
1
)
{
input
.
dispatchEvent
(
new
CustomEvent
(
"
strokemove
"
,
{
detail
:
e
}))
}
})
canvas
.
addEventListener
(
"
touchmove
"
,
(
e
)
=>
e
.
preventDefault
())
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment