diff --git a/src/components/content/AnnotationItem.vue b/src/components/content/AnnotationItem.vue index 332c72cb89c894c306998010ea167b2b71412bd2..91221ee513fda5f1094524c7751d9bd8d0cbef38 100644 --- a/src/components/content/AnnotationItem.vue +++ b/src/components/content/AnnotationItem.vue @@ -15,10 +15,14 @@ :container-width="containerWidth" :reference-annotation="referenceAnnotation" :access-rights="accessRights" + :multi-delete="multiDelete" @deleteAnnotation="deleteAnnotation" + @multiDelete="onMultiDelete" @updated="updateAnnotation" @postAnnotate="postAnnotate" ) + span(slot="multi-delete") + slot(name="multi-delete") // Content ----------------------------------------------------------------------------------------------------------- .q-my-md @@ -36,6 +40,7 @@ mb-headline.q-px-md.q-pt-xs(v-if="asset" content-class="text-caption" :title="asset.filename") .asset(v-else-if="annotationType !== 'TextualBody'") mb-display-file.mb-file + // Display Text -------------------------------------------------------------------------- markdown-display.q-px-md.text-body2.formatting( v-if="annotationType === 'TextualBody'" @@ -90,7 +95,8 @@ export default { 'isComment', 'highlightAnnotation', 'postAnnotator', - 'timelineTime' + 'timelineTime', + 'multiDelete' ], mixins: [DeleteItem, CheckAccess], components: { @@ -170,6 +176,10 @@ export default { this.annotationToDisplay = annotation this.$emit('updated') }, + onMultiDelete (id) { + console.log('TEST') + this.$emit('multiDelete', id) + }, async deleteAnnotation () { const removed = await this.deleteItem(this.message, 'Annotation', 'annotations', this.annotation.id || this.annotation._id) diff --git a/src/components/content/AnnotationItem/Header.vue b/src/components/content/AnnotationItem/Header.vue index 8cc76b2c62a9dfd9cb7b358c9716034fdc7db7a0..de01aebf8d3883670d97692fb7f9e85847fd16d0 100644 --- a/src/components/content/AnnotationItem/Header.vue +++ b/src/components/content/AnnotationItem/Header.vue @@ -19,13 +19,14 @@ q-item.q-pa-none(dense) // Content ----------------------------------------------------------------------------------------------------------- // Type q-item-section(side) - q-icon(:name="iconName" size="sm" :color="$q.dark.isActive ? 'mb-white' : 'mb-black'") + slot(v-if="multiDelete" name="multi-delete") + q-icon(v-else :name="iconName" size="sm" :color="$q.dark.isActive ? 'mb-white' : 'mb-black'") // Creator q-item-section(side) .text-caption {{annotation.creator.name}} q-item-section // Actions - q-item-section(side) + q-item-section(v-if="!multiDelete" side) // desktop action buttons .no-wrap(v-if="containerWidth > 360") @@ -54,6 +55,15 @@ q-item.q-pa-none(dense) @click.native="$emit('deleteAnnotation')" ) + // multi delete + mb-icon-btn( + v-if="(accessRights.update || accessRights.delete) && (postAnnotator || isComment)" + size="sm" + icon-name="delete_sweep" + :tooltip="$t('timelines.annotations.delete_multiple')" + @click.native="$emit('multiDelete', annotation.id)" + ) + // Mobile more button ---------------------------------------------------------------------------------------------- template(v-else) .no-wrap @@ -78,11 +88,19 @@ q-item.q-pa-none(dense) q-separator.mb-bg-bg-light.q-my-sm + // single delete q-item(v-close-popup clickable @click="$emit('deleteAnnotation')") q-item-section(avatar) q-icon.text-mb-red-100(name="delete" size="xs") q-item-section.text-no-wrap.text-mb-red-100 | {{$t('timelines.annotations.delete_annotation')}} + + // multi delete + q-item(v-if="postAnnotator || isComment" v-close-popup clickable @click="$emit('multiDelete', annotation.id)") + q-item-section(avatar) + q-icon.text-mb-red-100(name="delete_sweep" size="xs") + q-item-section.text-no-wrap.text-mb-red-100 + | {{$t('timelines.annotations.delete_multiple')}} </template> <script> @@ -98,7 +116,8 @@ export default { 'hover', 'postAnnotator', 'accessRights', - 'referenceAnnotation' + 'referenceAnnotation', + 'multiDelete' ], components: { EditAnnotation }, data () { diff --git a/src/components/content/AnnotationsStream.vue b/src/components/content/AnnotationsStream.vue index 1a9deea67ef9b8aefca45d43005abb14c970664c..decbeb3a819d9433f689d2ca383c3fbf067ab638 100644 --- a/src/components/content/AnnotationsStream.vue +++ b/src/components/content/AnnotationsStream.vue @@ -1,29 +1,46 @@ <template lang="pug"> -.annotations-wrapper(v-if="!loading") +// has loaded ---------------------------------------------------------------------------------------------------------- +.annotations-wrapper.relative-position(v-if="!loading") + // items transition-group(name="annotation") + annotation-item( + v-for="annotation in annotations" + :key="annotation.id || annotation._id" + :annotation="annotation" + :reference-annotation="referenceAnnotation" + :ref="annotation.id || annotation._id" + :highlight-annotation="timelineTime ? isPositionHighlight(annotation) : isLatestAnnotation(annotation)" + :is-comment="isComment" + :post-annotator="postAnnotator" + :timeline-time="timelineTime" + :multi-delete="multiDelete.isActive" + @remove="(id) => $emit('remove', id)" + @updated="$emit('update')" + @timeClicked="(t) => $emit('timeClicked', t)" + @multiDelete="onMultiDelete" + ) + template(v-if="multiDelete.isActive" slot="multi-delete") + mb-checkbox(:value="multiDelete.selected" :val="annotation.id" @input="onInputCheckbox" size="sm" color="mb-red-100") - annotation-item( - v-for="annotation in annotations" - :key="annotation.id || annotation._id" - :annotation="annotation" - :reference-annotation="referenceAnnotation" - :ref="annotation.id || annotation._id" - :highlight-annotation="timelineTime ? isPositionHighlight(annotation) : isLatestAnnotation(annotation)" - :is-comment="isComment" - :post-annotator="postAnnotator" - :timeline-time="timelineTime" - @remove="(id) => $emit('remove', id)" - @updated="$emit('update')" - @timeClicked="(t) => $emit('timeClicked', t)" - ) + // multi delete buttons + div(v-if="multiDelete.isActive" :class="[isComment ? 'q-mt-xs' : 'fixed-bottom-left q-mb-md q-ml-sm']") + mb-btn.q-mr-sm(@click.native="deleteSelected" :label="$t('general.delete_selected')" :delete="true") + mb-icon-btn( + @click.native="multiDelete = { isActive: false, selected: [] }" + :cancel="true" + icon-name="clear" + :background="true" + ) +// is loading ---------------------------------------------------------------------------------------------------------- .annotations-wrapper(v-else) mb-skeleton.q-mb-sm(v-for="n in 3" :key="`skeleton-${n}`") </template> <script> import AnnotationItem from 'components/content/AnnotationItem' +import DeleteItem from 'src/mixins/DeleteItem' export default { name: 'AnnotationsStream', @@ -39,8 +56,13 @@ export default { 'loading', 'noScroll' ], + mixins: [DeleteItem], data () { return { + multiDelete: { + isActive: false, + selected: [] + }, // Used for annotation by timea scrollToIndex: 0 } @@ -62,6 +84,29 @@ export default { } }, methods: { + deleteSelected () { + const msg = this.isComment ? 'comments' : 'annotations' + this.deleteMultiple( + this.$t(`messages.really_delete.${msg}`), + 'annotations', + this.multiDelete.selected + ).then(() => { + this.$emit('update') + this.multiDelete = { isActive: false, selected: [] } + }) + }, + async deleteAnnotation (id) { + const removed = await this.deleteItem(this.message, 'Annotation', + 'annotations', id) + if (removed) this.$emit('update', id) + }, + onInputCheckbox (obj) { + this.multiDelete.selected = obj + }, + onMultiDelete (val) { + this.multiDelete.selected.push(val) + this.multiDelete.isActive = true + }, // Scrolling and Highlighting isPositionHighlight (annotation) { if (this.timelineTime) { diff --git a/src/i18n/en-us/messages.js b/src/i18n/en-us/messages.js index f8a90c35dc961ba4e7696640534682adba92ba02..e9d1e4a58f2ab4907a77b9b73532e866842518ce 100644 --- a/src/i18n/en-us/messages.js +++ b/src/i18n/en-us/messages.js @@ -25,6 +25,8 @@ export default { could_not_load_asset: 'Could not load file', no_preview_for_asset: 'Preview missing', really_delete: { + annotations: 'Really delete selected annotations?', + comments: 'Really delete selected comments?', asset: 'This action could not only affect your files and timelines, but also break other people\'s content if they reference your file.', assets: 'Really delete all the files? Some might still be in use and deleting them can corrupt your content!', group: 'Really delete group? Members will lose access to shared content!', diff --git a/src/i18n/en-us/timelines.js b/src/i18n/en-us/timelines.js index 232a1046f8d91d61c923c7dc6de1175e5cd5c6b8..9bd87cabe9a91a38fe3820d36fdd40aeca466a2f 100644 --- a/src/i18n/en-us/timelines.js +++ b/src/i18n/en-us/timelines.js @@ -23,6 +23,7 @@ export default { caption_sketch: 'Add sketch caption', changes_saved: 'Changes saved!', delete_annotation: 'Delete', + delete_multiple: 'Delete multiple', edit_annotation: 'Edit Annotation', submit: 'Submit', live: 'Live Annotate', diff --git a/src/mb-components/MbBtn.vue b/src/mb-components/MbBtn.vue index 2fce54255c8520988a71243d5346f908fb719eb8..fcf4e40826df3762d5c7bc58baf9040f0882caa9 100644 --- a/src/mb-components/MbBtn.vue +++ b/src/mb-components/MbBtn.vue @@ -33,7 +33,7 @@ component( export default { name: 'MbBtn', props: ['label', 'to', 'href', 'target', 'disable', 'cancel', 'iconName', 'active', 'size', 'type', 'tooltip', - 'iconNameRight', 'rounded', 'noWrap', 'full-width'], + 'iconNameRight', 'rounded', 'noWrap', 'full-width', 'delete'], data () { return { hover: false @@ -59,6 +59,10 @@ export default { if (this.hover || this.active) return 'mb-red-100' else return 'mb-red-20' } + // delete button + else if (this.delete) { + return 'mb-red-100' + } // regular button else { if (this.hover || this.active) return this.darkMode ? 'mb-white' : 'mb-black' @@ -70,6 +74,10 @@ export default { if (this.disable) { return this.darkMode ? 'mb-light-20' : 'mb-dark-20' } + // delete + else if (this.delete) { + return 'mb-white' + } // regular else { if (this.hover || this.active) return this.darkMode ? 'mb-black' : 'mb-white' diff --git a/src/mb-components/MbCheckbox.vue b/src/mb-components/MbCheckbox.vue index 692b8537c760d83fcf12d2111b15745c969fe840..969ed6b5fdfd2f6008535361772b7f229101cd4f 100644 --- a/src/mb-components/MbCheckbox.vue +++ b/src/mb-components/MbCheckbox.vue @@ -1,17 +1,18 @@ <template lang="pug"> q-checkbox( -v-model="modelValue" -color="mb-blue-100" -:size="size || 'md'" -:label="label" -@input="handleInput" + v-model="modelValue" + :color="color || 'mb-blue-100'" + :size="size || 'md'" + :label="label" + :val="val || undefined" + @input="handleInput" ) </template> <script> export default { name: 'MbCheckbox', - props: ['label', 'value', 'size'], + props: ['label', 'value', 'size', 'val', 'color'], watch: { value () { this.modelValue = this.value