Merge pull request #6540 from scinote-eln/master

Merge master
This commit is contained in:
Martin Artnik 2023-10-26 15:35:23 +02:00 committed by GitHub
commit 88b8498dc2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 152 additions and 38 deletions

View file

@ -1 +1 @@
1.29.1 1.29.1.1

View file

@ -1,7 +1,7 @@
@layer components { @layer components {
.sci-label { .sci-label {
@apply text-sm font-medium text-sn-grey; @apply text-sm font-medium text-sn-dark-grey;
} }
.sci-input-container-v2 { .sci-input-container-v2 {

View file

@ -210,6 +210,14 @@
@publish="publishProtocol" @publish="publishProtocol"
@cancel="closePublishModal" @cancel="closePublishModal"
/> />
<clipboardPasteModal v-if="showClipboardPasteModal"
:image="pasteImages"
:objects="steps"
:objectType="'step'"
:selectedObjectId="firstObjectInViewport()"
@files="uploadFilesToStep"
@cancel="showClipboardPasteModal = false"
/>
</div> </div>
</template> </template>
@ -221,6 +229,8 @@
import Tinymce from '../shared/tinymce.vue' import Tinymce from '../shared/tinymce.vue'
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue' import ReorderableItemsModal from '../shared/reorderable_items_modal.vue'
import PublishProtocol from './modals/publish_protocol.vue' import PublishProtocol from './modals/publish_protocol.vue'
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue'
import AssetPasteMixin from '../shared/content/attachments/mixins/paste.js'
import UtilsMixin from '../mixins/utils.js' import UtilsMixin from '../mixins/utils.js'
import stackableHeadersMixin from '../mixins/stackableHeadersMixin'; import stackableHeadersMixin from '../mixins/stackableHeadersMixin';
@ -234,8 +244,8 @@
required: true required: true
} }
}, },
components: { Step, InlineEdit, ProtocolOptions, Tinymce, ReorderableItemsModal, ProtocolMetadata, PublishProtocol}, components: { Step, InlineEdit, ProtocolOptions, Tinymce, ReorderableItemsModal, ProtocolMetadata, PublishProtocol, clipboardPasteModal},
mixins: [UtilsMixin, stackableHeadersMixin, moduleNameObserver], mixins: [UtilsMixin, stackableHeadersMixin, moduleNameObserver, AssetPasteMixin],
computed: { computed: {
inRepository() { inRepository() {
return this.protocol.attributes.in_repository return this.protocol.attributes.in_repository
@ -245,7 +255,7 @@
}, },
urls() { urls() {
return this.protocol.attributes.urls || {} return this.protocol.attributes.urls || {}
} },
}, },
data() { data() {
return { return {
@ -433,6 +443,16 @@
$('.my_module-name .view-mode').trigger('click'); $('.my_module-name .view-mode').trigger('click');
$('.my_module-name .input-field').focus(); $('.my_module-name .input-field').focus();
}, 300) }, 300)
},
uploadFilesToStep(file, stepId) {
this.$children.find(child => child.step?.id == stepId).uploadFiles(file);
},
firstObjectInViewport() {
let step = $('.step-container:not(.locked)').toArray().find(element => {
const { top, bottom } = element.getBoundingClientRect()
return bottom > 0 && top < window.innerHeight
})
return step ? step.dataset.id : null
} }
} }
} }

View file

