Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
GuiΩ
ActivitesMentales_Django
Commits
61b0be87
Commit
61b0be87
authored
Jun 29, 2022
by
GuiΩ
Browse files
feat(modal): Modal is draggable and resizable
parent
11e80a01
Changes
7
Hide whitespace changes
Inline
Side-by-side
frontend/src/components/CodeEditor.vue
View file @
61b0be87
...
...
@@ -11,11 +11,15 @@
-->
<
script
lang=
"ts"
setup
>
import
{
ref
}
from
"
vue
"
;
interface
ICodeEditorProps
{
code
?:
string
,
tabsize
?:
number
}
const
codeEditor
=
ref
<
HTMLAreaElement
|
null
>
(
null
)
const
emit
=
defineEmits
([
'
input
'
,
'
update:code
'
])
const
props
=
withDefaults
(
defineProps
<
ICodeEditorProps
>
(),
{
...
...
@@ -97,17 +101,36 @@
break
}
}
function
setHeight
(
height
:
string
)
{
codeEditor
.
value
!
.
style
.
height
=
height
}
function
setWidth
(
width
:
string
)
{
codeEditor
.
value
!
.
style
.
width
=
width
}
defineExpose
({
setHeight
,
setWidth
})
</
script
>
<
template
lang=
"pug"
>
textarea(
//.CodeEditor__shadowArea(
// @keydown="handleKeyDown"
// @input="handleInput")
{{
props
.
code
}}
textarea.CodeEditor__codeArea(
ref="codeEditor"
:value="props.code"
cols="70"
rows="30"
spellcheck="false"
@keydown="handleKeyDown"
@input="handleInput")/
</
template
>
<
style
>
<
style
lang=
"sass"
>
.CodeEditor__codeArea
z-index
:
0
resize
:
none
position
:
relative
.CodeEditor__shadowArea
z-index
:
-1
</
style
>
\ No newline at end of file
frontend/src/components/MessageBox.vue
View file @
61b0be87
...
...
@@ -10,15 +10,17 @@ Composant Vuejs qui affiche une fenêtre modale contenant un message
-->
<
script
lang=
"ts"
setup
>
import
{
computed
,
nextTick
,
reactive
,
ref
}
from
"
vue
"
;
import
{
computed
,
nextTick
,
reactive
,
ref
}
from
"
vue
"
;
import
Modal
from
"
@/components/Modal.vue
"
;
const
bottom
=
ref
<
HTMLElement
|
null
>
(
null
)
const
modal
=
ref
<
InstanceType
<
typeof
Modal
>
|
null
>
(
null
)
const
body
=
ref
<
HTMLDivElement
|
null
>
(
null
)
const
bottom
=
ref
<
HTMLElement
|
null
>
(
null
)
const
state
=
reactive
({
title
:
''
,
message
:
''
,
// message à afficher
isVisible
:
false
,
// la fenêtre est-elle affichée ?
})
...
...
@@ -35,58 +37,77 @@ const bottom = ref<HTMLElement | null>(null)
async
function
show
(
title
:
string
,
message
:
string
)
{
state
.
title
=
title
state
.
message
=
message
state
.
isVisible
=
true
modal
.
value
!
.
show
()
await
nextTick
()
bottom
.
value
?.
scrollIntoView
()
}
function
close
()
{
modal
.
value
!
.
close
()
}
function
onResize
({
width
:
newWidth
,
height
:
newHeight
}:
{
width
:
number
,
height
:
number
})
{
console
.
debug
(
'
MB.onResize
'
,
newWidth
,
newHeight
)
body
.
value
!
.
style
.
height
=
(
newHeight
-
129
)
+
'
px
'
}
defineExpose
({
show
})
</
script
>
<
template
lang=
"pug"
>
Modal.MessageBox_modal(v-show="state.isVisible" @close="state.isVisible = false")
Modal(
ref="modal"
@resize="onResize")
template(v-slot:title)
{{
state
.
title
}}
template(v-slot:body)
.MessageBox__body(ref="body")
pre(v-if="! html.value")
{{
state
.
message
}}
span(v-if="html.value" v-html="state.message")
div(ref="bottom")
//transition(v-if="visible")
// .message-mask(@click="visible = false")
// .message-wrapper
.message-container(data-textid="message")
pre(v-if="! html.value")
{{
state
.
message
}}
span(v-if="html.value" v-html="state.message")
div(ref="bottom")
template(v-slot:footer)
button(data-testid="cancel" type="button" @click="close") Fermer
</
template
>
<
style
>
.message-mask
{
position
:
fixed
;
z-index
:
9998
;
top
:
0
;
left
:
0
;
width
:
100%
;
height
:
100%
;
background-color
:
rgba
(
0
,
0
,
0
,
0.5
);
display
:
table
;
transition
:
opacity
0.3s
ease
;
}
.message-wrapper
{
vertical-align
:
middle
;
display
:
table-cell
;
width
:
100%
;
}
.message-container
{
overflow-y
:
scroll
;
max-width
:
40%
;
max-height
:
600px
;
margin
:
0
auto
;
padding
:
20px
30px
;
background-color
:
#fff
;
border-radius
:
2px
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0.33
);
transition
:
all
0.3s
ease
;
font-family
:
Helvetica
,
Arial
,
sans-serif
;
}
<
style
lang=
"sass"
>
.MessageBox__body
overflow
:
auto
/*.message-mask {*/
/* position: fixed;*/
/* z-index: 9998;*/
/* top: 0;*/
/* left: 0;*/
/* width: 100%;*/
/* height: 100%;*/
/* background-color: rgba(0, 0, 0, 0.5);*/
/* display: table;*/
/* transition: opacity 0.3s ease;*/
/* overflow: auto;*/
/*}*/
/*.message-wrapper {*/
/* vertical-align: middle;*/
/* display: table-cell;*/
/* width: 100%;*/
/*}*/
/*.message-container {*/
/* overflow-y: scroll;*/
/* max-width: 40%;*/
/* max-height: 600px;*/
/* margin: 0 auto;*/
/* padding: 20px 30px;*/
/* background-color: #fff;*/
/* border-radius: 2px;*/
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);*/
/* transition: all 0.3s ease;*/
/* font-family: Helvetica, Arial, sans-serif;*/
/*}*/
</
style
>
frontend/src/components/Modal.vue
View file @
61b0be87
...
...
@@ -4,93 +4,215 @@
-->
<
script
lang=
"ts"
setup
>
import
{
nextTick
,
reactive
,
ref
}
from
"
vue
"
;
interface
IModalProps
{
minHeight
?:
number
minWidth
?:
number
}
interface
IModalState
{
isVisible
:
boolean
,
isDragging
:
boolean
,
isWaiting
:
boolean
,
resizingDirection
?:
'
s
'
|
'
e
'
|
'
se
'
,
lastX
:
number
,
lastY
:
number
,
firstOpening
:
boolean
,
}
const
emit
=
defineEmits
([
'
clos
e
'
'
resiz
e
'
])
const
backdrop
=
ref
<
HTMLDivElement
|
null
>
(
null
)
const
modal
=
ref
<
HTMLDivElement
|
null
>
(
null
)
const
props
=
withDefaults
(
defineProps
<
IModalProps
>
(),
{
minHeight
:
100
,
minWidth
:
150
,
})
const
state
:
IModalState
=
reactive
({
isVisible
:
false
,
isDragging
:
false
,
isWaiting
:
false
,
lastX
:
0
,
lastY
:
0
,
firstOpening
:
true
,
})
function
onMouseDownOnHeader
(
event
:
MouseEvent
)
{
console
.
debug
(
'
Modal.onMouseDownOnHeader
'
,
event
)
state
.
isDragging
=
true
state
.
lastX
=
event
.
clientX
state
.
lastY
=
event
.
clientY
}
function
onMouseDown
(
event
:
MouseEvent
)
{
console
.
debug
(
'
Modal.onMouseDown
'
,
event
,
modal
.
value
!
.
getBoundingClientRect
())
const
bb
=
modal
.
value
!
.
getBoundingClientRect
()
const
x
=
event
.
clientX
const
y
=
event
.
clientY
if
(
bb
.
right
-
15
<
x
&&
x
<
bb
.
right
&&
bb
.
top
<
y
&&
y
<
bb
.
bottom
-
20
)
state
.
resizingDirection
=
'
e
'
else
if
(
bb
.
left
<
x
&&
x
<
bb
.
right
-
20
&&
bb
.
bottom
-
15
<
y
&&
y
<
bb
.
bottom
)
state
.
resizingDirection
=
'
s
'
else
if
(
bb
.
right
-
20
<
x
&&
x
<
bb
.
right
&&
bb
.
bottom
-
20
<
y
&&
y
<
bb
.
bottom
)
state
.
resizingDirection
=
'
se
'
}
function
onMouseMove
(
event
:
MouseEvent
)
{
console
.
debug
(
'
Modal.onMouseMove
'
,
event
)
const
bb
=
modal
.
value
!
.
getBoundingClientRect
()
const
x
=
event
.
clientX
const
y
=
event
.
clientY
if
(
state
.
isDragging
)
{
modal
.
value
!
.
style
.
left
=
(
bb
.
left
+
x
-
state
.
lastX
)
+
'
px
'
modal
.
value
!
.
style
.
top
=
(
bb
.
top
+
y
-
state
.
lastY
)
+
'
px
'
}
else
if
(
state
.
resizingDirection
)
{
const
newSize
=
{
width
:
bb
.
width
,
height
:
bb
.
height
}
const
w
=
bb
.
width
+
x
-
state
.
lastX
const
h
=
bb
.
height
+
y
-
state
.
lastY
switch
(
state
.
resizingDirection
)
{
case
'
e
'
:
newSize
.
width
=
w
<
props
.
minWidth
?
props
.
minWidth
:
w
modal
.
value
!
.
style
.
width
=
newSize
.
width
+
'
px
'
break
case
'
s
'
:
newSize
.
height
=
h
<
props
.
minHeight
?
props
.
minHeight
:
h
modal
.
value
!
.
style
.
height
=
newSize
.
height
+
'
px
'
break
case
'
se
'
:
newSize
.
height
=
h
<
props
.
minHeight
?
props
.
minHeight
:
h
newSize
.
width
=
w
<
props
.
minWidth
?
props
.
minWidth
:
w
modal
.
value
!
.
style
.
width
=
newSize
.
width
+
'
px
'
modal
.
value
!
.
style
.
height
=
newSize
.
height
+
'
px
'
}
emit
(
'
resize
'
,
newSize
)
}
else
if
(
bb
.
right
-
10
<
x
&&
x
<
bb
.
right
&&
bb
.
top
<
y
&&
y
<
bb
.
bottom
-
10
)
backdrop
.
value
!
.
style
.
cursor
=
'
e-resize
'
else
if
(
bb
.
left
<
x
&&
x
<
bb
.
right
-
10
&&
bb
.
bottom
-
10
<
y
&&
y
<
bb
.
bottom
)
backdrop
.
value
!
.
style
.
cursor
=
'
s-resize
'
else
if
(
bb
.
right
-
10
<
x
&&
x
<
bb
.
right
&&
bb
.
bottom
-
10
<
y
&&
y
<
bb
.
bottom
)
backdrop
.
value
!
.
style
.
cursor
=
'
se-resize
'
else
if
(
state
.
isWaiting
)
backdrop
.
value
!
.
style
.
cursor
=
'
wait
'
else
backdrop
.
value
!
.
style
.
cursor
=
'
default
'
state
.
lastX
=
x
state
.
lastY
=
y
}
function
onMouseUp
(
event
:
MouseEvent
)
{
state
.
isDragging
=
false
state
.
resizingDirection
=
undefined
}
async
function
show
()
{
state
.
isVisible
=
true
await
nextTick
()
if
(
state
.
firstOpening
)
{
const
bb
=
modal
.
value
!
.
getBoundingClientRect
()
modal
.
value
!
.
style
.
left
=
((
backdrop
.
value
!
.
getBoundingClientRect
().
right
-
bb
.
right
+
bb
.
left
)
/
2
)
+
'
px
'
modal
.
value
!
.
style
.
top
=
((
backdrop
.
value
!
.
getBoundingClientRect
().
bottom
-
bb
.
bottom
+
bb
.
top
)
/
2
)
+
'
px
'
state
.
firstOpening
=
false
emit
(
'
resize
'
,
{
width
:
bb
.
width
,
height
:
bb
.
height
})
}
}
function
setWaiting
(
wait
:
boolean
)
{
state
.
isWaiting
=
wait
if
(
state
.
isWaiting
)
modal
.
value
!
.
style
.
cursor
=
'
wait
'
else
modal
.
value
!
.
style
.
cursor
=
'
default
'
}
function
close
()
{
emit
(
'
close
'
)
state
.
isVisible
=
false
state
.
isWaiting
=
false
}
defineExpose
({
show
,
close
,
setWaiting
})
</
script
>
<
template
lang=
"pug"
>
.Modal_backdrop
.Modal
header.Modal_header
slot(name="title") default title
button.Modal_closeBtn(
.Modal__backdrop(
ref="backdrop"
v-show="state.isVisible"
@mousemove.stop="onMouseMove"
@mouseup="onMouseUp")
.Modal(
ref="modal"
@mousedown="onMouseDown")
header.Modal__header(
@mousedown.stop="onMouseDownOnHeader")
.Modal__title
slot(name="title") default title
button.Modal__closeBtn(
type="button"
title="Fermer"
@click="close") X
section.Modal_body
@click="close") 🗙
section.Modal__body
slot(name="body") default body
footer.Modal_footer
footer.Modal_
_
footer
slot(name="footer")
</
template
>
<
style
>
.Modal_backdrop
{
z-index
:
1
;
position
:
fixed
;
top
:
0
;
bottom
:
0
;
left
:
0
;
right
:
0
;
background-color
:
rgba
(
0
,
0
,
0
,
0.3
);
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
resize
:
both
;
overflow
:
scroll
;
}
<
style
lang=
"sass"
>
.Modal__backdrop
z-index
:
1
position
:
fixed
top
:
0
bottom
:
0
left
:
0
right
:
0
background-color
:
rgba
(
0
,
0
,
0
,
0
.3
)
.Modal
{
background
:
#FFFFFF
;
box-shadow
:
2px
2px
20px
1px
;
display
:
flex
;
flex-direction
:
column
;
max-height
:
100%
;
}
.Modal
position
:
absolute
box-shadow
:
2px
2px
20px
1px
background
:
#FFFFFF
border-radius
:
10px
display
:
flex
flex-direction
:
column
max-height
:
100%
width
:
50%
.Modal_header
,
.Modal_footer
{
flex
:
0
0
auto
;
padding
:
15px
;
display
:
flex
;
}
.Modal__header
padding
:
5px
5px
0
5px
display
:
grid
grid-template-columns
:
auto
min-content
align-items
:
center
border-bottom
:
1px
solid
#eeeeee
font-weight
:
bold
cursor
:
move
.Modal_header
{
position
:
relative
;
border-bottom
:
1px
solid
#eeeeee
;
color
:
#4AAE9B
;
justify-content
:
space-between
;
}
.Modal__title
text-align
:
center
.Modal_footer
{
border-top
:
1px
solid
#eeeeee
;
flex-direction
:
column
;
justify-content
:
flex-end
;
}
.Modal__closeBtn
border
:
none
cursor
:
pointer
font-size
:
larger
//color: #4AAE9B
background
:
transparent
.Modal_body
{
flex
:
1
1
auto
;
position
:
relative
;
padding
:
20px
10px
;
overflow
:
auto
;
}
.Modal__body
flex
:
1
1
auto
//position: relative
padding
:
20px
10px
//overflow: hidden
.Modal_closeBtn
{
position
:
absolute
;
top
:
0
;
right
:
0
;
border
:
none
;
font-size
:
20px
;
padding
:
10px
;
cursor
:
pointer
;
font-weight
:
bold
;
color
:
#4AAE9B
;
background
:
transparent
;
}
.Modal__footer
flex
:
0
0
auto
padding
:
10px
border-top
:
1px
solid
#eeeeee
display
:
grid
justify-items
:
center
</
style
>
\ No newline at end of file
frontend/src/components/QuestionEditor.vue
View file @
61b0be87
...
...
@@ -7,29 +7,24 @@ Events:
<
script
lang=
"ts"
setup
>
import
Modal
from
'
@/components/Modal.vue
'
import
CodeEditor
from
"
@/components/CodeEditor.vue
"
;
import
ThemeTree
from
"
@/components/ThemeTree.vue
"
;
// import VModal from 'vue-js-modal';
import
{
computed
,
nextTick
,
reactive
,
ref
,
watch
}
from
'
vue
'
;
import
{
useQuestionsStore
}
from
"
@/store/questions
"
;
import
type
{
IQuestion
,
IThemePos
,
QuestionID
,
ThemeID
}
from
"
@/types
"
;
import
{
useThemesStore
}
from
"
@/store/themes
"
;
import
ThemeTree
from
"
@/components/ThemeTree.vue
"
import
{
computed
,
nextTick
,
reactive
,
ref
}
from
'
vue
'
import
{
useQuestionsStore
}
from
"
@/store/questions
"
import
type
{
IQuestion
,
IThemePos
,
QuestionID
,
ThemeID
}
from
"
@/types
"
import
{
useThemesStore
}
from
"
@/store/themes
"
const
questions
=
useQuestionsStore
()
const
themes
=
useThemesStore
()
//
// interface IQuestionEditorProps {
// show: boolean,
// options: {action: 'edit' | 'duplicate' | 'new', questionId: QuestionID}
// }
interface
IQuestionEditorState
{
themesSelection
:
ThemeID
[],
question
:
Partial
<
IQuestion
>
,
oldQuestion
:
IQuestion
|
null
,
// visible: boolean,
isModalVisible
:
boolean
}
const
modal
=
ref
<
InstanceType
<
typeof
Modal
>
|
null
>
(
null
)
const
root
=
ref
<
HTMLElement
|
null
>
(
null
)
const
codeEditor
=
ref
<
InstanceType
<
typeof
CodeEditor
>
|
null
>
(
null
)
const
themeTree
=
ref
<
InstanceType
<
typeof
ThemeTree
>
|
null
>
(
null
)
const
emit
=
defineEmits
([
...
...
@@ -42,9 +37,7 @@ Events:
const
state
:
IQuestionEditorState
=
reactive
({
themesSelection
:
[],
question
:
{
themes
:
[]},
oldQuestion
:
null
,
// visible: false,
isModalVisible
:
false
oldQuestion
:
null
})
const
errors
=
computed
(()
=>
questions
.
getSaveErrors
)
...
...
@@ -88,7 +81,7 @@ Events:
state
.
question
.
avalider
=
false
state
.
question
.
validee
=
false
setThemesSelection
(
state
.
question
.
themes
!
);
show
();
modal
.
value
.
show
();
await
nextTick
()
themeTree
.
value
?.
initOpenThemes
()
}
...
...
@@ -103,9 +96,9 @@ Events:
state
.
question
=
JSON
.
parse
(
JSON
.
stringify
(
state
.
oldQuestion
))
}
setThemesSelection
(
state
.
question
.
themes
!
)
show
()
await
nextTick
()
themeTree
.
value
?.
initOpenThemes
()
modal
.
value
.
show
()
//
await nextTick()
//
themeTree.value?.initOpenThemes()
}
// function opened(event) {
...
...
@@ -120,7 +113,7 @@ Events:
}
function
save
()
{
root
.
value
!
.
s
tyle
[
'
cursor
'
]
=
'
wait
'
;
modal
.
value
!
.
s
etWaiting
(
true
)
let
themes
=
[];
for
(
let
i
=
0
;
i
<
state
.
themesSelection
.
length
;
i
++
)
{
const
themeId
=
state
.
themesSelection
[
i
];
...
...
@@ -140,161 +133,167 @@ Events:
.
then
(()
=>
{
if
(
questions
.
getSaveStatus
)
close
()
root
.
value
!
.
s
tyle
.
cursor
=
'
default
'
;
modal
.
value
.
s
etWaiting
(
false
)
})
}
function
show
()
{
state
.
isModalVisible
=
true
function
onResize
({
width
:
newWidth
,
height
:
newHeight
}:
{
width
:
number
,
height
:
number
})
{
console
.
debug
(
'
QE.onResize
'
,
newWidth
,
newHeight
)
themeTree
.
value
!
.
setHeight
((
newHeight
-
139
)
+
'
px
'
)
codeEditor
.
value
!
.
setHeight
((
newHeight
-
188
)
+
'
px
'
)
codeEditor
.
value
!
.
setWidth
((
newWidth
-
480
)
+
'
px
'
)
}
function
close
()
{
state
.
isModalVisible
=
false
modal
.
value
.
close
()
}
defineExpose
({
edit
,
duplicate
})
</
script
>
<
template
lang=
"pug"
>
//modal(
// classes="question-editor"
// name="question-editor"
// adaptative="true"
// height="550"
// width="750"
// :min-width="650"
// :min-height=550
// :draggable="true"
// :resizable="true"
// :scrollable="true"
// :clickToClose="false"
// @opened="opened")
Modal(v-show="state.isModalVisible" @close="state.isModalVisible = false")
template(v-slot:title)
{{
state
.
question
.
id
?
'
Question
'
+
state
.
question
.
id
:
'
Nouvelle question
'
}}
template(v-slot:body)
.content(ref="root")
#code-panel
table
tr
td
label(for="code") Code LaTeX
td
.error(v-if="errors && errors.code" data-testid="code-error")
{{
errors
.
code
.
join
(
'
\n
'
)
}}
.link(
v-if="errors && errors.latexlog"
data-testid="latexlog"
@click="$emit('message', {title: 'Erreur de compilation', message:errors.latexlog[0]})"
) Voir le log LaTeX