Commit 11e80a01 authored by GuiΩ's avatar GuiΩ
Browse files

feat(seance): copy/paste on context menu

parent dc197372
/*!
/ Copyright (c) 2022.
/ GuiΩ guillaume.claus@worldonline.fr
/
$color-selector: #d9ffe6
$color-feuille: #ffdffb
$color-border: lightGray
$color-disabled: #aaa
$borders: solid 2px $color-border
\ No newline at end of file
......@@ -4,7 +4,7 @@
-->
<script lang="ts" setup>
import {nextTick, reactive, ref} from "vue";
import {nextTick, reactive, ref, watchEffect} from "vue";
export interface IContextMenuOption {
name?: string,
......
......@@ -21,11 +21,11 @@ Events:
import {useClassesStore} from "@/store/classes";
import {computed, provide, reactive} from "vue";
import {useUndoablesStore} from "@/store/undoables";
import type {ISeance, ISeanceR, SeanceID} from "@/types";
import type {IClipboardContent, IClipboardSeance, ISeance, ISeanceR, ISelection, SeanceID} from "@/types";
interface IFeuilleListState {
selection: {seances: SeanceID[]},
clipboard: {seances: Partial<ISeanceR>[]}
selection: ISelection,
clipboard: IClipboardContent
}
const feuilles = useFeuillesStore()
......@@ -45,24 +45,21 @@ Events:
const currFeuilles = computed(() => feuilles.getCurrFeuilles )
function setSelection(newSelection) {
function setSelection(newSelection: ISelection) {
console.debug('FL.setSelection', newSelection)
state.selection = newSelection
}
function onCopy(content) {
console.debug('FL.setClipboard', content)
const newSeances: Partial<ISeanceR>[] = []
function onCopy(event: MouseEvent) {
console.debug('FL.copy', event)
const newSeances: IClipboardSeance[] = []
for (const seanceId of state.selection.seances) {
const seance = feuilles.getSeance(seanceId)
const newSeance = {feuille: seance?.feuille!, questionsPosees: []}
const newSeance: IClipboardSeance = {feuille: seance?.feuille!, questionsPosees: []}
for (const questionPosee of seance?.questionsPosees!)
newSeance.questionsPosees!.push({
id: null,
position: questionPosee.position,
question: questionPosee.question,
score: null,
seance: null
})
newSeances.push(newSeance)
}
......
......@@ -71,16 +71,16 @@ Events:
.FeuilleHeader
span.FeuilleHeader_deleteFeuilleBtn(
:class="{disabled: ! isDeletable}"
:title="isDeletable ? 'Supprimer' : 'Impossible de supprimer une feuille évaluée'"
:title="isDeletable ? 'Supprimer la feuille' : 'Impossible de supprimer une feuille évaluée'"
@click="deleteFeuille") &#x1F5D9;
|
| feuille {{ feuille.position }}
|
template(v-for="model of latex.getAllModels")
template(v-if="model.type === 'FE'")
a(:href="'api/feuille/' + feuille.id + '/get_pdf/?model_id=' + model.id") {{model.label}}.pdf
|&nbsp;
div(v-if="feuille.seances && feuille.seances.length")
//|
//template(v-for="model of latex.getAllModels")
// template(v-if="model.type === 'FE'")
// a(:href="'api/feuille/' + feuille.id + '/get_pdf/?model_id=' + model.id") {{model.label}}.pdf
// |&nbsp;
.FeuilleBody(v-if="feuille.seances && feuille.seances.length")
template(
v-for="seanceId in feuille.seances"
:key="seanceId")
......@@ -99,44 +99,56 @@ Events:
@seance-changed="$emit('seance-changed', $event)"
@show-question-tooltip="$emit('show-question-tooltip', $event)"
@show-context-menu="$emit('show-context-menu', $event)")/
button.Feuille_addSeanceBtn(
type="button"
@click="addSeance({feuille: props.feuilleId})"
) Ajouter une séance
.FeuilleFooter
button.Feuille_addSeanceBtn(
type="button"
@click="addSeance({feuille: props.feuilleId})"
) Ajouter une séance
br
| feuille {{ feuille.position }} - Téléchagements :
|
template(v-for="model of latex.getAllModels")
template(v-if="model.type === 'FE'")
a(:href="'api/feuille/' + feuille.id + '/get_pdf/?model_id=' + model.id") {{model.label}}.pdf
|&nbsp;
br
br
</template>
<style>
.Feuille {
display: flex;
flex-direction: column;
border-bottom: solid 3px black;
padding: 0;
margin: 0;
}
.FeuilleHeader {
/*display: block;*/
align-self: center;
flex: 0 0 auto;
font-size: 24px;
margin: 10px;
}
.FeuilleHeader_deleteFeuilleBtn {
color: red;
cursor: pointer;
}
.FeuilleHeader_deleteFeuilleBtn.disabled {
color: gray;
cursor: default;
}
.Feuille_addSeanceBtn {
align-self: center;
margin: 20px;
max-width: fit-content;
}
<style lang="sass">
@use '../assets/base'
.Feuille
display: flex
flex-direction: column
border-bottom: solid 3px black
padding: 0
margin: 0
.FeuilleHeader
align-self: center
flex: 0 0 auto
font-size: 24px
margin: 10px
.FeuilleHeader_deleteFeuilleBtn
color: red
cursor: pointer
.FeuilleHeader_deleteFeuilleBtn.disabled
color: base.$color-disabled
cursor: default
.FeuilleFooter
align-self: center
.Feuille_addSeanceBtn
align-self: center
margin: 20px
min-width: fit-content
</style>
\ No newline at end of file
......@@ -198,30 +198,47 @@ Events:
emit('set-selection', newSelection)
}
function onContextMenu(event: MouseEvent) {
async function onContextMenu(event: MouseEvent) {
console.debug('FLFS.onContextMenu', event, props.clipboard)
if (! props.selection.seances.includes(props.seanceId)) {
if (!props.selection.seances.includes(props.seanceId)) {
onClick(event)
await nextTick()
}
// emit('set-selection', {seances: [ props.seanceId ]})
const multipleSel = props.selection.seances.length > 1
const multipleClip = props.clipboard.seances.length > 1
emit('show-context-menu', {event:event, options:[
{ name: 'Copier ' + (multipleSel ? ' les seances' : 'la seance') ,
callback: () => emit('copy', event) },
{ name: 'Couper ' + (multipleSel ? ' les seances' : 'la seance') ,
disabled: ! (selectionIsDeletable.value && isDeletable.value),
callback: () => {emit('copy', event) ; nextTick(); undoables.deleteSeances(props.selection.seances) }},
{ name:'Supprimer ' + (multipleSel ? 'les seances' : 'la seance'),
disabled: ! (selectionIsDeletable.value && isDeletable.value),
callback: () => undoables.deleteSeances(props.selection.seances)},
{ name: 'Insérer '+ (multipleClip ? 'les seances' : 'la seance') + ' avant',
emit('show-context-menu', {
event: event, options: [
{
name: 'Copier ' + (multipleSel ? ' les seances' : 'la seance'),
callback: () => emit('copy', event)
},
{
name: 'Couper ' + (multipleSel ? ' les seances' : 'la seance'),
disabled: !(selectionIsDeletable.value && isDeletable.value),
callback: () => {
emit('copy', event);
nextTick();
undoables.deleteSeances(props.selection.seances)
}
},
{
name: 'Supprimer ' + (multipleSel ? 'les seances' : 'la seance'),
disabled: !(selectionIsDeletable.value && isDeletable.value),
callback: () => undoables.deleteSeances(props.selection.seances)
},
{
name: 'Insérer ' + (multipleClip ? 'les seances' : 'la seance') + ' avant',
disabled: props.clipboard.seances.length < 1,
callback: () => pasteSeance(seance.value?.position!)},
{ name: 'Insérer '+ (multipleClip ? 'les seances' : 'la seance') + ' après',
callback: () => pasteSeance(seance.value?.position!)
},
{
name: 'Insérer ' + (multipleClip ? 'les seances' : 'la seance') + ' après',
disabled: props.clipboard.seances.length < 1,
callback: () => pasteSeance(seance.value?.position! +1)},
{name: 'Annuler'}]})
callback: () => pasteSeance(seance.value?.position! + 1)
},
{name: 'Annuler'}]
})
}
......@@ -317,6 +334,7 @@ Events:
</template>
<style lang="sass">
@use '../assets/base'
.Seance
display: flex
......@@ -339,7 +357,7 @@ Events:
color: red
cursor: pointer
&.disabled
color: gray
color: base.$color-disabled
cursor: default
......
......@@ -142,7 +142,7 @@
}
function onQuestionEdit({questionId:questionId, action:action}: {questionId: QuestionID, action: 'edit' | 'duplicate'}) {
questionTooltip.value.close()
questionTooltip.value! .close()
state.onLoaded = undefined
switch (action) {
case "edit":
......@@ -182,16 +182,15 @@ QuestionTooltip(
@selection-changed="themes.setSelectedTheme($event[0])")/
.Selector_question
button(data-testid="newQuestion" @click="$refs.questionEditor.edit()") Nouvelle question
.Selector_questionListContainer
QuestionList.Selector_questionList(
ref="questionList"
:themeId="themes.getSelectedId"
:classeId="classes.getSelectedId"
@show-question-tooltip="onShowQuestionTooltip"
@show-context-menu="onShowContextMenu"
@question-edit="onQuestionEdit"
@question-drop="onQuestionDrop")
//@question-duplicate="questionDuplicate($event.questionId)"")/
QuestionList.Selector_questionList(
ref="questionList"
:themeId="themes.getSelectedId"
:classeId="classes.getSelectedId"
@show-question-tooltip="onShowQuestionTooltip"
@show-context-menu="onShowContextMenu"
@question-edit="onQuestionEdit"
@question-drop="onQuestionDrop")
//@question-duplicate="questionDuplicate($event.questionId)"")/
FeuilleList.FeuilleList(
ref="feuilleList"
......@@ -206,89 +205,80 @@ QuestionTooltip(
</template>
<style>
body {
background-color: #eff0f1;
margin: 0;
}
.MessageBox {
z-index: 10;
}
.QuestionEditor {
z-index: 5;
}
#main-page {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
display: flex;
flex-direction: column;
align-content: flex-start;
}
.Toolbar {
flex: 0 0 auto;
width: 100%;
border-bottom-style: inset;
}
.Mainpanel {
flex: 1 1 auto;
display: flex;
flex-direction: row;
align-items: flex-start;
border: solid;
height: calc(100% - 45px);
}
.Selector {
flex: 1 1 0;
display: flex;
flex-direction: row;
background-color: #d9ffe6;
padding: 10px;
border: solid 2px lightGray;
height: calc(100% - 24px);
}
.Selector_theme {
flex: 1 1 auto;
vertical-align: top;
background-color: white;
border: solid 2px lightGray;
overflow: auto;
/*max-height: 600px;*/
/*overflow-y: scroll;*/
margin: 0;
}
.Selector_question {
text-align: center;
display: flex;
flex-direction: column;
border-bottom: solid 2px lightGray;
}
.Selector_questionListContainer {
flex: 1 1 auto;
overflow-y: auto;
min-width: 230px;
}
.Selector_questionList {
/*max-height: 100%;*/
/*overflow-y: scroll;*/
/*scroll-behavior: smooth;*/
/*height: 500px;*/
border: solid 2px lightGray;
}
.FeuilleList {
flex: 10 10 auto;
}
<style lang="sass">
@use '../assets/base'
body
background-color: #eff0f1
margin: 0
.MessageBox
z-index: 10
.QuestionEditor
z-index: 5
#main-page
position: absolute
top: 0
bottom: 0
right: 0
left: 0
display: flex
flex-direction: column
align-content: flex-start
.Toolbar
flex: 0 0 auto
width: 100%
border-bottom-style: inset
.Mainpanel
flex: 1 1 auto
display: flex
flex-direction: row
align-items: flex-start
border: solid
height: calc(100% - 45px)
.Selector
flex: 1 1 0
display: flex
flex-direction: row
background-color: base.$color-selector
padding: 10px
border: base.$borders
height: calc(100% - 24px)
.Selector_theme
flex: 1 1 auto
vertical-align: top
background-color: white
border: base.$borders
overflow: auto
/*max-height: 600px*/
/*overflow-y: scroll*/
margin: 0
.Selector_question
text-align: center
display: flex
flex-direction: column
border-bottom: base.$borders
.Selector_questionList
border: base.$borders
.FeuilleList
flex: 10 10 auto
</style>
\ No newline at end of file
......@@ -389,9 +389,10 @@ Events:
</script>
<template lang="pug">
#question-list(ref="root") {{ questionsIds.length }} question{{ questionsIds.length > 1 ? 's' : '' }}
div
QuestionListNode(
.QuestionList(ref="root")
|{{ questionsIds.length }} question{{ questionsIds.length > 1 ? 's' : '' }}
.QuestionList_list
QuestionListNode.QuestionList_node(
v-for="(questionId, index) in questionsIds"
:key="index"
:ref="el => questionNodes[index] = el"
......@@ -410,10 +411,20 @@ Events:
@drop.prevent="onDrop($event, index, questionId)"
@show-context-menu="$emit('show-context-menu', $event)"
@show-question-tooltip="$emit('show-question-tooltip', $event)")/
//question-edit="$emit('question-edit', $event)"
//)/
</template>
<style>
<style lang="sass">
@use '../assets/base'
.QuestionList
flex: 1 1 auto
height: calc(100% - 100px)
min-width: 212px
display: flex
flex-direction: column
.QuestionList_list
flex: 1 1 auto
overflow-y: auto
border-top: base.$borders
</style>
......@@ -58,7 +58,7 @@
</script>
<template lang="pug">
.question(
.QuestioList_node(
ref="root"
:id="'question_' + questionId"
:data-testid="'question_' + questionId"
......@@ -67,7 +67,7 @@
v-for="(score, index) in scores"
:key="index"
:score="score")/
img(
img.QuestionList_imageThumb(
v-if="question"
:title="'question ' + questionId"
:alt="'question ' + questionId"
......@@ -82,36 +82,35 @@
@dragover.prevent
@drop.prevent="resend('drop', $event)"
draggable="true")/
.question_vide(v-else) Question {{ questionId }}
.QuestionListe_emptyNode(v-else) Question {{ questionId }}
</template>
<style scoped>
.question {
background-color: rgba(255, 255, 255, .8);
padding: 0;
margin: 0;
}
.question.selected {
background-color: rgba(255, 150, 150, .8);
}
img.editable {
background: rgba(100, 100, 100, .8);
}
img.avalider {
background: rgba(200, 200, 200, .8);
}
img.atrier {
border: 1px solid red;
}
.question_vide {
height: 60px;
margin: auto;
/*padding-top: 20px;*/
background: rgba(255, 255, 255, .8) center no-repeat url("/static/loading.gif");
border: 1px solid black;
}
<style lang="sass">
@use '../assets/base'
.QuestionList_node
background-color: rgba(255, 255, 255, .8)
padding: 0
margin: 0
&.selected
background-color: rgb(242, 228, 240)
border: solid 1px rgba(255, 0, 0, 1)
.QuestionList_imageThumb
&.editable
background: rgba(100, 100, 100, .8)
&.avalider
background: rgba(200, 200, 200, .8)
&.atrier
border: 1px solid red
.QuestionList_emptyNode
height: 60px
margin: auto
/*padding-top: 20px*/
background: rgba(255, 255, 255, .8) center no-repeat url("/static/loading.gif")
border: 1px solid black
</style>
\ No newline at end of file
......@@ -13,6 +13,20 @@ export interface IClasse {
feuilles: FeuilleID[],
}
export interface IClipboardContent {
seances: IClipboardSeance[]
}
export interface IClipboardQuestionPosee {
position: number,
question: QuestionID
}
export interface IClipboardSeance {
feuille: FeuilleID,
questionsPosees: IClipboardQuestionPosee[]
}
export interface IQuestionDropEvent {
event: DragEvent,
seanceSourceId?: SeanceID,
......@@ -121,6 +135,10 @@ export interface ISeanceR { // recursive