mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-01-04 14:44:26 +08:00
Merge pull request #7067 from aignatov-bio/ai-sci-10105-update-tags-task-modal
Update tags modal [SCI-10105]
This commit is contained in:
commit
ae56294670
5 changed files with 171 additions and 101 deletions
|
@ -90,5 +90,13 @@ input[type="checkbox"].sci-checkbox {
|
|||
border: $border-tertiary;
|
||||
}
|
||||
}
|
||||
|
||||
&:checked + .sci-checkbox-label {
|
||||
&::before {
|
||||
background-color: var(--sn-sleepy-grey);
|
||||
border: 1px solid var(--sn-sleepy-grey);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
<TagsModal v-if="tagsModalObject"
|
||||
:params="tagsModalObject"
|
||||
:tagsColors="tagsColors"
|
||||
:projectName="projectName"
|
||||
:projectTagsUrl="projectTagsUrl"
|
||||
@close="updateTable" />
|
||||
<NewModal v-if="newModalOpen"
|
||||
|
@ -88,7 +89,8 @@ export default {
|
|||
projectTagsUrl: { type: String, required: true },
|
||||
assignedUsersUrl: { type: String, required: true },
|
||||
usersFilterUrl: { type: String, required: true },
|
||||
statusesList: { type: Array, required: true }
|
||||
statusesList: { type: Array, required: true },
|
||||
projectName: { type: String }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -6,89 +6,118 @@
|
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label">
|
||||
<h4 v-if="canManage" class="modal-title truncate !block">
|
||||
{{ i18n.t("experiments.canvas.modal_manage_tags.head_title") }}
|
||||
</h4>
|
||||
<h4 v-else class="modal-title truncate !block">
|
||||
{{ i18n.t("experiments.canvas.modal_manage_tags.head_title_read") }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-4">
|
||||
<div v-if="canManage" class="mb-4">
|
||||
{{ i18n.t("experiments.canvas.modal_manage_tags.explanatory_text") }}
|
||||
</div>
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
<div v-if="this.params.permissions.manage_tags" v-for="tag in tagsList" :key="tag.id"
|
||||
class="flex items-center gap-2 px-3 py-2.5 hover:bg-sn-super-light-grey group">
|
||||
<div class="mb-4">
|
||||
<h5>{{ i18n.t("experiments.canvas.modal_manage_tags.project_tags", { project: this.projectName }) }}</h5>
|
||||
</div>
|
||||
<div class="max-h-80 overflow-y-auto" v-click-outside="finishEditMode">
|
||||
<template v-for="tag in allTags" :key="tag.id">
|
||||
<div
|
||||
class="flex items-center gap-3 px-3 py-2.5 group"
|
||||
:class="{
|
||||
'!bg-sn-super-light-blue': tag.editing,
|
||||
'hover:bg-sn-super-light-grey': canManage
|
||||
}"
|
||||
>
|
||||
<div class="sci-checkbox-container">
|
||||
<input type="checkbox"
|
||||
:disabled="!canManage"
|
||||
class="sci-checkbox"
|
||||
:checked="tag.assigned" @change="toggleTag(tag)">
|
||||
<label class="sci-checkbox-label"></label>
|
||||
</div>
|
||||
<div v-if="!tag.editing" @click="startEditMode(tag)"
|
||||
class="flex grow"
|
||||
:class="{'cursor-pointer': canManage}"
|
||||
>
|
||||
<div class="h-6 px-1.5 flex items-center truncate text-sn-white rounded" :style="{ backgroundColor: tag.attributes.color }">
|
||||
{{ tag.attributes.name }}
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<GeneralDropdown>
|
||||
<template v-slot:field>
|
||||
<div class="h-6 w-6 rounded relative flex items-center justify-center text-sn-white" :style="{ backgroundColor: tag.attributes.color }">
|
||||
a
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<div class="grid grid-cols-4 gap-1">
|
||||
<div v-for="color in tagsColors" :key="color"
|
||||
class="h-6 w-6 rounded relative flex items-center justify-center text-sn-white cursor-pointer"
|
||||
@click.stop="updateTagColor(color, tag)"
|
||||
:style="{ backgroundColor: color }">
|
||||
<i v-if="color == tag.attributes.color" class="sn-icon sn-icon-check"></i>
|
||||
<span v-else>a</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
<input type="text" :value="tag.attributes.name" class="border-0 focus:outline-none bg-transparent" @change="updateTagName($event.target.value, tag)"/>
|
||||
<i @click.stop="finishEditMode($event, tag)" class="sn-icon sn-icon-check cursor-pointer ml-auto"></i>
|
||||
</template>
|
||||
<i v-if="canManage" @click.stop="deleteTag(tag)"
|
||||
class="tw-hidden sn-icon sn-icon-delete cursor-pointer group-hover:block"
|
||||
:class="{
|
||||
'ml-auto': !tag.editing,
|
||||
'!block': tag.editing
|
||||
}"
|
||||
></i>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="canManage">
|
||||
<div class="mb-4 mt-4">
|
||||
{{ i18n.t('experiments.canvas.modal_manage_tags.create_new') }}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<GeneralDropdown>
|
||||
<template v-slot:field>
|
||||
<div class="h-8 w-8 rounded relative" :style="{ backgroundColor: tag.attributes.color }">
|
||||
<div class="absolute top-1 left-1 rounded-full w-1 h-1 bg-white"></div>
|
||||
<div
|
||||
class="h-6 w-6 border border-solid border-transparent rounded relative flex items-center justify-center text-sn-white"
|
||||
:style="{ backgroundColor: newTag.color }"
|
||||
:class="{'!border-sn-grey !text-sn-grey': !newTag.color}"
|
||||
>
|
||||
a
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<div class="grid grid-cols-4 gap-1">
|
||||
<div v-for="color in tagsColors" :key="color"
|
||||
class="h-8 w-8 cursor-pointer rounded relative flex items-center justify-center"
|
||||
@click="updateTagColor(color, tag)"
|
||||
:style="{ backgroundColor: color }">
|
||||
<div class="absolute top-1 left-1 rounded-full w-1 h-1 bg-white"></div>
|
||||
<i v-if="color == tag.attributes.color" class="sn-icon sn-icon-check text-white"></i>
|
||||
class="h-6 w-6 rounded relative flex items-center justify-center text-sn-white cursor-pointer"
|
||||
@click.stop="newTag.color = color"
|
||||
:style="{ backgroundColor: color }">
|
||||
<i v-if="color == newTag.color" class="sn-icon sn-icon-check"></i>
|
||||
<span v-else>a</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
<div class="flex-grow truncate">
|
||||
<InlineEdit
|
||||
:value="tag.attributes.name"
|
||||
:characterLimit="255"
|
||||
attributeName='Tag name'
|
||||
:allowBlank="false"
|
||||
:singleLine="true"
|
||||
@update="(value) => updateTagName(value, tag)"
|
||||
/>
|
||||
</div>
|
||||
<i class="tw-hidden group-hover:block sn-icon sn-icon-close cursor-pointer" @click="unassignTag(tag)"></i>
|
||||
<input type="text" v-model="newTag.name"
|
||||
:placeholder="i18n.t('experiments.canvas.modal_manage_tags.new_tag_name')"
|
||||
class="border-0 focus:outline-none bg-transparent" />
|
||||
<i v-if="validNewTag" @click.stop="createTag" class="sn-icon sn-icon-check cursor-pointer ml-auto"></i>
|
||||
<i @click.stop="newTag = { name: null, color: null }"
|
||||
class="tw-hidden sn-icon sn-icon-delete cursor-pointer "
|
||||
:class="{
|
||||
'ml-auto': !validNewTag,
|
||||
'!block': newTag.name || newTag.color
|
||||
}"
|
||||
></i>
|
||||
</div>
|
||||
<div v-else v-for="tag in tagsList" :key="tag.id" class="flex items-center gap-2 px-3 py-2.5">
|
||||
<div class="h-8 w-8 rounded relative" :style="{ backgroundColor: tag.attributes.color }">
|
||||
<div class="absolute top-1 left-1 rounded-full w-1 h-1 bg-white"></div>
|
||||
</div>
|
||||
<div class="flex-grow truncate">
|
||||
{{ tag.attributes.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="this.params.permissions.manage_tags"
|
||||
class="text-sn-grey flex items-center gap-2 px-3 cursor-pointer
|
||||
py-2.5 hover:bg-sn-super-light-grey"
|
||||
@click="createTag()"
|
||||
>
|
||||
<div class="h-8 w-8 rounded relative border-sn-grey border-solid">
|
||||
<div class="absolute top-1 left-1 rounded-full w-1 h-1 bg-white border-sn-grey border-solid"></div>
|
||||
</div>
|
||||
<div>{{ i18n.t('experiments.canvas.modal_manage_tags.create_new') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div v-if="(tagsToAssign.length > 0 || !this.loadingTags)
|
||||
&& this.params.permissions.manage_tags" class="mr-auto">
|
||||
<GeneralDropdown ref="assignDropdown">
|
||||
<template v-slot:field>
|
||||
<button class="btn btn-primary">{{ i18n.t('general.assign') }}</button>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
<div v-for="tag in tagsToAssign" :key="tag.id"
|
||||
@click="assignTag(tag)"
|
||||
class="px-3 py-2.5 hover:bg-sn-super-light-grey cursor-pointer flex items-center gap-2">
|
||||
<div class="h-6 w-6 rounded relative" :style="{ backgroundColor: tag.attributes.color }">
|
||||
<div class="absolute top-1 left-1 rounded-full w-1 h-1 bg-white"></div>
|
||||
</div>
|
||||
<div class="min-w-[10rem] truncate">{{ tag.attributes.name }}</div>
|
||||
<i @click.stop="deleteTag(tag)" class="sn-icon sn-icon-delete cursor-pointer ml-auto"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
</div>
|
||||
<button class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -103,7 +132,7 @@
|
|||
></ConfirmationModal>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
import InlineEdit from '../../shared/inline_edit.vue';
|
||||
|
@ -122,8 +151,14 @@ export default {
|
|||
},
|
||||
projectTagsUrl: {
|
||||
required: true
|
||||
},
|
||||
projectName: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
'click-outside': vOnClickOutside
|
||||
},
|
||||
components: {
|
||||
InlineEdit,
|
||||
GeneralDropdown,
|
||||
|
@ -134,34 +169,48 @@ export default {
|
|||
return {
|
||||
allTags: [],
|
||||
assignedTags: [],
|
||||
newTag: {
|
||||
name: null,
|
||||
color: null
|
||||
},
|
||||
loadingTags: false,
|
||||
tagToUpdate: null,
|
||||
query: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tagsList() {
|
||||
return this.assignedTags.map((tag) => {
|
||||
const tagObject = this.allTags.find((t) => parseInt(t.id, 10) === tag.attributes.tag_id);
|
||||
const modifiedTag = tag;
|
||||
modifiedTag.attributes = {
|
||||
...tag.attributes,
|
||||
name: tagObject.attributes.name,
|
||||
color: tagObject.attributes.color
|
||||
};
|
||||
return modifiedTag;
|
||||
});
|
||||
validNewTag() {
|
||||
return this.newTag.name && this.newTag.color;
|
||||
},
|
||||
tagsToAssign() {
|
||||
return this.allTags.filter((tag) => (
|
||||
!this.assignedTags.find((t) => t.attributes.tag_id === parseInt(tag.id, 10))
|
||||
&& tag.attributes.name.toLowerCase().includes(this.query.toLowerCase())
|
||||
));
|
||||
canManage() {
|
||||
return this.params.permissions.manage_tags;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadAlltags();
|
||||
},
|
||||
methods: {
|
||||
startEditMode(tag) {
|
||||
if (!this.canManage) return;
|
||||
|
||||
this.finishEditMode();
|
||||
|
||||
tag.editing = true;
|
||||
this.tagToUpdate = tag;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.modal.querySelector('input').focus();
|
||||
});
|
||||
},
|
||||
finishEditMode(e, tag = null) {
|
||||
if (e && e.target.closest('.sn-dropdown')) return;
|
||||
|
||||
const tagToFinish = tag || this.allTags.find((t) => t.editing);
|
||||
|
||||
if (tagToFinish) {
|
||||
tagToFinish.editing = false;
|
||||
this.updateTag(this.tagToUpdate);
|
||||
}
|
||||
},
|
||||
loadAlltags() {
|
||||
this.loadingTags = true;
|
||||
axios.get(this.projectTagsUrl).then((response) => {
|
||||
|
@ -173,16 +222,31 @@ export default {
|
|||
loadAssignedTags() {
|
||||
axios.get(this.params.urls.assigned_tags).then((response) => {
|
||||
this.assignedTags = response.data.data;
|
||||
this.loadingTags = true;
|
||||
this.allTags.forEach((tag) => {
|
||||
const assignedTag = this.assignedTags.find((at) => at.attributes.tag_id === parseInt(tag.id, 10));
|
||||
if (assignedTag) {
|
||||
tag.assigned = true;
|
||||
tag.attributes.urls.unassign = assignedTag.attributes.urls.update;
|
||||
} else {
|
||||
tag.assigned = false;
|
||||
}
|
||||
});
|
||||
this.loadingTags = false;
|
||||
});
|
||||
},
|
||||
toggleTag(tag) {
|
||||
if (tag.assigned) {
|
||||
this.unassignTag(tag);
|
||||
} else {
|
||||
this.assignTag(tag);
|
||||
}
|
||||
},
|
||||
unassignTag(tag) {
|
||||
axios.delete(tag.attributes.urls.update).then(() => {
|
||||
axios.delete(tag.attributes.urls.unassign).then(() => {
|
||||
this.loadAssignedTags();
|
||||
});
|
||||
},
|
||||
assignTag(tag) {
|
||||
this.$refs.assignDropdown.closeMenu();
|
||||
axios.post(this.params.urls.assign_tags, {
|
||||
my_module_tag: {
|
||||
tag_id: tag.id
|
||||
|
@ -191,15 +255,11 @@ export default {
|
|||
this.loadAssignedTags();
|
||||
});
|
||||
},
|
||||
updateTagName(value, tag) {
|
||||
const tagToUpdate = this.allTags.find((t) => parseInt(t.id, 10) === tag.attributes.tag_id);
|
||||
tagToUpdate.attributes.name = value;
|
||||
this.updateTag(tagToUpdate);
|
||||
updateTagName(value) {
|
||||
this.tagToUpdate.attributes.name = value;
|
||||
},
|
||||
updateTagColor(color, tag) {
|
||||
const tagToUpdate = this.allTags.find((t) => parseInt(t.id, 10) === tag.attributes.tag_id);
|
||||
tagToUpdate.attributes.color = color;
|
||||
this.updateTag(tagToUpdate);
|
||||
updateTagColor(color) {
|
||||
this.tagToUpdate.attributes.color = color;
|
||||
},
|
||||
updateTag(tag) {
|
||||
axios.put(tag.attributes.urls.update, {
|
||||
|
@ -211,19 +271,15 @@ export default {
|
|||
});
|
||||
},
|
||||
createTag() {
|
||||
const randmonColor = this.tagsColors[Math.floor(Math.random() * this.tagsColors.length)];
|
||||
axios.post(this.projectTagsUrl, {
|
||||
tag: {
|
||||
name: this.i18n.t('tags.create.new_name'),
|
||||
color: randmonColor
|
||||
},
|
||||
tag: this.newTag,
|
||||
my_module_id: this.params.id
|
||||
}).then(() => {
|
||||
this.newTag = { name: null, color: null };
|
||||
this.loadAlltags();
|
||||
});
|
||||
},
|
||||
async deleteTag(tag) {
|
||||
this.$refs.assignDropdown.closeMenu();
|
||||
const ok = await this.$refs.deleteTagModal.show();
|
||||
if (ok) {
|
||||
axios.delete(tag.attributes.urls.update, {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
users-filter-url="<%= users_filter_projects_path %>"v
|
||||
user-roles-url="<%= user_roles_projects_path %>"
|
||||
:tags-colors="<%= Constants::TAG_COLORS.to_json %>"
|
||||
project-name="<%= @experiment.project.name %>"
|
||||
:statuses-list="<%= MyModuleStatus.all.order(:id).map{ |i| [i.id, i.name] }.to_json %>"
|
||||
project-tags-url="<%= project_tags_path(@experiment.project) %>"
|
||||
canvas-url="<%= view_mode == 'active' ? canvas_experiment_path(@experiment) : module_archive_experiment_path(@experiment) %>"
|
||||
|
|
|
@ -1630,17 +1630,20 @@ en:
|
|||
reload_on_submit: "Save action is running. Reloading this page may cause unexpected behavior."
|
||||
modal_manage_tags:
|
||||
head_title: "Manage tags"
|
||||
head_title_read: "Tags"
|
||||
subtitle: "Showing tags of task %{module}"
|
||||
no_tags: "No tags!"
|
||||
edit_tag: "Edit tag."
|
||||
remove_tag: "Remove tag from task %{module}."
|
||||
delete_tag: "Permanently delete tag from all tasks."
|
||||
delete_tag_confirmation: "Deleting a tag will remove it from all tagged tasks. Are you sure you wish to continue?"
|
||||
explanatory_text: "Add a set of tags to mark the tasks inside this project. Changing the tag applies to all tagged tasks. Deleting a tag removes it from all tagged tasks."
|
||||
delete_tag_confirmation: "Deleting a tag will remove it from all tagged tasks. You wont be able to get it back.<br><br><b>Are you sure you wish to continue?</b>"
|
||||
explanatory_text: "Add a set of tags to mark the tasks inside this project."
|
||||
project_tags: "%{project} project tags"
|
||||
save_tag: "Save tag."
|
||||
cancel_tag: "Cancel changes to the tag."
|
||||
create: "Add"
|
||||
create_new: "Create new tag"
|
||||
create_new: "Create a new tag"
|
||||
new_tag_name: "Tag name"
|
||||
edit:
|
||||
id: "ID:"
|
||||
new_module: "New task"
|
||||
|
|
Loading…
Reference in a new issue