Commit 5e8cacf1 authored by GuiΩ's avatar GuiΩ
Browse files

feat(seances): select ; copy ; paste 1 or multiple seances

parent e7fd7a9e
......@@ -19,15 +19,56 @@ Events:
import FeuilleListFeuille from '@/components/FeuilleListFeuille.vue'
import {useFeuillesStore} from "@/store/feuilles";
import {useClassesStore} from "@/store/classes";
import {computed} from "vue";
import {computed, provide, reactive} from "vue";
import {useUndoablesStore} from "@/store/undoables";
import type {ISeance, ISeanceR, SeanceID} from "@/types";
interface IFeuilleListState {
selection: {seances: SeanceID[]},
clipboard: {seances: Partial<ISeanceR>[]}
}
const feuilles = useFeuillesStore()
const classes = useClassesStore()
const undoables = useUndoablesStore()
const emit = defineEmits([
'show-question-tooltip',
'show-context-menu',
'loaded'
])
const state: IFeuilleListState = reactive({
selection: {seances: []},
clipboard: {seances: []}
})
const currFeuilles = computed(() => feuilles.getCurrFeuilles )
function setSelection(newSelection) {
console.debug('FL.setSelection', newSelection)
state.selection = newSelection
}
function onCopy(content) {
console.debug('FL.setClipboard', content)
const newSeances: Partial<ISeanceR>[] = []
for (const seanceId of state.selection.seances) {
const seance = feuilles.getSeance(seanceId)
const newSeance = {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)
}
state.clipboard = {seances: newSeances}
}
function addFeuille() {
undoables.addFeuille(classes.getSelectedId!)
}
......@@ -40,8 +81,12 @@ Events:
template(v-for="feuilleId in currFeuilles")
FeuilleListFeuille(
:feuille-id="feuilleId"
:selection="state.selection"
:clipboard="state.clipboard"
@loaded="$emit('loaded', $event)"
@message="$emit('message', $event)"
@set-selection="setSelection"
@copy="onCopy"
@question-edit="$emit('question-edit', $event)"
@question-drop="$emit('question-drop', $event)"
@feuille-changed="updateFeuille($event.feuilleId)"
......
......@@ -16,15 +16,24 @@ Events:
import {useFeuillesStore} from "@/store/feuilles"
import {computed} from "vue";
import {useLatexStore} from "@/store/latex";
import type {ISeanceR} from "@/types";
import type {ISeanceR, SeanceID} from "@/types";
import {useUndoablesStore} from "@/store/undoables";
const latex = useLatexStore()
const undoables = useUndoablesStore()
const emit = defineEmits([
'show-question-tooltip',
'show-context-menu',
'set-selection',
'copy',
'loaded'
])
const props = defineProps<{
feuilleId: number,
selection: {seances: SeanceID[]},
clipboard: {seances: Partial<ISeanceR>[]},
}>()
const feuilles = useFeuillesStore()
......@@ -44,7 +53,7 @@ Events:
})
function addSeance() {
undoables.addSeance(props.feuilleId)
undoables.addSeance({feuille: props.feuilleId})
}
function deleteFeuille() {
......@@ -56,7 +65,9 @@ Events:
</script>
<template lang="pug">
.Feuille(v-if="feuille" :data-testid="'feuille_' + feuille.id")
.Feuille(
v-if="feuille"
:data-testid="'feuille_' + feuille.id")
.FeuilleHeader
span.FeuilleHeader_deleteFeuilleBtn(
:class="{disabled: ! isDeletable}"
......@@ -75,10 +86,14 @@ Events:
:key="seanceId")
FeuilleListFeuilleSeance(
:seance-id="seanceId"
:selection="props.selection"
:clipboard="props.clipboard"
@loaded="$emit('loaded', $event)"
@message="$emit('message', $event)"
@feuille-changed="$emit('feuille-changed', $event)"
@question-edit="$emit('question-edit', $event)"
@set-selection="$emit('set-selection', $event)"
@copy="$emit('copy', $event)"
@question-drop="$emit('question-drop', $event)"
@score-changed="$emit('score-changed', $event)"
@seance-changed="$emit('seance-changed', $event)"
......@@ -86,7 +101,7 @@ Events:
@show-context-menu="$emit('show-context-menu', $event)")/
button.Feuille_newFeuilleBtn(
type="button"
@click="addSeance(props.feuilleId)"
@click="addSeance({feuille: props.feuilleId})"
) Ajouter une séance
</template>
......
......@@ -25,11 +25,11 @@ Events:
import Datepicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css'
import {computed, nextTick, reactive, ref} from "vue";
import {computed, inject, nextTick, reactive, ref} from "vue";
import {useFeuillesStore} from "@/store/feuilles";
import FeuilleListFeuilleSeanceSlot from "@/components/FeuilleListFeuilleSeanceSlot.vue";
import {useLatexStore} from "@/store/latex";
import type {IQuestionPosee} from "@/types";
import type {IQuestionPosee, ISeanceR, SeanceID} from "@/types";
import {useClassesStore} from "@/store/classes";
import {useUndoablesStore} from "@/store/undoables";
......@@ -48,18 +48,19 @@ Events:
const btnScoreSave = ref<HTMLInputElement | null>(null)
const datePicker = ref<InstanceType<typeof Datepicker> | null>(null)
const emit = defineEmits([
'loaded',
'set-selection',
'show-context-menu',
'show-question-tooltip',
'copy'])
const props = defineProps<{
seanceId: number, // the current seance
selection: {seances: SeanceID[]},
clipboard: {seances: Partial<ISeanceR>[]},
}>()
// data: function () {
// return {
// fr: fr,
// date: new Date(), // the date of the seance
// datePickerVisible: false, // is the date currently editing ?
const state: IFeuilleListFeuilleSeanceState = reactive({
scoresEditing: false,
newScores: [], // array of the temp scores before insertion in the database
......@@ -76,6 +77,8 @@ Events:
return true
})
const isSelected = computed(() => props.selection.seances.indexOf(props.seanceId)>-1)
const seance = computed(() => feuilles.getSeance(props.seanceId))
const dateStr = computed(() => {
......@@ -170,6 +173,54 @@ Events:
}
}
function onClick(event: MouseEvent) {
console.info('Seance', props.seanceId)
console.debug('FLFS.onClick', event, props.selection)
let newSelection = JSON.parse(JSON.stringify(props.selection))
if (event.ctrlKey) {
const index = newSelection.seances.indexOf(props.seanceId)
if (index > -1)
newSelection.seances.splice(index,1)
else
newSelection.seances.push(props.seanceId)
} else
newSelection = {seances: [props.seanceId]}
emit('set-selection', newSelection)
}
function onContextMenu(event: MouseEvent) {
console.debug('FLFS.onContextMenu', event, props.clipboard)
if (! props.selection.seances.includes(props.seanceId))
onClick(event)
// 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: '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',
disabled: props.clipboard.seances.length < 1,
callback: () => pasteSeance(seance.value?.position! +1)},
{name: 'Annuler'}]})
}
function pasteSeance(position: number) {
const seances: Partial<ISeanceR>[] = JSON.parse(JSON.stringify(props.clipboard.seances))
for (const s of seances) {
s.feuille = seance.value?.feuille!
s.position = position++
}
if (seances.length > 1)
undoables.addSeances(seances, 'Insérer des séance')
else
undoables.addSeance(seances[0], 'Insérer une séance')
}
// // pour débugage
// function onShowContextMenu(event) {
// console.log('FLFS.onShowContextMenu', event);
......@@ -179,8 +230,12 @@ Events:
</script>
<template lang="pug">
.seance(:data-testid="'seance_' + props.seanceId")
div
.Seance(
:data-testid="'seance_' + props.seanceId"
v-if="seance"
@click="onClick")
.Seance_body(
:class="{selected: isSelected}")
| Seance {{ seance.position }}
FeuilleListFeuilleSeanceSlot(
v-for="position in 5"
......@@ -193,9 +248,9 @@ Events:
:scoreEditing="state.scoresEditing"
@loaded="$emit('loaded', $event)"
@focus-next="focusNext(position)"
@contextmenu="onContextMenu"
@message="$emit('message', $event)"
@question-edit="$emit('question-edit', $event)"
@question-drop="$emit('question-drop', $event)"
@show-question-tooltip="$emit('show-question-tooltip', $event)"
@show-context-menu="$emit('show-context-menu', $event)")/
......@@ -205,7 +260,7 @@ Events:
br
button.annuler(v-if="state.scoresEditing" @click="state.scoresEditing = false") Annuler
br
span.seance-date(v-if="!state.isDatePickerVisible" @click="showDatePicker") {{ dateStr }}
span.Seance_date(v-if="!state.isDatePickerVisible" @click="showDatePicker") {{ dateStr }}
//span.seance-date {{ dateStr }}
Datepicker(
ref="datePicker"
......@@ -241,9 +296,9 @@ Events:
a(v-if="isDeletable" @click="undoables.deleteSeance(seance)") Supprimer la séance
</template>
<style scoped>
<style>
.seance {
.Seance {
display: table-row;
margin: 0;
padding: 0;
......@@ -251,7 +306,12 @@ Events:
vertical-align: middle;
}
.seance-date {
.Seance_body.selected {
background: rgba(1,0,0,.05);
border: solid red 1px;
}
.Seance_date {
position: relative;
top: 50%;
cursor: pointer;
......
......@@ -44,9 +44,9 @@ Events:
const emit = defineEmits([
'show-question-tooltip',
'show-context-menu',
'question-edit',
'question-drop',
'show-context-menu',
'update:newScore',
'focus-next',
'loaded'
......@@ -81,6 +81,7 @@ Events:
function onClick(event: MouseEvent) {
console.info('question', question.value.id)
nClick++
if (nClick === 1) {
clickTimer = setTimeout(() => {
......
......@@ -13,7 +13,7 @@
import {useThemesStore} from "@/store/themes"
import {useClassesStore} from "@/store/classes"
import {useUndoablesStore} from "@/store/undoables";
import type {ClasseID, IQuestionDropEvent, IQuestionPosee, QuestionID, ThemeID} from "@/types";
import type {ClasseID, IQuestionDropEvent, IQuestionPosee, QuestionID, SeanceID, ThemeID} from "@/types";
import ContextMenu from "@/components/ContextMenu.vue";
import type {IContextMenuOption} from "@/components/ContextMenu.vue";
import MessageBox from "@/components/MessageBox.vue";
......
......@@ -17,6 +17,7 @@ Events:
import {useThemesStore} from "@/store/themes";
import {useQuestionsStore} from "@/store/questions";
import type {ClasseID, IQuestion, QuestionID, ThemeID} from "@/types";
import type {SeanceID} from "@/types";
const questions = useQuestionsStore()
const themes = useThemesStore()
......@@ -28,8 +29,6 @@ Events:
interface IQuestionListState {
questionsIds: QuestionID[],
scores: {},
dropEvent?: MouseEvent,
dropIndex: number,
questionsSel: QuestionID[],
onLoaded?: () => void
......@@ -59,8 +58,6 @@ Events:
const state: IQuestionListState = reactive({
questionsIds: [],
scores: {},
dropEvent: undefined,
dropIndex: 0,
questionsSel: [],
})
......@@ -280,21 +277,6 @@ Events:
root.value!.style.background = '';
}
// function endDrop() {
// const positionDest = questions.getPositionInTheme(questions.getForCurrTheme[state.dropIndex], props.themeId!)
//
// let element
// if (positionDest === null)
// element = findLastUnordered()
// else
// element = questionNodes.value[state.dropIndex]
// // element.style.borderBottom = ''
// // element.style.borderTop = ''
// // state.dropEvent = undefined
// state.dropIndex = 0
// // this.$refs.dropContextMenu.close()
// }
function changePosition(indexSource: number, indexDrop: number) {
const ids = questions.getForCurrTheme
// const indexSource = this.dropEvent.dataTransfer.getData('index');
......
import {IFeuille, IQuestionPosee, ISeance, ISeanceR, IUndoable} from "@/types";
import {IFeuille, IQuestionPosee, ISeance, ISeanceR, IUndoable, SeanceID} from "@/types";
import {useFeuillesStore} from "@/store/feuilles";
import {useClassesStore} from "@/store/classes";
......@@ -25,7 +25,7 @@ abstract class BaseUndoable implements IUndoable {
}
export class MultipleAction extends BaseUndoable {
private actions: (IUndoable | IUndoable[])[] // the actions that are in the same array can be simultaneously
protected actions: (IUndoable | IUndoable[])[] // the actions that are in the same array can be simultaneously
constructor(redoMessage: string = '', undoMessage: string = '', actions: (IUndoable | IUndoable[])[] = []) {
super(false, redoMessage, undoMessage)
......@@ -70,6 +70,10 @@ export class QuestionPoseeAddDelAction extends BaseUndoable {
this.questionPosee = questionPosee
}
setSeanceId(seanceId: SeanceID) {
this.questionPosee.seance = seanceId
}
do() {
console.debug('QuestionPoseeAddDelAction.do', this.questionPosee, this.done)
const feuilles = useFeuillesStore()
......@@ -85,7 +89,7 @@ export class QuestionPoseeAddDelAction extends BaseUndoable {
this._done = true
return feuilles._addQuestionPosee(this.questionPosee)
.then((questionPosee) => {
if (!this.questionPosee.hasOwnProperty('id')) {
if (! this.questionPosee.id) {
this.questionPosee.id = questionPosee.id
return questionPosee
}
......@@ -137,6 +141,8 @@ class _SeanceAddDelAction extends BaseUndoable {
this.seance = seance
}
getId() { return this.seance.id }
do() {
console.debug('_SeanceAddDelAction.do', this.seance, this.done)
const feuilles = useFeuillesStore()
......@@ -147,7 +153,7 @@ class _SeanceAddDelAction extends BaseUndoable {
this.done = true
return feuilles._addSeance(this.seance)
.then((seance: ISeance) => {
if (! this.seance.hasOwnProperty('id')) {
if (! this.seance.id) {
this.seance.id = seance.id
}
})
......@@ -155,14 +161,14 @@ class _SeanceAddDelAction extends BaseUndoable {
}
}
export class SeanceAddDelActionR extends MultipleAction {
private seance: Partial<ISeanceR>
class _SeanceAddDelActionR extends MultipleAction {
private seance?: Partial<ISeanceR>
private add?: boolean
constructor(seance: Partial<ISeanceR>, add: boolean = true) {
constructor(seance: Partial<ISeanceR>, add: boolean = true, redoMsg?: string, undoMsg?: string) {
if (seance.feuille === undefined) {
console.error('new SeanceAddDelActionR: position is undef', seance, add)
super()
this.seance = seance
return
}
const feuilles = useFeuillesStore()
......@@ -182,18 +188,69 @@ export class SeanceAddDelActionR extends MultipleAction {
questions.push(new QuestionPoseeAddDelAction(questionPosee, add))
}
}
let actions = [questions, new _SeanceAddDelAction(seance, add)]
if (add)
super('Ajouter la séance ' + seance.position, 'Supprimer la séance ' + seance.position, actions)
else
super('Supprimer la séance ' + seance.position, 'Restaurer la séance ' + seance.position, actions)
let actions
if (add) {
actions = [new _SeanceAddDelAction(seance, add), questions]
if (! redoMsg)
redoMsg = 'Ajouter la séance ' + seance.position
if (! undoMsg)
undoMsg = 'Supprimer la séance ' + seance.position
} else {
actions = [questions, new _SeanceAddDelAction(seance, add)]
if (! redoMsg)
redoMsg = 'Supprimer la séance ' + seance.position
if (! undoMsg)
undoMsg = 'Restaurer la séance ' + seance.position
}
super(redoMsg, undoMsg, actions)
this.seance = seance
this.add = add
}
do() {
async do() {
console.debug('SeanceAddDelActionR.do', this.seance, this.done)
const feuilles = useFeuillesStore()
return super.do().then(() => feuilles.loadFeuille(this.seance.feuille!, true))
if (this.done || ! this.add)
await super.do()
else { // new seance added
const seanceAction = this.actions[0] as _SeanceAddDelAction
await seanceAction.do()
const requests = []
const questionActions = this.actions[1] as QuestionPoseeAddDelAction[]
for (let qa of questionActions) {
qa.setSeanceId(seanceAction.getId()!)
requests.push(qa.do())
}
await Promise.all(requests)
this.done = !this.done
}
console.log(this.redoMessage + ' done', this.done)
await feuilles.loadFeuille(this.seance?.feuille!, true)
}
}
export class SeanceAddActionR extends _SeanceAddDelActionR {
constructor(seance: Partial<ISeanceR>, redoMsg?: string, undoMsg?: string) {
super(seance, true, redoMsg, undoMsg);
}
}
export class SeancesAddActionR extends MultipleAction {
constructor(seances: Partial<ISeanceR>[], redoMsg?: string, undoMsg?: string) {
const actions = []
for (const seance of seances)
actions.push(new SeanceAddActionR(seance))
if (! redoMsg)
redoMsg = 'Insérer des séances'
if (! undoMsg)
undoMsg = 'Supprimer des séances'
super(redoMsg, undoMsg, actions);
}
}
export class SeanceDelActionR extends _SeanceAddDelActionR {
constructor(seance: Partial<ISeanceR>, redoMsg?: string, undoMsg?: string) {
super(seance, false, redoMsg, undoMsg);
}
}
......@@ -245,7 +302,7 @@ export class FeuilleAddDelActionR extends MultipleAction {
for (const seanceId of feuille.seances) {
const seance = feuilles.getSeance(seanceId)
if (seance)
seancesActions.push(new SeanceAddDelActionR(seance, add))
seancesActions.push(new _SeanceAddDelActionR(seance, add))
}
let actions: ( IUndoable | IUndoable[] )[] = [seancesActions, new _FeuilleAddDelAction(feuille, add)]
let isInsertion = false
......
......@@ -2,7 +2,12 @@ import {defineStore} from 'pinia'
import type {ClasseID, FeuilleID, IFeuille, IQuestionPosee, ISeanceR, IUndoable, QuestionID} from "@/types";
import {useClassesStore} from "@/store/classes";
import {
FeuilleAddDelActionR, MultipleAction, QuestionPoseeAddDelAction, QuestionPoseePatchAction, SeanceAddDelActionR
FeuilleAddDelActionR,
MultipleAction,
QuestionPoseeAddDelAction,
QuestionPoseePatchAction,
SeanceAddActionR,
SeanceDelActionR, SeancesAddActionR
} from "@/store/undoableActions";
......@@ -140,22 +145,25 @@ export const useUndoablesStore = defineStore( 'undoablesStore',{
* Seances *
********************/
// addSeance(seance: ISeance) {
addSeance(feuilleId: FeuilleID, position?: number) {
console.debug('undoables.addSeance', feuilleId, position)
let seance: Partial<ISeanceR> = {
feuille: feuilleId,
questionsPosees: []
}
if (position !== undefined)
seance.position = position
this.queuePush(new SeanceAddDelActionR(seance, true))
addSeances(seances: Partial<ISeanceR>[], redoMsg?: string, undoMsg?: string) {
for (const seance of seances)
if (seance.questionsPosees === undefined)
seance.questionsPosees = []
this.queuePush(new SeancesAddActionR(seances, redoMsg, undoMsg))
return this.redo()
},
addSeance(seance: Partial<ISeanceR>, redoMsg?: string, undoMsg?: string) {
console.debug('undoables.addSeance', seance)
if (seance.questionsPosees === undefined)
seance.questionsPosees = []
this.queuePush(new SeanceAddActionR(seance, redoMsg, undoMsg))
return this.redo()
},
deleteSeance(seance: ISeanceR) {
console.debug('undoables.deleteSeance', seance)
this.queuePush(new SeanceAddDelActionR(seance, false))
this.queuePush(new SeanceDelActionR(seance))
return this.redo()
},
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment