Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
Stud.IP
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Jan-Hendrik Willms
Stud.IP
Commits
61d8f52b
Commit
61d8f52b
authored
2 years ago
by
Viktoria Wiebe
Committed by
Marcus Eibrink-Lunzenauer
2 years ago
Browse files
Options
Downloads
Patches
Plain Diff
CW ImageMapBlock: Implement dragging functionality, closes #1136
Closes #1136 Merge request
studip/studip!1088
parent
330ddc50
No related branches found
No related tags found
No related merge requests found
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
resources/assets/stylesheets/scss/courseware.scss
+32
-1
32 additions, 1 deletion
resources/assets/stylesheets/scss/courseware.scss
resources/vue/components/courseware/CoursewareImageMapBlock.vue
+299
-67
299 additions, 67 deletions
...ces/vue/components/courseware/CoursewareImageMapBlock.vue
with
331 additions
and
68 deletions
resources/assets/stylesheets/scss/courseware.scss
+
32
−
1
View file @
61d8f52b
...
...
@@ -110,6 +110,8 @@ $media-buttons: (
next
:
arr_eol-right
);
$cw-wrapper-gap
:
0
.5em
;
/* * * * * * * *
c o n t e n t s
* * * * * * * * */
...
...
@@ -954,12 +956,13 @@ form.cw-container-dialog-edit-form {
width
:
100%
;
.cw-block-content
{
overflow
:
auto
;
position
:
relative
;
}
}
.cw-content-wrapper-active
{
border
:
solid
thin
$content-color-40
;
.cw-block-content
{
padding
:
0
.5em
;
padding
:
$cw-wrapper-gap
;
}
}
.cw-container-wrapper-discuss
{
...
...
@@ -1027,6 +1030,27 @@ form.cw-container-dialog-edit-form {
margin-left
:
10px
;
}
.cw-draggable-shapes-wrapper
{
position
:
absolute
;
top
:
0px
;
left
:
0px
;
width
:
100%
;
height
:
100%
;
margin
:
$cw-wrapper-gap
;
.cw-draggable-area
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
text-align
:
center
;
&
:hover
{
cursor
:
grab
;
}
}
}
@media
only
screen
and
(
max-width
:
1820px
)
{
.cw-structural-element
.cw-container-wrapper.cw-container-wrapper-discuss
{
max-width
:
1095px
;
...
...
@@ -1727,6 +1751,7 @@ $icons: (
&
.cw-tab-active
{
display
:
block
;
height
:
unset
;
padding
:
4px
8px
;
}
form
.default
{
...
...
@@ -4069,6 +4094,12 @@ i m a g e m a p b l o c k
.cw-image-map-original-img
{
display
:
none
;
}
form
.default
{
label
.cw-block-image-map-dimensions
>
input
[
type
=
number
]
{
display
:
inline-block
;
}
}
}
/* * * * * * * * * * * * * * * * *
...
...
This diff is collapsed.
Click to expand it.
resources/vue/components/courseware/CoursewareImageMapBlock.vue
+
299
−
67
View file @
61d8f52b
<
template
>
<div
class=
"cw-block cw-block-image-map"
>
<div
class=
"cw-block cw-block-image-map"
@
mousedown=
"selectShape"
>
<courseware-default-block
:block=
"block"
:canEdit=
"canEdit"
...
...
@@ -35,6 +35,38 @@
"
/>
</map>
<div
v-if=
"showEditMode && viewMode === 'edit' && currentShapes.length > 0"
ref=
"draggableShapeWrapper"
class=
"cw-draggable-shapes-wrapper"
>
<vue-resizeable
v-for=
"(shape, index) in currentShapes"
:key=
"index"
:index=
"index"
style=
"position: absolute"
ref=
"resizableAreaComponents"
:fitParent=
"true"
:dragSelector=
"dragSelector"
:active=
"handlers"
:left=
"getShapeOffsetLeft(shape)"
:top=
"getShapeOffsetTop(shape)"
:width=
"getShapeWidth(shape)"
:height=
"getShapeHeight(shape)"
@
resize:start=
"dragStartHandler"
@
resize:end=
"endDraggingShape"
@
drag:start=
"dragStartHandler"
@
drag:end=
"endDraggingShape"
>
<div
class=
"cw-draggable-area"
:style=
"
{
backgroundColor: getColorRGBA(shape.data.color),
color: shape.data.textcolor ? getColorRGBA(shape.data.textcolor) : '',
borderRadius: getShapeBorderRadius(shape),
border: getShapeBorder(shape),
cursor: selectedShapeIndex !== false ? 'grabbing' : '',
}"
@click="followLink(index)">
{{
shape
.
data
.
text
}}
</div>
</vue-resizeable>
</div>
</
template
>
<
template
v-if=
"canEdit"
#edit
>
<form
class=
"default"
@
submit.prevent=
""
>
...
...
@@ -56,7 +88,7 @@
v-for=
"(shape, index) in currentShapes"
:key=
"index"
:index=
"index"
:name=
"shape.title"
:name=
"shape.title
? shape.title : ''
"
:icon=
"shape.title === '' ? 'link-extern' : ''"
:selected=
"index === 0"
>
...
...
@@ -68,6 +100,7 @@
:reduce=
"color => color.class"
:clearable=
"false"
v-model=
"shape.data.color"
@
input=
"drawScreen"
>
<template
#open-indicator
="
selectAttributes
"
>
<span
v-bind=
"selectAttributes"
><studip-icon
shape=
"arr_1down"
size=
"10"
/></span>
...
...
@@ -113,6 +146,30 @@
<translate>
Beschriftung
</translate>
<input
type=
"text"
v-model=
"shape.data.text"
@
change=
"drawScreen"
/>
</label>
<label>
<translate>
Textfarbe
</translate>
<studip-select
:options=
"colors"
label=
"name"
:reduce=
"color => color.class"
:clearable=
"false"
v-model=
"shape.data.textcolor"
@
input=
"drawScreen"
>
<
template
#open-indicator=
"selectAttributes"
>
<span
v-bind=
"selectAttributes"
><studip-icon
shape=
"arr_1down"
size=
"10"
/></span>
</
template
>
<
template
#no-options
>
<translate>
Es steht keine Auswahl zur Verfügung.
</translate>
</
template
>
<
template
#selected-option=
"{name, rgba}"
>
<span
class=
"vs__option-color"
:style=
"
{'background-color': rgba}">
</span><span>
{{
name
}}
</span>
</
template
>
<
template
#option=
"{name, rgba}"
>
<span
class=
"vs__option-color"
:style=
"
{'background-color': rgba}">
</span><span>
{{
name
}}
</span>
</
template
>
</studip-select>
</label>
<label>
<translate>
Art des Links
</translate>
<select
v-model=
"shape.link_type"
>
...
...
@@ -161,6 +218,7 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
import
CoursewareFileChooser
from
'
./CoursewareFileChooser.vue
'
;
import
CoursewareTabs
from
'
./CoursewareTabs.vue
'
;
import
CoursewareTab
from
'
./CoursewareTab.vue
'
;
import
VueResizeable
from
'
vrp-vue-resizable
'
;
import
{
blockMixin
}
from
'
./block-mixin.js
'
;
import
{
mapActions
,
mapGetters
}
from
'
vuex
'
;
...
...
@@ -172,6 +230,7 @@ export default {
CoursewareFileChooser
,
CoursewareTabs
,
CoursewareTab
,
VueResizeable
,
},
props
:
{
block
:
Object
,
...
...
@@ -201,7 +260,14 @@ export default {
{
name
:
this
.
$gettext
(
'
Dunkelgrau
'
),
class
:
'
darkgrey
'
,
rgba
:
'
rgba(52,73,94,1)
'
},
{
name
:
this
.
$gettext
(
'
Schwarz
'
),
class
:
'
black
'
,
rgba
:
'
rgba(0,0,0,1)
'
}
],
file
:
null
file
:
null
,
dragSelector
:
"
.cw-draggable-area
"
,
handlers
:
[
"
r
"
,
"
rb
"
,
"
b
"
,
"
lb
"
,
"
l
"
,
"
lt
"
,
"
t
"
,
"
rt
"
],
draggedShapeWidth
:
50
,
draggedShapeHeight
:
50
,
selectedShapeIndex
:
false
,
draggingActive
:
false
,
showEditMode
:
false
,
};
},
computed
:
{
...
...
@@ -209,6 +275,7 @@ export default {
courseware
:
'
courseware-structural-elements/all
'
,
fileRefById
:
'
file-refs/byId
'
,
urlHelper
:
'
urlHelper
'
,
viewMode
:
'
viewMode
'
,
}),
fileId
()
{
return
this
.
block
?.
attributes
?.
payload
?.
file_id
;
...
...
@@ -232,7 +299,8 @@ export default {
updateBlock
:
'
updateBlockInContainer
'
,
loadFileRef
:
'
file-refs/loadById
'
,
}),
async
initCurrentData
()
{
async
initCurrentData
(
event
)
{
this
.
showEditMode
=
Boolean
(
event
);
this
.
currentFileId
=
this
.
fileId
;
this
.
currentShapes
=
JSON
.
parse
(
JSON
.
stringify
(
this
.
shapes
));
await
this
.
loadFile
();
...
...
@@ -307,7 +375,9 @@ export default {
drawShapes
()
{
let
context
=
this
.
context
;
let
view
=
this
;
this
.
currentShapes
.
forEach
((
value
)
=>
{
this
.
currentShapes
.
forEach
((
value
,
index
)
=>
{
// skip the selected shape when redrawing so it disappears while dragging the shape
if
(
this
.
selectedShapeIndex
!==
index
)
{
let
shape
=
value
;
let
text
=
shape
.
data
.
text
;
let
shape_width
=
0
;
...
...
@@ -360,11 +430,15 @@ export default {
text
=
view
.
fitTextToShape
(
context
,
text
,
shape_width
);
context
.
textAlign
=
'
center
'
;
context
.
font
=
'
14px Arial
'
;
if
(
shape
.
data
.
textcolor
)
{
context
.
fillStyle
=
this
.
getColorRGBA
(
shape
.
data
.
textcolor
);
}
else
{
if
(
view
.
darkColors
.
indexOf
(
shape
.
data
.
color
)
>
-
1
)
{
context
.
fillStyle
=
'
#ffffff
'
;
}
else
{
context
.
fillStyle
=
'
#000000
'
;
}
}
let
lineHeight
=
shape_height
/
(
text
.
length
+
1
);
text
.
forEach
((
value
,
key
)
=>
{
context
.
fillText
(
value
,
text_X
,
text_Y
+
lineHeight
*
(
key
+
1
));
...
...
@@ -372,6 +446,7 @@ export default {
}
context
.
closePath
();
}
});
},
fitTextToShape
(
context
,
text
,
shapeWidth
)
{
...
...
@@ -512,6 +587,7 @@ export default {
},
removeShape
(
index
)
{
this
.
currentShapes
.
splice
(
index
,
1
);
this
.
drawScreen
();
},
fixUrl
(
index
)
{
let
url
=
this
.
currentShapes
[
index
].
target_external
;
...
...
@@ -520,6 +596,162 @@ export default {
}
this
.
currentShapes
[
index
].
target_external
=
url
;
},
dragStartHandler
(
data
)
{
// redraw screen now that a shape was selected so that it disappears while dragging or resizing
this
.
drawScreen
();
},
selectShape
(
data
)
{
// set current draggable div shape to canvas shape coordinates
let
canvas
=
this
.
$refs
.
image_from_canvas
;
let
canvasSpecs
=
canvas
.
getBoundingClientRect
();
let
mouseX
=
(
data
.
clientX
-
canvasSpecs
.
left
)
*
(
canvas
.
width
/
canvasSpecs
.
width
);
let
mouseY
=
(
data
.
clientY
-
canvasSpecs
.
top
)
*
(
canvas
.
height
/
canvasSpecs
.
height
);
this
.
currentShapes
.
forEach
((
value
,
key
)
=>
{
let
shape
=
value
;
// if the event target is the draggable area, check for the shape area normally
// else check if the click was on a resizable area that belongs to a shape since
// resizable areas are partly outside the shape
if
(
data
.
target
.
classList
.
contains
(
'
cw-draggable-area
'
))
{
if
(
this
.
mouseHit
(
mouseX
,
mouseY
,
shape
))
{
this
.
selectedShapeIndex
=
key
;
}
}
else
{
mouseX
=
data
.
target
.
parentElement
.
offsetLeft
;
mouseY
=
data
.
target
.
parentElement
.
offsetTop
;
if
(
shape
.
type
==
'
arc
'
)
{
mouseX
+=
shape
.
data
.
radius
;
mouseY
+=
shape
.
data
.
radius
;
}
if
(
shape
.
type
==
'
rect
'
)
{
mouseX
+=
shape
.
data
.
width
/
2
;
mouseY
+=
shape
.
data
.
height
/
2
;
}
if
(
shape
.
type
==
'
ellipse
'
)
{
mouseX
+=
shape
.
data
.
radiusX
;
mouseY
+=
shape
.
data
.
radiusY
;
}
if
(
this
.
mouseHit
(
mouseX
,
mouseY
,
shape
))
{
this
.
selectedShapeIndex
=
key
;
}
}
});
},
endDraggingShape
(
data
)
{
this
.
draggingActive
=
true
;
// transfer div shape data to canvas according to shape
let
shape
=
this
.
currentShapes
[
this
.
selectedShapeIndex
];
if
(
shape
.
type
==
'
arc
'
)
{
let
circle_width
=
data
.
width
!=
shape
.
data
.
radius
*
2
?
data
.
width
:
data
.
height
;
// if the shape was clicked and not dragged, set the dragging status to false to follow the link
if
(
shape
.
data
.
centerX
==
data
.
left
+
shape
.
data
.
radius
||
shape
.
data
.
centerY
==
data
.
top
+
shape
.
data
.
radius
)
{
this
.
draggingActive
=
false
;
}
shape
.
data
.
radius
=
circle_width
/
2
;
shape
.
data
.
centerX
=
data
.
left
+
shape
.
data
.
radius
;
shape
.
data
.
centerY
=
data
.
top
+
shape
.
data
.
radius
;
}
if
(
shape
.
type
==
'
rect
'
)
{
if
(
shape
.
data
.
X
==
data
.
left
||
shape
.
data
.
Y
==
data
.
top
)
{
this
.
draggingActive
=
false
;
}
shape
.
data
.
X
=
data
.
left
;
shape
.
data
.
Y
=
data
.
top
;
shape
.
data
.
width
=
data
.
width
;
shape
.
data
.
height
=
data
.
height
;
}
if
(
shape
.
type
==
'
ellipse
'
)
{
if
(
shape
.
data
.
X
==
data
.
left
+
shape
.
data
.
radiusX
||
shape
.
data
.
Y
==
data
.
top
+
shape
.
data
.
radiusY
)
{
this
.
draggingActive
=
false
;
}
shape
.
data
.
radiusX
=
data
.
width
/
2
;
shape
.
data
.
radiusY
=
data
.
height
/
2
;
shape
.
data
.
X
=
data
.
left
+
shape
.
data
.
radiusX
;
shape
.
data
.
Y
=
data
.
top
+
shape
.
data
.
radiusY
;
}
// unselect shape to stop skipping the selected shape when drawing the canvas
this
.
selectedShapeIndex
=
false
;
this
.
drawScreen
();
},
mouseHit
(
mouseX
,
mouseY
,
shape
)
{
// check if the mouseclick was on a shape and return true if it was
if
(
shape
.
type
==
'
arc
'
)
{
let
dx
=
shape
.
data
.
centerX
-
mouseX
;
let
dy
=
shape
.
data
.
centerY
-
mouseY
;
return
(
dx
*
dx
+
dy
*
dy
<
shape
.
data
.
radius
*
shape
.
data
.
radius
);
}
if
((
shape
.
type
==
'
rect
'
)
||
(
shape
.
type
==
'
text
'
))
{
let
dx
=
mouseX
-
shape
.
data
.
X
;
let
dy
=
mouseY
-
shape
.
data
.
Y
;
return
((
dx
<=
shape
.
data
.
width
)
&&
(
dy
<=
shape
.
data
.
height
)
&&
(
dx
>=
0
)
&&
(
dy
>=
0
));
}
if
(
shape
.
type
==
'
ellipse
'
)
{
let
dx
=
shape
.
data
.
X
-
mouseX
;
let
dy
=
shape
.
data
.
Y
-
mouseY
;
return
((
Math
.
abs
(
dx
)
<
shape
.
data
.
radiusX
)
&&
(
Math
.
abs
(
dy
)
<
shape
.
data
.
radiusY
));
}
},
getColorRGBA
(
color
)
{
return
this
.
colors
.
filter
((
col
)
=>
{
return
col
.
class
===
color
})[
0
].
rgba
;
},
getShapeBorder
(
shape
)
{
return
shape
.
data
.
color
===
'
transparent
'
?
'
dashed thin #000
'
:
'
none
'
;
},
getShapeBorderRadius
(
shape
)
{
if
(
shape
.
type
==
'
rect
'
)
{
return
0
;
}
else
{
return
'
50%
'
;
}
},
getShapeOffsetLeft
(
shape
)
{
if
(
shape
.
type
==
'
arc
'
)
{
return
parseInt
(
shape
.
data
.
centerX
-
shape
.
data
.
radius
);
}
if
(
shape
.
type
==
'
rect
'
)
{
return
parseInt
(
shape
.
data
.
X
);
}
if
(
shape
.
type
==
'
ellipse
'
)
{
return
parseInt
(
shape
.
data
.
X
)
-
shape
.
data
.
radiusX
;
}
},
getShapeOffsetTop
(
shape
)
{
if
(
shape
.
type
==
'
arc
'
)
{
return
parseInt
(
shape
.
data
.
centerY
-
shape
.
data
.
radius
);
}
if
(
shape
.
type
==
'
rect
'
)
{
return
parseInt
(
shape
.
data
.
Y
);
}
if
(
shape
.
type
==
'
ellipse
'
)
{
return
parseInt
(
shape
.
data
.
Y
)
-
shape
.
data
.
radiusY
;
}
},
getShapeWidth
(
shape
)
{
if
(
shape
.
type
==
'
arc
'
)
{
return
parseInt
(
shape
.
data
.
radius
*
2
);
}
if
(
shape
.
type
==
'
rect
'
)
{
return
parseInt
(
shape
.
data
.
width
);
}
if
(
shape
.
type
==
'
ellipse
'
)
{
return
parseInt
(
shape
.
data
.
radiusX
*
2
);
}
},
getShapeHeight
(
shape
)
{
if
(
shape
.
type
==
'
arc
'
)
{
return
parseInt
(
shape
.
data
.
radius
*
2
);
}
if
(
shape
.
type
==
'
rect
'
)
{
return
parseInt
(
shape
.
data
.
height
);
}
if
(
shape
.
type
==
'
ellipse
'
)
{
return
parseInt
(
shape
.
data
.
radiusY
*
2
);
}
},
followLink
(
index
)
{
if
(
!
this
.
draggingActive
)
{
this
.
$refs
.
map
.
areas
[
index
].
click
();
}
},
}
};
</
script
>
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