@ -4,7 +4,8 @@
@drop.prevent="dropFile" @drop.prevent="dropFile"
@dragenter.prevent="dragEnter($event)" @dragenter.prevent="dragEnter($event)"
@dragover.prevent @dragover.prevent
:class="{ 'draging-file': dragingFile, 'editing-name': editingName }" :data-id="step.id"
:class="{ 'draging-file': dragingFile, 'editing-name': editingName, 'locked': !urls.update_url }"
> >
<div class="drop-message" @dragleave.prevent="!showFileModal ? dragingFile = false : null"> <div class="drop-message" @dragleave.prevent="!showFileModal ? dragingFile = false : null">
{{ i18n.t('protocols.steps.drop_message', { position: step.attributes.position + 1 }) }} {{ i18n.t('protocols.steps.drop_message', { position: step.attributes.position + 1 }) }}
@ -134,12 +135,6 @@
</div> </div>
</div> </div>
<deleteStepModal v-if="confirmingDelete" @confirm="deleteStep" @cancel="closeDeleteModal"/> <deleteStepModal v-if="confirmingDelete" @confirm="deleteStep" @cancel="closeDeleteModal"/>
<clipboardPasteModal v-if="showClipboardPasteModal"
:parent="step"
:image="pasteImages"
@files="uploadFiles"
@cancel="showClipboardPasteModal = false"
/>
<ReorderableItemsModal v-if="reordering" <ReorderableItemsModal v-if="reordering"
:title="i18n.t('protocols.steps.modals.reorder_elements.title', { step_position: step.attributes.position + 1 })" :title="i18n.t('protocols.steps.modals.reorder_elements.title', { step_position: step.attributes.position + 1 })"
:items="reorderableElements" :items="reorderableElements"
@ -162,7 +157,6 @@
import Checklist from '../shared/content/checklist.vue' import Checklist from '../shared/content/checklist.vue'
import deleteStepModal from './modals/delete_step.vue' import deleteStepModal from './modals/delete_step.vue'
import Attachments from '../shared/content/attachments.vue' import Attachments from '../shared/content/attachments.vue'
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue'
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue' import ReorderableItemsModal from '../shared/reorderable_items_modal.vue'
import MenuDropdown from '../shared/menu_dropdown.vue' import MenuDropdown from '../shared/menu_dropdown.vue'
@ -202,7 +196,6 @@
attachmentsReady: false, attachmentsReady: false,
confirmingDelete: false, confirmingDelete: false,
showFileModal: false, showFileModal: false,
showClipboardPasteModal: false,
showCommentsSidebar: false, showCommentsSidebar: false,
dragingFile: false, dragingFile: false,
reordering: false, reordering: false,
@ -226,7 +219,6 @@
StepText, StepText,
Checklist, Checklist,
deleteStepModal, deleteStepModal,
clipboardPasteModal,
Attachments, Attachments,
StorageUsage, StorageUsage,
ReorderableItemsModal, ReorderableItemsModal,
@ -542,10 +534,6 @@
rect.right <= (window.innerWidth || $(window).width()) rect.right <= (window.innerWidth || $(window).width())
); );
}, },
copyPasteImageModal(pasteImages) {
this.pasteImages = pasteImages;
this.showClipboardPasteModal = true;
},
insertElement(element) { insertElement(element) {
let position = element.attributes.position; let position = element.attributes.position;
this.elements = this.elements.map( s => { this.elements = this.elements.map( s => {

View file

@ -3,7 +3,8 @@
@drop.prevent="dropFile" @drop.prevent="dropFile"
@dragenter.prevent="dragEnter($event)" @dragenter.prevent="dragEnter($event)"
@dragover.prevent @dragover.prevent
:class="{ 'bg-sn-super-light-blue': dragingFile, 'bg-white': !dragingFile }" :data-id="result.id"
:class="{ 'bg-sn-super-light-blue': dragingFile, 'bg-white': !dragingFile, 'locked': locked }"
> >
<div class="text-xl items-center flex flex-col text-sn-blue h-full justify-center left-0 absolute top-0 w-full" <div class="text-xl items-center flex flex-col text-sn-blue h-full justify-center left-0 absolute top-0 w-full"
v-if="dragingFile" v-if="dragingFile"

View file

@ -32,6 +32,14 @@
@result:drag_enter="dragEnter" @result:drag_enter="dragEnter"
/> />
</div> </div>
<clipboardPasteModal v-if="showClipboardPasteModal"
:image="pasteImages"
:objects="results"
:objectType="'result'"
:selectedObjectId="firstObjectInViewport()"
@files="uploadFilesToResult"
@cancel="showClipboardPasteModal = false"
/>
</div> </div>
</template> </template>
@ -43,10 +51,13 @@
import stackableHeadersMixin from '../mixins/stackableHeadersMixin'; import stackableHeadersMixin from '../mixins/stackableHeadersMixin';
import moduleNameObserver from '../mixins/moduleNameObserver'; import moduleNameObserver from '../mixins/moduleNameObserver';
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue'
import AssetPasteMixin from '../shared/content/attachments/mixins/paste.js'
export default { export default {
name: 'Results', name: 'Results',
components: { ResultsToolbar, Result }, components: { ResultsToolbar, Result, clipboardPasteModal },
mixins: [stackableHeadersMixin, moduleNameObserver], mixins: [stackableHeadersMixin, moduleNameObserver, AssetPasteMixin],
props: { props: {
url: { type: String, required: true }, url: { type: String, required: true },
canCreate: { type: String, required: true }, canCreate: { type: String, required: true },
@ -136,6 +147,16 @@
}, },
dragEnter(id) { dragEnter(id) {
this.activeDragResult = id; this.activeDragResult = id;
},
uploadFilesToResult(file, resultId) {
this.$children.find(child => child.result?.id == resultId).uploadFiles(file);
},
firstObjectInViewport() {
let result = $('.result-wrapper:not(.locked)').toArray().find(element => {
const { top, bottom } = element.getBoundingClientRect()
return bottom > 0 && top < window.innerHeight
})
return result ? result.dataset.id : null
} }
} }
} }

View file

@ -12,29 +12,68 @@
<h4 class="modal-title">{{i18n.t('assets.from_clipboard.modal_title')}}</h4> <h4 class="modal-title">{{i18n.t('assets.from_clipboard.modal_title')}}</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p><strong>{{i18n.t('assets.from_clipboard.image_preview')}}</strong></p> <label class="sci-label">{{i18n.t('assets.from_clipboard.image_preview')}}</label>
<canvas style="border:1px solid grey;max-width:400px;max-height:300px" id="clipboardPreview" /> <div class="flex justify-center w-full">
<p><strong>{{i18n.t('assets.from_clipboard.file_name')}}</strong></p> <canvas class="max-h-80 max-w-lg rounded border border-solid border-sn-light-grey" ref="preview" />
<div class="input-group"> </div>
<input id="clipboardImageName" type="text" class="form-control" <div class="w-full py-6">
<label class="sci-label">{{i18n.t(`assets.from_clipboard.select_${objectType}`)}}</label>
<SelectSearch
:value="target"
@change="setTarget"
:options="targets"
:isLoading="false"
:placeholder="
i18n.t(`protocols.steps.modals.move_element.${objectType}.search_placeholder`)
"
:searchPlaceholder="
i18n.t(`protocols.steps.modals.move_element.${objectType}.search_placeholder`)
"
/>
</div>
<label class="sci-label">{{i18n.t('assets.from_clipboard.file_name')}}</label>
<div class="sci-input-container-v2">
<input id="clipboardImageName" type="text" class="sci-input-field !pr-16" v-model="fileName"
:placeholder="i18n.t('assets.from_clipboard.file_name_placeholder')" aria-describedby="image-name"> :placeholder="i18n.t('assets.from_clipboard.file_name_placeholder')" aria-describedby="image-name">
<span class="input-group-addon" id="image-name"></span> <span class="absolute right-2.5 text-sn-grey ">
.{{ extension }}
</span>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="cancel">{{i18n.t('general.cancel')}}</button> <button type="button" class="btn btn-secondary" @click="cancel">{{i18n.t('general.cancel')}}</button>
<button type="button" class="btn btn-success" @click="uploadImage">{{i18n.t('assets.from_clipboard.add_image')}}</button> <button type="button" class="btn btn-success" :disabled="!valid" @click="uploadImage">{{i18n.t('assets.from_clipboard.add_image')}}</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import SelectSearch from "../../select_search.vue";
export default { export default {
name: 'clipboardPasteModal', name: 'clipboardPasteModal',
props: { props: {
parent: Object, objects: Array,
image: DataTransferItem image: DataTransferItem,
objectType: String,
selectedObjectId: String
},
data () {
return {
target: null,
targets: [],
fileName: '',
extension: '',
}
},
components: {
SelectSearch
},
computed: {
valid() {
return this.target && this.fileName.length > 0;
}
}, },
mounted() { mounted() {
$(this.$refs.modal).modal('show'); $(this.$refs.modal).modal('show');
@ -42,15 +81,26 @@
$(this.$refs.modal).on('hidden.bs.modal', () => { $(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('cancel'); this.$emit('cancel');
}); });
if (this.selectedObjectId) this.target = this.selectedObjectId;
this.targets = this.objects.map((object) => {
return [
object.id,
object.attributes.name
]
});
}, },
methods: { methods: {
setTarget(target) {
this.target = target;
},
cancel() { cancel() {
$(this.$refs.modal).modal('hide'); $(this.$refs.modal).modal('hide');
}, },
appendImage(item) { appendImage(item) {
let imageBlob = item.getAsFile(); let imageBlob = item.getAsFile();
if (imageBlob) { if (imageBlob) {
var canvas = document.getElementById('clipboardPreview'); var canvas = this.$refs.preview;
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
var img = new Image(); var img = new Image();
img.onload = function() { img.onload = function() {
@ -63,12 +113,12 @@
let extension = imageBlob.name.slice( let extension = imageBlob.name.slice(
(Math.max(0, imageBlob.name.lastIndexOf('.')) || Infinity) + 1 (Math.max(0, imageBlob.name.lastIndexOf('.')) || Infinity) + 1
); );
$('#image-name').html('.' + extension); // add extension near file name this.extension = extension;
this.imageBlob = imageBlob; this.imageBlob = imageBlob;
} }
}, },
uploadImage() { uploadImage() {
let newName = $('#clipboardImageName').val(); let newName = this.fileName;
let imageBlog = this.imageBlob; let imageBlog = this.imageBlob;
// check if the name is set // check if the name is set
if (newName && newName.length > 0) { if (newName && newName.length > 0) {
@ -82,7 +132,7 @@
this.imageBlob = new File([blob], name, { type: imageBlog.type }); this.imageBlob = new File([blob], name, { type: imageBlog.type });
} }
$(this.$refs.modal).modal('hide'); $(this.$refs.modal).modal('hide');
this.$emit('files', this.imageBlob); this.$emit('files', this.imageBlob, this.target);
} }
} }
} }

View file

@ -0,0 +1,32 @@
export default {
data() {
return {
showClipboardPasteModal: false,
pasteImages: null,
};
},
mounted() {
document.addEventListener('paste', this.handlePaste);
},
unmounted() {
document.removeEventListener('paste', this.handlePaste);
},
methods: {
handlePaste(e) {
if ( e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' ) return;
this.pasteImages = this.getImagesFromClipboard(e);
if (this.pasteImages && this.firstObjectInViewport()) this.showClipboardPasteModal = true;
},
getImagesFromClipboard(e) {
let image = null;
if (e.clipboardData) {
for (let i = 0; i < e.clipboardData.items.length; i++) {
if (e.clipboardData.items[i].type.indexOf('image') !== -1) {
image = e.clipboardData.items[i];
}
}
}
return image;
},
},
};

View file

@ -3454,9 +3454,11 @@ en:
drop_label: 'Drop to add to Step' drop_label: 'Drop to add to Step'
file_label: 'File' file_label: 'File'
from_clipboard: from_clipboard:
modal_title: 'Add image from clipboard' modal_title: 'Insert image from clipboard'
image_preview: 'Image preview' image_preview: 'Pasted image preview'
add_image: 'Add' select_step: 'Select to which step youd like to attach image'
select_result: 'Select to which result youd like to attach image'
add_image: 'Insert'
file_name: 'File name' file_name: 'File name'
file_name_placeholder: 'Image' file_name_placeholder: 'Image'
placeholder: placeholder: