mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-03-04 19:53:19 +08:00
Merge branch 'features/new-datatable' into gc_SCI_10150
This commit is contained in:
commit
c48359c402
37 changed files with 685 additions and 360 deletions
|
@ -146,13 +146,11 @@
|
|||
});
|
||||
// initialize my_module tab remote loading
|
||||
$('#experimentTable, .my-modules-protocols-index, #experiment-canvas')
|
||||
.on('ajax:before', '.edit-tags-link', function() {
|
||||
manageTagsModal.modal('show');
|
||||
.on('click', '.edit-tags-link', function() {
|
||||
if($('#tagsModalComponent').length) {
|
||||
$('#tagsModalComponent').data('tagsModal').open()
|
||||
}
|
||||
})
|
||||
.on('ajax:success', '.edit-tags-link', function(e, data) {
|
||||
$('#manage-module-tags-modal-module').text(data.my_module.name);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
}
|
||||
|
||||
bindEditTagsAjax();
|
||||
|
|
|
@ -933,17 +933,9 @@ function bindEditTagsAjax(elements) {
|
|||
|
||||
// initialize my_module tab remote loading
|
||||
$(elements).find("a.edit-tags-link")
|
||||
.on("ajax:before", function () {
|
||||
var moduleId = $(this).closest(".panel-default").attr("data-module-id");
|
||||
manageTagsModal.attr("data-module-id", moduleId);
|
||||
manageTagsModal.modal('show');
|
||||
})
|
||||
.on("ajax:success", function (e, data) {
|
||||
$("#manage-module-tags-modal-module").text(data.my_module.name);
|
||||
initTagsModalBody(data);
|
||||
})
|
||||
.on('click', function(){
|
||||
$(this).addClass('updated-module-tags');
|
||||
var modal = $(this).closest(".panel-default").find('.tags-modal-component').data('tagsModal').open();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
--ag-icon-font-code-checkbox-indeterminate: asset-url("checkbox/indeterminate.svg");
|
||||
--ag-input-focus-box-shadow: none;
|
||||
--ag-cell-horizontal-padding: .75rem;
|
||||
|
||||
border: 0;
|
||||
|
||||
.ag-cell {
|
||||
|
@ -31,6 +30,10 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ag-header-cell-resize {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.ag-input-field-input:focus {
|
||||
outline: none !important;
|
||||
outline-offset: 0 !important;
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,8 @@ class ExperimentsController < ApplicationController
|
|||
.select('COUNT(DISTINCT comments.id) as task_comments_count')
|
||||
.select('my_modules.*').group(:id)
|
||||
end
|
||||
|
||||
save_view_type('canvas')
|
||||
end
|
||||
|
||||
def my_modules
|
||||
|
@ -456,6 +458,12 @@ class ExperimentsController < ApplicationController
|
|||
params.require(:experiment).require(:view_type)
|
||||
end
|
||||
|
||||
def save_view_type(view_type)
|
||||
view_state = @experiment.current_view_state(current_user)
|
||||
view_state.state['my_modules']['view_type'] = view_type
|
||||
view_state.save!
|
||||
end
|
||||
|
||||
def check_read_permissions
|
||||
current_team_switch(@experiment.project.team) if current_team != @experiment.project.team
|
||||
render_403 unless can_read_experiment?(@experiment) ||
|
||||
|
|
|
@ -38,6 +38,7 @@ class MyModulesController < ApplicationController
|
|||
meta: pagination_dict(my_modules)
|
||||
end
|
||||
format.html do
|
||||
save_view_type('table')
|
||||
render 'my_modules/index'
|
||||
end
|
||||
end
|
||||
|
@ -573,6 +574,12 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def save_view_type(view_type)
|
||||
view_state = @experiment.current_view_state(current_user)
|
||||
view_state.state['my_modules']['view_type'] = view_type
|
||||
view_state.save!
|
||||
end
|
||||
|
||||
def log_activity(type_of, my_module = nil, message_items = {})
|
||||
my_module ||= @my_module
|
||||
message_items = { my_module: my_module.id }.merge(message_items)
|
||||
|
|
68
app/javascript/packs/vue/legacy/tags_modal.js
Normal file
68
app/javascript/packs/vue/legacy/tags_modal.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
/* global I18n dropdownSelector */
|
||||
|
||||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import TagsModal from '../../../vue/my_modules/modals/tags.vue';
|
||||
import { mountWithTurbolinks } from '../helpers/turbolinks.js';
|
||||
|
||||
window.initTagsModalComponent = (id) => {
|
||||
const app = createApp({
|
||||
data() {
|
||||
return {
|
||||
tagsModalOpen: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
$(this.$refs.tagsModal).data('tagsModal', this);
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.tagsModalOpen = true;
|
||||
},
|
||||
close() {
|
||||
this.tagsModalOpen = false;
|
||||
},
|
||||
syncTags(tags) {
|
||||
// My module page
|
||||
if ($('#module-tags-selector').length) {
|
||||
const assignedTags = tags.filter((i) => i.assigned).map((i) => (
|
||||
{
|
||||
value: i.id,
|
||||
label: i.attributes.name,
|
||||
params: {
|
||||
color: i.attributes.color
|
||||
}
|
||||
}
|
||||
));
|
||||
dropdownSelector.setData('#module-tags-selector', assignedTags);
|
||||
}
|
||||
|
||||
// Canvas
|
||||
if ($('#canvas-container').length) {
|
||||
$.ajax({
|
||||
url: $('#canvas-container').attr('data-module-tags-url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success(data) {
|
||||
$.each(data.my_modules, (index, myModule) => {
|
||||
$(`div.panel[data-module-id='${myModule.id}']`)
|
||||
.find('.edit-tags-link')
|
||||
.html(myModule.tags_html);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
app.component('tags-modal', TagsModal);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
mountWithTurbolinks(app, id);
|
||||
};
|
||||
|
||||
document.addEventListener('turbolinks:load', () => {
|
||||
const tagsModalContainers = document.querySelectorAll('.vue-tags-modal:not(.initialized)');
|
||||
tagsModalContainers.forEach((container) => {
|
||||
$(container).addClass('initialized')
|
||||
window.initTagsModalComponent(`#${container.id}`);
|
||||
});
|
||||
});
|
|
@ -122,7 +122,8 @@ export default {
|
|||
{
|
||||
field: 'created_at',
|
||||
headerName: this.i18n.t('experiments.card.start_date'),
|
||||
sortable: true
|
||||
sortable: true,
|
||||
minWidth: 130
|
||||
},
|
||||
{
|
||||
field: 'updated_at',
|
||||
|
|
|
@ -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="h-6 px-1.5 flex items-center max-w-80 truncate text-sn-white rounded"
|
||||
:class="{
|
||||
'cursor-pointer': canManage
|
||||
}"
|
||||
:style="{ backgroundColor: tag.attributes.color }">
|
||||
{{ tag.attributes.name }}
|
||||
</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 grow 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';
|
||||
|
@ -112,7 +141,7 @@ import ConfirmationModal from '../../shared/confirmation_modal.vue';
|
|||
|
||||
export default {
|
||||
name: 'TagsModal',
|
||||
emits: ['close'],
|
||||
emits: ['close', 'tagsLoaded'],
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
|
@ -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,32 @@ 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.$emit('tagsLoaded', this.allTags);
|
||||
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 +256,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 +272,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, {
|
||||
|
|
|
@ -1,16 +1,45 @@
|
|||
<template>
|
||||
|
||||
<span v-if="params.data.tags > 0" @click.stop="openModal" class="text-sn-blue cursor-pointer">
|
||||
{{ params.data.tags }}
|
||||
</span>
|
||||
<span v-else-if="params.data.permissions.manage_tags" @click.stop="openModal" class="text-sn-blue cursor-pointer">
|
||||
{{ i18n.t('experiments.table.add_tag') }}
|
||||
</span>
|
||||
<span v-else>{{ i18n.t('experiments.table.not_set') }}</span>
|
||||
<div class="flex items-center gap-1.5 h-9 mt-0.5">
|
||||
<template v-if="params.data.tags.length > 0 || params.data.permissions.manage_tags">
|
||||
<div v-if="params.data.tags.length > 0"
|
||||
class="h-6 px-1.5 flex items-center rounded text-white max-w-[150px]"
|
||||
:style="{'background': params.data.tags[0].color}">
|
||||
<div class="truncate">{{ params.data.tags[0].name }}</div>
|
||||
</div>
|
||||
<GeneralDropdown v-if="params.data.tags.length > 1" >
|
||||
<template v-slot:field>
|
||||
<div class="h-6 min-w-[24px] text-sn-dark-grey flex items-center justify-center rounded-full text-[.625rem]
|
||||
bg-sn-light-grey border !border-sn-sleepy-grey cursor-pointer">
|
||||
<span>+{{ params.data.tags.length - 1 }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<div>
|
||||
{{ i18n.t('experiments.table.used_tags') }}
|
||||
</div>
|
||||
<hr class="my-2" />
|
||||
<div class="max-h-[200px] overflow-y-auto flex flex-wrap gap-1.5 max-w-[240px]">
|
||||
<div v-for="tag in params.data.tags" :key="tag.id"
|
||||
class="h-6 px-1.5 flex items-center rounded text-white max-w-[150px]"
|
||||
:style="{'background': tag.color}">
|
||||
<div class="truncate">{{ tag.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
<div v-if="params.data.permissions.manage_tags" @click.stop="openModal"
|
||||
class="cursor-pointer text-sn-sleep-grey border !border-dashed h-6 w-6 flex items-center
|
||||
justify-center !border-sn-sleep-grey rounded-full ">
|
||||
<i class="sn-icon sn-icon-new-task"></i>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ i18n.t('experiments.table.not_set') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import GeneralDropdown from '../../shared/general_dropdown.vue';
|
||||
|
||||
export default {
|
||||
name: 'TagsRenderer',
|
||||
props: {
|
||||
|
@ -18,6 +47,9 @@ export default {
|
|||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
GeneralDropdown
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.params.dtComponent.$emit('editTags', null, [this.params.data]);
|
||||
|
|
|
@ -88,7 +88,7 @@ export default {
|
|||
NewProjectModal,
|
||||
NewFolderModal,
|
||||
MoveModal,
|
||||
AccessModal,
|
||||
AccessModal
|
||||
},
|
||||
props: {
|
||||
dataSource: { type: String, required: true },
|
||||
|
@ -102,7 +102,7 @@ export default {
|
|||
userRolesUrl: { type: String },
|
||||
currentFolderId: { type: String },
|
||||
foldersTreeUrl: { type: String },
|
||||
moveToUrl: { type: String },
|
||||
moveToUrl: { type: String }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -114,41 +114,51 @@ export default {
|
|||
objectsToMove: null,
|
||||
reloadingTable: false,
|
||||
folderDeleteDescription: '',
|
||||
exportDescription: '',
|
||||
columnDefs: [
|
||||
{
|
||||
field: 'name',
|
||||
flex: 1,
|
||||
headerName: this.i18n.t('projects.index.card.name'),
|
||||
sortable: true,
|
||||
cellRenderer: this.nameRenderer,
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
headerName: this.i18n.t('projects.index.card.id'),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
headerName: this.i18n.t('projects.index.card.start_date'),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'users',
|
||||
headerName: this.i18n.t('projects.index.card.users'),
|
||||
cellRenderer: 'UsersRenderer',
|
||||
sortable: false,
|
||||
minWidth: 210,
|
||||
notSelectable: true
|
||||
},
|
||||
],
|
||||
exportDescription: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
columnDefs() {
|
||||
const columns = [{
|
||||
field: 'name',
|
||||
flex: 1,
|
||||
headerName: this.i18n.t('projects.index.card.name'),
|
||||
sortable: true,
|
||||
cellRenderer: this.nameRenderer
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
headerName: this.i18n.t('projects.index.card.id'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
headerName: this.i18n.t('projects.index.card.start_date'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'users',
|
||||
headerName: this.i18n.t('projects.index.card.users'),
|
||||
cellRenderer: 'UsersRenderer',
|
||||
sortable: false,
|
||||
minWidth: 210,
|
||||
notSelectable: true
|
||||
}];
|
||||
|
||||
if (this.currentViewMode === 'archived') {
|
||||
columns.push({
|
||||
field: 'archived_on',
|
||||
headerName: this.i18n.t('projects.index.card.archived_date'),
|
||||
sortable: true
|
||||
});
|
||||
}
|
||||
|
||||
return columns;
|
||||
},
|
||||
viewRenders() {
|
||||
return [
|
||||
{ type: 'table' },
|
||||
{ type: 'cards' },
|
||||
{ type: 'cards' }
|
||||
];
|
||||
},
|
||||
toolbarActions() {
|
||||
|
@ -160,7 +170,7 @@ export default {
|
|||
label: this.i18n.t('projects.index.new'),
|
||||
type: 'emit',
|
||||
path: this.createUrl,
|
||||
buttonStyle: 'btn btn-primary',
|
||||
buttonStyle: 'btn btn-primary'
|
||||
});
|
||||
}
|
||||
if (this.createFolderUrl) {
|
||||
|
@ -170,32 +180,32 @@ export default {
|
|||
label: this.i18n.t('projects.index.new_folder'),
|
||||
type: 'emit',
|
||||
path: this.createFolderUrl,
|
||||
buttonStyle: 'btn btn-light',
|
||||
buttonStyle: 'btn btn-light'
|
||||
});
|
||||
}
|
||||
return {
|
||||
left,
|
||||
right: [],
|
||||
right: []
|
||||
};
|
||||
},
|
||||
filters() {
|
||||
const filters = [
|
||||
{
|
||||
key: 'query',
|
||||
type: 'Text',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
type: 'DateRange',
|
||||
label: this.i18n.t('filters_modal.created_on.label'),
|
||||
},
|
||||
label: this.i18n.t('filters_modal.created_on.label')
|
||||
}
|
||||
];
|
||||
|
||||
if (this.currentViewMode === 'archived') {
|
||||
filters.push({
|
||||
key: 'archived_at',
|
||||
type: 'DateRange',
|
||||
label: this.i18n.t('filters_modal.archived_on.label'),
|
||||
label: this.i18n.t('filters_modal.archived_on.label')
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -206,13 +216,13 @@ export default {
|
|||
optionRenderer: this.usersFilterRenderer,
|
||||
labelRenderer: this.usersFilterRenderer,
|
||||
label: this.i18n.t('projects.index.filters_modal.members.label'),
|
||||
placeholder: this.i18n.t('projects.index.filters_modal.members.placeholder'),
|
||||
placeholder: this.i18n.t('projects.index.filters_modal.members.placeholder')
|
||||
});
|
||||
|
||||
filters.push({
|
||||
key: 'folder_search',
|
||||
type: 'Checkbox',
|
||||
label: this.i18n.t('projects.index.filters_modal.folders.label'),
|
||||
label: this.i18n.t('projects.index.filters_modal.folders.label')
|
||||
});
|
||||
|
||||
return filters;
|
||||
|
@ -241,7 +251,7 @@ export default {
|
|||
access(event, rows) {
|
||||
this.accessModalParams = {
|
||||
object: rows[0],
|
||||
roles_path: this.userRolesUrl,
|
||||
roles_path: this.userRolesUrl
|
||||
};
|
||||
},
|
||||
async archive(event, rows) {
|
||||
|
@ -302,7 +312,7 @@ export default {
|
|||
if (ok) {
|
||||
axios.post(event.path, {
|
||||
project_ids: rows.filter((row) => !row.folder).map((row) => row.id),
|
||||
project_folder_ids: rows.filter((row) => row.folder).map((row) => row.id),
|
||||
project_folder_ids: rows.filter((row) => row.folder).map((row) => row.id)
|
||||
}).then((response) => {
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
|
|
|
@ -130,7 +130,8 @@ export default {
|
|||
},
|
||||
{
|
||||
field: 'nr_of_rows',
|
||||
headerName: this.i18n.t('libraries.index.table.number_of_items')
|
||||
headerName: this.i18n.t('libraries.index.table.number_of_items'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'shared_label',
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{{ i18n.t('action_toolbar.no_actions') }}
|
||||
</div>
|
||||
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0">
|
||||
<a :class="`rounded flex gap-2 items-center py-1.5 px-2.5
|
||||
<a :class="`rounded flex gap-2 items-center py-1.5 px-2.5 hover:text-sn-white hover:bg-sn-blue
|
||||
bg-sn-white color-sn-blue hover:no-underline focus:no-underline ${action.button_class}`"
|
||||
:href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'"
|
||||
:id="action.button_id"
|
||||
|
|
|
@ -48,8 +48,10 @@
|
|||
@first-data-rendered="onFirstDataRendered"
|
||||
@sortChanged="setOrder"
|
||||
@columnResized="saveTableState"
|
||||
@columnMoved="saveTableState"
|
||||
@columnMoved="onColumnMoved"
|
||||
@bodyScroll="handleScroll"
|
||||
@columnPinned="handlePin"
|
||||
@columnVisible="handleVisibility"
|
||||
@rowSelected="setSelectedRows"
|
||||
@cellClicked="clickCell"
|
||||
:CheckboxSelectionCallback="withCheckboxes"
|
||||
|
@ -64,7 +66,7 @@
|
|||
:params="actionsParams"
|
||||
@toolbar:action="emitAction" />
|
||||
</div>
|
||||
<div v-if="scrollMode == 'pages'" class="flex items-center py-4">
|
||||
<div v-if="scrollMode == 'pages'" class="flex items-center py-4" :class="{'opacity-0': initializing }">
|
||||
<div class="mr-auto">
|
||||
<Pagination
|
||||
:totalPage="totalPage"
|
||||
|
@ -166,6 +168,7 @@ export default {
|
|||
order: null,
|
||||
totalPage: 0,
|
||||
selectedRows: [],
|
||||
keepSelection: false,
|
||||
searchValue: '',
|
||||
initializing: true,
|
||||
activeFilters: {},
|
||||
|
@ -174,7 +177,9 @@ export default {
|
|||
dataLoading: true,
|
||||
lastPage: false,
|
||||
tableState: null,
|
||||
userSettingsUrl: null
|
||||
userSettingsUrl: null,
|
||||
fetchedTableState: null,
|
||||
gridReady: false
|
||||
};
|
||||
},
|
||||
components: {
|
||||
|
@ -211,6 +216,7 @@ export default {
|
|||
cellRendererParams: {
|
||||
dtComponent: this
|
||||
},
|
||||
pinned: (column.field === 'name' ? 'left' : null),
|
||||
comparator: () => false
|
||||
}));
|
||||
|
||||
|
@ -272,10 +278,18 @@ export default {
|
|||
},
|
||||
perPage() {
|
||||
this.saveTableState();
|
||||
},
|
||||
fetchedTableState(newValue) {
|
||||
if (newValue !== null && this.gridReady) {
|
||||
this.applyTableState(newValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
created() {
|
||||
this.userSettingsUrl = document.querySelector('meta[name="user-settings-url"]').getAttribute('content');
|
||||
this.fetchTableState();
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
window.addEventListener('resize', this.resize);
|
||||
},
|
||||
|
@ -300,39 +314,55 @@ export default {
|
|||
this.loadData();
|
||||
}
|
||||
},
|
||||
fetchAndApplyTableState() {
|
||||
axios
|
||||
.get(this.userSettingsUrl, {
|
||||
params: {
|
||||
key: this.stateKey
|
||||
}
|
||||
})
|
||||
handlePin(event) {
|
||||
if (event.pinned === 'right') {
|
||||
this.columnApi.setColumnPinned(event.column.colId, null);
|
||||
}
|
||||
this.saveTableState();
|
||||
},
|
||||
handleVisibility(event) {
|
||||
if (!event.visible && event.source !== 'api') {
|
||||
this.columnApi.setColumnVisible(event.column.colId, true);
|
||||
}
|
||||
this.saveTableState();
|
||||
},
|
||||
fetchTableState() {
|
||||
axios.get(this.userSettingsUrl, { params: { key: this.stateKey } })
|
||||
.then((response) => {
|
||||
if (response.data.data) {
|
||||
const { currentViewRender, columnsState, perPage, order } = response.data.data;
|
||||
this.tableState = response.data.data;
|
||||
this.currentViewRender = currentViewRender;
|
||||
this.columnsState = columnsState;
|
||||
this.perPage = perPage;
|
||||
this.order = order;
|
||||
|
||||
if (this.order) {
|
||||
this.tableState.columnsState.forEach((column) => {
|
||||
const updatedColumn = column;
|
||||
updatedColumn.sort = this.order.column === column.colId ? this.order.dir : null;
|
||||
return updatedColumn;
|
||||
});
|
||||
this.fetchedTableState = response.data.data;
|
||||
if (this.gridReady && this.fetchedTableState) {
|
||||
this.applyTableState(this.fetchedTableState);
|
||||
}
|
||||
this.columnApi.applyColumnState({
|
||||
state: this.tableState.columnsState,
|
||||
applyOrder: true
|
||||
});
|
||||
}
|
||||
setTimeout(() => {
|
||||
} else {
|
||||
this.initializing = false;
|
||||
}, 200);
|
||||
this.saveTableState();
|
||||
}
|
||||
});
|
||||
},
|
||||
applyTableState(state) {
|
||||
const { currentViewRender, columnsState, perPage, order } = state;
|
||||
this.tableState = state;
|
||||
this.currentViewRender = currentViewRender;
|
||||
this.columnsState = columnsState;
|
||||
this.perPage = perPage;
|
||||
this.order = order;
|
||||
|
||||
if (this.order) {
|
||||
this.tableState.columnsState.forEach((column) => {
|
||||
const updatedColumn = column;
|
||||
updatedColumn.sort = this.order.column === column.colId ? this.order.dir : null;
|
||||
return updatedColumn;
|
||||
});
|
||||
}
|
||||
this.columnApi.applyColumnState({
|
||||
state: this.tableState.columnsState,
|
||||
applyOrder: true
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.initializing = false;
|
||||
}, 200);
|
||||
},
|
||||
getRowClass() {
|
||||
if (this.currentViewMode === 'archived') {
|
||||
return '!bg-sn-super-light-grey';
|
||||
|
@ -358,11 +388,11 @@ export default {
|
|||
this.reloadTable();
|
||||
}
|
||||
},
|
||||
reloadTable() {
|
||||
reloadTable(clearSelection = true) {
|
||||
if (this.dataLoading) return;
|
||||
|
||||
this.dataLoading = true;
|
||||
this.selectedRows = [];
|
||||
if (clearSelection) this.selectedRows = [];
|
||||
this.page = 1;
|
||||
this.loadData(true);
|
||||
},
|
||||
|
@ -380,46 +410,47 @@ export default {
|
|||
})
|
||||
.then((response) => {
|
||||
if (reload) {
|
||||
if (this.gridApi) {
|
||||
this.gridApi.setRowData([]);
|
||||
}
|
||||
if (this.gridApi) this.gridApi.setRowData([]);
|
||||
this.rowData = [];
|
||||
}
|
||||
|
||||
if (this.scrollMode === 'pages') {
|
||||
this.selectedRows = [];
|
||||
if (this.gridApi) {
|
||||
this.gridApi.setRowData(this.formatData(response.data.data));
|
||||
}
|
||||
if (this.gridApi) this.gridApi.setRowData(this.formatData(response.data.data));
|
||||
this.rowData = this.formatData(response.data.data);
|
||||
} else {
|
||||
const newRows = this.rowData.slice();
|
||||
this.formatData(response.data.data).forEach((row) => {
|
||||
newRows.push(row);
|
||||
});
|
||||
this.rowData = newRows;
|
||||
if (this.gridApi) {
|
||||
const viewport = document.querySelector('.ag-body-viewport');
|
||||
const { scrollTop } = viewport;
|
||||
this.gridApi.setRowData(this.rowData);
|
||||
this.$nextTick(() => {
|
||||
viewport.scrollTop = scrollTop;
|
||||
});
|
||||
}
|
||||
this.lastPage = !response.data.meta.next_page;
|
||||
this.handleInfiniteScroll(response);
|
||||
}
|
||||
this.totalPage = response.data.meta.total_pages;
|
||||
this.$emit('tableReloaded');
|
||||
this.dataLoading = false;
|
||||
this.restoreSelection();
|
||||
|
||||
this.handleScroll();
|
||||
});
|
||||
},
|
||||
handleInfiniteScroll(response) {
|
||||
const newRows = this.rowData.slice();
|
||||
this.formatData(response.data.data).forEach((row) => {
|
||||
newRows.push(row);
|
||||
});
|
||||
this.rowData = newRows;
|
||||
if (this.gridApi) {
|
||||
const viewport = document.querySelector('.ag-body-viewport');
|
||||
const { scrollTop } = viewport;
|
||||
this.gridApi.setRowData(this.rowData);
|
||||
this.$nextTick(() => {
|
||||
viewport.scrollTop = scrollTop;
|
||||
});
|
||||
}
|
||||
this.lastPage = !response.data.meta.next_page;
|
||||
},
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
|
||||
this.fetchAndApplyTableState();
|
||||
this.gridReady = true;
|
||||
if (this.fetchedTableState) {
|
||||
this.applyTableState(this.fetchedTableState);
|
||||
}
|
||||
},
|
||||
onFirstDataRendered() {
|
||||
this.resize();
|
||||
|
@ -428,20 +459,23 @@ export default {
|
|||
this.perPage = value;
|
||||
this.page = 1;
|
||||
this.lastPage = false;
|
||||
this.reloadTable();
|
||||
this.reloadTable(false);
|
||||
},
|
||||
setPage(page) {
|
||||
this.page = page;
|
||||
this.loadData();
|
||||
this.loadData(false);
|
||||
},
|
||||
setOrder() {
|
||||
const orderState = this.getOrder(this.columnApi.getColumnState());
|
||||
const [order] = orderState;
|
||||
this.order = order;
|
||||
this.saveTableState();
|
||||
this.reloadTable();
|
||||
this.reloadTable(false);
|
||||
},
|
||||
saveTableState() {
|
||||
if (this.initializing) {
|
||||
return;
|
||||
}
|
||||
const columnsState = this.columnApi ? this.columnApi.getColumnState() : this.tableState?.columnsState || [];
|
||||
const tableState = {
|
||||
columnsState,
|
||||
|
@ -456,8 +490,23 @@ export default {
|
|||
axios.put(this.userSettingsUrl, { settings: [settings] });
|
||||
this.tableState = tableState;
|
||||
},
|
||||
setSelectedRows() {
|
||||
this.selectedRows = this.gridApi.getSelectedRows();
|
||||
restoreSelection() {
|
||||
if (this.gridApi) {
|
||||
this.gridApi.forEachNode((node) => {
|
||||
if (this.selectedRows.find((row) => row.id === node.data.id)) {
|
||||
node.setSelected(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
setSelectedRows(e) {
|
||||
if (!this.rowData.find((row) => row.id === e.data.id)) return;
|
||||
|
||||
if (e.node.isSelected()) {
|
||||
this.selectedRows.push(e.data);
|
||||
} else {
|
||||
this.selectedRows = this.selectedRows.filter((row) => row.id !== e.data.id);
|
||||
}
|
||||
},
|
||||
emitAction(action) {
|
||||
this.$emit(action.name, action, this.selectedRows);
|
||||
|
@ -484,19 +533,15 @@ export default {
|
|||
},
|
||||
hideColumn(column) {
|
||||
this.columnApi.setColumnVisible(column.field, false);
|
||||
this.saveTableState();
|
||||
},
|
||||
showColumn(column) {
|
||||
this.columnApi.setColumnVisible(column.field, true);
|
||||
this.saveTableState();
|
||||
},
|
||||
pinColumn(column) {
|
||||
this.columnApi.setColumnPinned(column.field, 'left');
|
||||
this.saveTableState();
|
||||
},
|
||||
unPinColumn(column) {
|
||||
this.columnApi.setColumnPinned(column.field, null);
|
||||
this.saveTableState();
|
||||
},
|
||||
reorderColumns(columns) {
|
||||
this.columnApi.moveColumns(columns, 1);
|
||||
|
@ -522,7 +567,12 @@ export default {
|
|||
dir
|
||||
};
|
||||
this.saveTableState();
|
||||
this.reloadTable();
|
||||
this.reloadTable(false);
|
||||
},
|
||||
onColumnMoved(event) {
|
||||
if (event.finished) {
|
||||
this.saveTableState();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export default {
|
||||
template: `
|
||||
<div class="w-full grid items-center gap-2 grid-cols-[auto_1.5rem] cursor-pointer"
|
||||
<div class="w-full grid items-center gap-2 grid-cols-[auto_1.5rem]"
|
||||
:class="{'cursor-pointer': params.enableSorting}"
|
||||
:data-e2e="'e2e-CO-TableHeader-' + params.column.colId "
|
||||
@click="onSortRequested((activeSort == 'asc' ? 'desc' : 'asc'), $event)">
|
||||
<div v-if="params.html" class="customHeaderLabel truncate" v-html="params.html"></div>
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
:title="i18n.t('experiments.table.column_display_modal.title')"
|
||||
class="btn btn-light icon-btn btn-black"
|
||||
>
|
||||
<i class="sn-icon sn-icon-manage-table"></i>
|
||||
<i class="sn-icon sn-icon-manage-columns"></i>
|
||||
</button>
|
||||
<GeneralDropdown v-if="currentViewRender === 'cards'" ref="dropdown" position="right">
|
||||
<template v-slot:field>
|
||||
|
@ -252,6 +252,7 @@ export default {
|
|||
if (ok) {
|
||||
this.$emit('resetColumnsToDefault');
|
||||
}
|
||||
this.showColumnsModal = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="max-h-[400px] p-3.5 pt-0">
|
||||
<div class="max-h-[40vh] px-3.5 overflow-y-auto">
|
||||
<div v-for="filter in filters" :key="filter.key">
|
||||
<Component
|
||||
:is="`${filter.type}Filter`"
|
||||
|
@ -28,7 +28,7 @@
|
|||
@update="updateFilter" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3.5 pt-0.5 flex items-center justify-end gap-4">
|
||||
<div class="p-3.5 flex items-center justify-end gap-4">
|
||||
<div @click.prevent="clearFilters" class="btn btn-secondary">
|
||||
{{ i18n.t('filters_modal.clear_btn') }}
|
||||
</div>
|
||||
|
|
|
@ -3,19 +3,20 @@
|
|||
<div ref="field" class="cursor-pointer" @click.stop="isOpen = (!isOpen || fieldOnlyOpen)">
|
||||
<slot name="field"></slot>
|
||||
</div>
|
||||
<teleport to="body">
|
||||
<div ref="flyout"
|
||||
class="sn-dropdown fixed z-[3000] bg-sn-white inline-block
|
||||
rounded p-2.5 sn-shadow-menu-sm"
|
||||
:class="{
|
||||
'right-0': position === 'right',
|
||||
'left-0': position === 'left',
|
||||
}"
|
||||
v-if="isOpen"
|
||||
>
|
||||
<slot name="flyout"></slot>
|
||||
</div>
|
||||
</teleport>
|
||||
<template v-if="isOpen">
|
||||
<teleport to="body">
|
||||
<div ref="flyout"
|
||||
class="sn-dropdown fixed z-[3000] bg-sn-white inline-block
|
||||
rounded p-2.5 sn-shadow-menu-sm"
|
||||
:class="{
|
||||
'right-0': position === 'right',
|
||||
'left-0': position === 'left',
|
||||
}"
|
||||
>
|
||||
<slot name="flyout"></slot>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -6,57 +6,58 @@
|
|||
<i v-if="caret && isOpen" class="sn-icon sn-icon-up"></i>
|
||||
<i v-else-if="caret" class="sn-icon sn-icon-down"></i>
|
||||
</button>
|
||||
<teleport to="body">
|
||||
<div ref="flyout"
|
||||
v-if="isOpen"
|
||||
class="fixed z-[3000] sn-menu-dropdown bg-sn-white inline-block rounded p-2.5 sn-shadow-menu-sm flex flex-col gap-[1px]"
|
||||
:class="{
|
||||
'right-0': position === 'right',
|
||||
'left-0': position === 'left',
|
||||
}"
|
||||
>
|
||||
<span v-for="(item, i) in listItems" :key="i" class="contents">
|
||||
<div v-if="item.dividerBefore" class="border-0 border-t border-solid border-sn-light-grey"></div>
|
||||
<a :href="item.url" v-if="!item.submenu"
|
||||
:target="item.url_target || '_self'"
|
||||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
:data-toggle="item.modalTarget && 'modal'"
|
||||
:data-target="item.modalTarget"
|
||||
class="block whitespace-nowrap rounded px-3 py-2.5 hover:!text-sn-blue hover:no-underline cursor-pointer hover:bg-sn-super-light-grey leading-5"
|
||||
@click="handleClick($event, item)"
|
||||
>
|
||||
{{ item.text }}
|
||||
</a>
|
||||
<div v-else class="-mx-2.5 px-2.5 group relative">
|
||||
<span
|
||||
<template v-if="isOpen">
|
||||
<teleport to="body">
|
||||
<div ref="flyout"
|
||||
class="fixed z-[3000] sn-menu-dropdown bg-sn-white inline-block rounded p-2.5 sn-shadow-menu-sm flex flex-col gap-[1px]"
|
||||
:class="{
|
||||
'right-0': position === 'right',
|
||||
'left-0': position === 'left',
|
||||
}"
|
||||
>
|
||||
<span v-for="(item, i) in listItems" :key="i" class="contents">
|
||||
<div v-if="item.dividerBefore" class="border-0 border-t border-solid border-sn-light-grey"></div>
|
||||
<a :href="item.url" v-if="!item.submenu"
|
||||
:target="item.url_target || '_self'"
|
||||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
class="flex group items-center rounded relative text-sn-blue whitespace-nowrap px-3 py-2.5 hover:no-underline cursor-pointer
|
||||
group-hover:bg-sn-super-light-blue hover:!bg-sn-super-light-grey"
|
||||
:data-toggle="item.modalTarget && 'modal'"
|
||||
:data-target="item.modalTarget"
|
||||
class="block whitespace-nowrap rounded px-3 py-2.5 hover:!text-sn-blue hover:no-underline cursor-pointer hover:bg-sn-super-light-grey leading-5"
|
||||
@click="handleClick($event, item)"
|
||||
>
|
||||
{{ item.text }}
|
||||
<i class="sn-icon sn-icon-right ml-auto"></i>
|
||||
</span>
|
||||
<div
|
||||
class="absolute bg-sn-white top-0 rounded p-2.5 sn-shadow-menu-sm flex flex-col gap-[1px] tw-hidden group-hover:block"
|
||||
:class="{
|
||||
'left-0 ml-[100%]': item.position === 'right',
|
||||
'right-0 mr-[100%]': item.position === 'left'
|
||||
}"
|
||||
>
|
||||
<a v-for="(sub_item, si) in item.submenu" :key="si"
|
||||
:href="sub_item.url"
|
||||
:traget="sub_item.url_target || '_self'"
|
||||
</a>
|
||||
<div v-else class="-mx-2.5 px-2.5 group relative">
|
||||
<span
|
||||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
class="block whitespace-nowrap rounded px-3 py-2.5 hover:!text-sn-blue hover:no-underline cursor-pointer hover:bg-sn-super-light-grey leading-5"
|
||||
@click="handleClick($event, sub_item)"
|
||||
class="flex group items-center rounded relative text-sn-blue whitespace-nowrap px-3 py-2.5 hover:no-underline cursor-pointer
|
||||
group-hover:bg-sn-super-light-blue hover:!bg-sn-super-light-grey"
|
||||
>
|
||||
{{ sub_item.text }}
|
||||
</a>
|
||||
{{ item.text }}
|
||||
<i class="sn-icon sn-icon-right ml-auto"></i>
|
||||
</span>
|
||||
<div
|
||||
class="absolute bg-sn-white top-0 rounded p-2.5 sn-shadow-menu-sm flex flex-col gap-[1px] tw-hidden group-hover:block"
|
||||
:class="{
|
||||
'left-0 ml-[100%]': item.position === 'right',
|
||||
'right-0 mr-[100%]': item.position === 'left'
|
||||
}"
|
||||
>
|
||||
<a v-for="(sub_item, si) in item.submenu" :key="si"
|
||||
:href="sub_item.url"
|
||||
:traget="sub_item.url_target || '_self'"
|
||||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
class="block whitespace-nowrap rounded px-3 py-2.5 hover:!text-sn-blue hover:no-underline cursor-pointer hover:bg-sn-super-light-grey leading-5"
|
||||
@click="handleClick($event, sub_item)"
|
||||
>
|
||||
{{ sub_item.text }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</teleport>
|
||||
</span>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -46,44 +46,46 @@
|
|||
<i v-else class="sn-icon ml-auto"
|
||||
:class="{ 'sn-icon-down': !isOpen, 'sn-icon-up': isOpen, 'text-sn-grey': disabled}"></i>
|
||||
</div>
|
||||
<teleport to="body">
|
||||
<div v-if="isOpen" ref="flyout"
|
||||
class="sn-select-dropdown bg-white inline-block sn-shadow-menu-sm rounded w-full
|
||||
fixed z-[3000]">
|
||||
<div v-if="multiple && withCheckboxes" class="p-2.5 pb-0">
|
||||
<div @click="selectAll" :class="sizeClass"
|
||||
class="border-x-0 border-transparent border-solid border-b-sn-light-grey
|
||||
py-1.5 px-3 cursor-pointer flex items-center gap-2 shrink-0">
|
||||
<div class="sn-checkbox-icon"
|
||||
:class="selectAllState"
|
||||
></div>
|
||||
{{ i18n.t('general.select_all') }}
|
||||
</div>
|
||||
</div>
|
||||
<perfect-scrollbar class="p-2.5 flex flex-col max-h-80 relative" :class="{ 'pt-0': withCheckboxes }">
|
||||
<template v-for="option in filteredOptions" :key="option[0]">
|
||||
<div
|
||||
@click.stop="setValue(option[0])"
|
||||
class="py-1.5 px-3 rounded cursor-pointer flex items-center gap-2 shrink-0"
|
||||
:class="[sizeClass, {'!bg-sn-super-light-blue': valueSelected(option[0])}]"
|
||||
>
|
||||
<div v-if="withCheckboxes"
|
||||
class="sn-checkbox-icon"
|
||||
:class="{
|
||||
'checked': valueSelected(option[0]),
|
||||
'unchecked': !valueSelected(option[0]),
|
||||
}"
|
||||
<template v-if="isOpen">
|
||||
<teleport to="body">
|
||||
<div ref="flyout"
|
||||
class="sn-select-dropdown bg-white inline-block sn-shadow-menu-sm rounded w-full
|
||||
fixed z-[3000]">
|
||||
<div v-if="multiple && withCheckboxes" class="p-2.5 pb-0">
|
||||
<div @click="selectAll" :class="sizeClass"
|
||||
class="border-x-0 border-transparent border-solid border-b-sn-light-grey
|
||||
py-1.5 px-3 cursor-pointer flex items-center gap-2 shrink-0">
|
||||
<div class="sn-checkbox-icon"
|
||||
:class="selectAllState"
|
||||
></div>
|
||||
<div v-if="optionRenderer" v-html="optionRenderer(option)"></div>
|
||||
<div v-else >{{ option[1] }}</div>
|
||||
{{ i18n.t('general.select_all') }}
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="filteredOptions.length === 0" class="text-sn-grey text-center py-2.5">
|
||||
{{ noOptionsPlaceholder || this.i18n.t('general.select_dropdown.no_options_placeholder') }}
|
||||
</div>
|
||||
</perfect-scrollbar>
|
||||
</div>
|
||||
</teleport>
|
||||
<perfect-scrollbar class="p-2.5 flex flex-col max-h-80 relative" :class="{ 'pt-0': withCheckboxes }">
|
||||
<template v-for="option in filteredOptions" :key="option[0]">
|
||||
<div
|
||||
@click.stop="setValue(option[0])"
|
||||
class="py-1.5 px-3 rounded cursor-pointer flex items-center gap-2 shrink-0"
|
||||
:class="[sizeClass, {'!bg-sn-super-light-blue': valueSelected(option[0])}]"
|
||||
>
|
||||
<div v-if="withCheckboxes"
|
||||
class="sn-checkbox-icon"
|
||||
:class="{
|
||||
'checked': valueSelected(option[0]),
|
||||
'unchecked': !valueSelected(option[0]),
|
||||
}"
|
||||
></div>
|
||||
<div v-if="optionRenderer" v-html="optionRenderer(option)"></div>
|
||||
<div v-else >{{ option[1] }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="filteredOptions.length === 0" class="text-sn-grey text-center py-2.5">
|
||||
{{ noOptionsPlaceholder || this.i18n.t('general.select_dropdown.no_options_placeholder') }}
|
||||
</div>
|
||||
</perfect-scrollbar>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
@ -228,6 +230,9 @@ export default {
|
|||
this.fetchOptions();
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.newValue = newValue;
|
||||
},
|
||||
isOpen() {
|
||||
if (this.isOpen) {
|
||||
this.$nextTick(() => {
|
||||
|
|
|
@ -132,7 +132,13 @@ module Lists
|
|||
end
|
||||
|
||||
def tags
|
||||
object.tags.length
|
||||
object.tags.map do |tag|
|
||||
{
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
color: tag.color
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def comments
|
||||
|
|
|
@ -10,7 +10,7 @@ module Lists
|
|||
:urls, :shared_read, :shared_write, :shareable_write
|
||||
|
||||
def nr_of_rows
|
||||
object.repository_rows.count
|
||||
object[:row_count]
|
||||
end
|
||||
|
||||
def shared
|
||||
|
|
|
@ -18,7 +18,7 @@ class UserAssignmentSerializer < ActiveModel::Serializer
|
|||
{
|
||||
id: object.user.id,
|
||||
name: object.user.name,
|
||||
avatar_url: avatar_path(object, :icon_small)
|
||||
avatar_url: avatar_path(object.user, :icon_small)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -13,12 +13,14 @@ module Lists
|
|||
'LEFT OUTER JOIN users AS archivers ' \
|
||||
'ON repositories.archived_by_id = archivers.id'
|
||||
)
|
||||
.includes(:repository_rows)
|
||||
.joins(:repository_rows)
|
||||
.joins(:team)
|
||||
.select('repositories.* AS repositories')
|
||||
.select('teams.name AS team_name')
|
||||
.select('creators.full_name AS created_by_user')
|
||||
.select('archivers.full_name AS archived_by_user')
|
||||
.select('repositories.*')
|
||||
.select('MAX(teams.name) AS team_name')
|
||||
.select('COUNT(repository_rows.*) AS row_count')
|
||||
.select('MAX(creators.full_name) AS created_by_user')
|
||||
.select('MAX(archivers.full_name) AS archived_by_user')
|
||||
.group('repositories.id')
|
||||
|
||||
view_mode = @params[:view_mode] || 'active'
|
||||
|
||||
|
@ -43,11 +45,12 @@ module Lists
|
|||
def sortable_columns
|
||||
@sortable_columns ||= {
|
||||
name: 'repositories.name',
|
||||
team: 'teams.name',
|
||||
created_by: 'creators.full_name',
|
||||
team: 'team_name',
|
||||
created_by: 'created_by_user',
|
||||
created_at: 'repositories.created_at',
|
||||
archived_on: 'repositories.archived_on',
|
||||
archived_by: 'archivers.full_name'
|
||||
archived_by: 'archived_by_user',
|
||||
nr_of_rows: 'row_count'
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -114,5 +114,31 @@
|
|||
<div role="tabpanel" class="tab-pane" id="<%= my_module.id %>_comments" data-contents="comments"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tagsModalContainer-<%= my_module.id %>" class="vue-tags-modal">
|
||||
<div ref="tagsModal" class="tags-modal-component" id="tagsModalComponent-<%= my_module.id %>"></div>
|
||||
<teleport to="body">
|
||||
<tags-modal v-if="tagsModalOpen"
|
||||
:params="<%=
|
||||
{
|
||||
id: my_module.id,
|
||||
permissions: {
|
||||
manage_tags: can_manage_my_module_tags?(my_module)
|
||||
},
|
||||
urls: {
|
||||
assigned_tags: assigned_tags_my_module_my_module_tags_path(my_module),
|
||||
assign_tags: my_module_my_module_tags_path(my_module)
|
||||
}
|
||||
}.to_json
|
||||
%>"
|
||||
:tags-colors="<%= Constants::TAG_COLORS.to_json %>"
|
||||
project-name="<%= my_module.experiment.project.name %>"
|
||||
project-tags-url="<%= project_tags_path(my_module.experiment.project) %>"
|
||||
@close="close"
|
||||
@tags-loaded="syncTags"
|
||||
/>
|
||||
</teleport>
|
||||
</div>
|
||||
<%= javascript_include_tag 'vue_legacy_tags_modal' %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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) %>"
|
||||
|
|
|
@ -155,6 +155,32 @@
|
|||
<!-- Consume Stock Modal -->
|
||||
<%= render partial: 'my_modules/repositories/consume_stock_modal'%>
|
||||
|
||||
<!-- Tags modal -->
|
||||
<div id="tagsModalContainer" class="vue-tags-modal">
|
||||
<div ref="tagsModal" id="tagsModalComponent"></div>
|
||||
<teleport to="body">
|
||||
<tags-modal v-if="tagsModalOpen"
|
||||
:params="<%=
|
||||
{
|
||||
id: @my_module.id,
|
||||
permissions: {
|
||||
manage_tags: can_manage_my_module_tags?(@my_module)
|
||||
},
|
||||
urls: {
|
||||
assigned_tags: assigned_tags_my_module_my_module_tags_path(@my_module),
|
||||
assign_tags: my_module_my_module_tags_path(@my_module)
|
||||
}
|
||||
}.to_json
|
||||
%>"
|
||||
:tags-colors="<%= Constants::TAG_COLORS.to_json %>"
|
||||
project-name="<%= @experiment.project.name %>"
|
||||
project-tags-url="<%= project_tags_path(@experiment.project) %>"
|
||||
@close="close"
|
||||
@tags-loaded="syncTags"
|
||||
/>
|
||||
</teleport>
|
||||
</div>
|
||||
|
||||
<%= javascript_include_tag 'inputmask' %>
|
||||
<%= stylesheet_link_tag 'datatables' %>
|
||||
<%= javascript_include_tag "handsontable.full" %>
|
||||
|
@ -169,4 +195,4 @@
|
|||
<%= javascript_include_tag "protocols/new_protocol" %>
|
||||
|
||||
<%= javascript_include_tag 'vue_protocol' %>
|
||||
|
||||
<%= javascript_include_tag 'vue_legacy_tags_modal' %>
|
||||
|
|
|
@ -570,7 +570,7 @@ en:
|
|||
visibility: "Visible to"
|
||||
users: "Access"
|
||||
name: "Project name"
|
||||
archived_date: "Archived"
|
||||
archived_date: "Archived on"
|
||||
end_of_list_placeholder: 'You’ve reached the end of the list'
|
||||
folder:
|
||||
description: "%{projects_count} projects | %{folders_count} folders"
|
||||
|
@ -1462,7 +1462,7 @@ en:
|
|||
id: "ID"
|
||||
start_date: "Started on"
|
||||
modified_date: "Modified date"
|
||||
archived_date: "Archived date"
|
||||
archived_date: "Archived on"
|
||||
completed_task: "Completed"
|
||||
completed_value: "%{completed}/%{all} tasks"
|
||||
description: "Description"
|
||||
|
@ -1537,7 +1537,7 @@ en:
|
|||
id_html: 'ID'
|
||||
task_name_html: 'Task name'
|
||||
due_date_html: 'Due'
|
||||
archived_html: 'Archived'
|
||||
archived_html: 'Archived on'
|
||||
age_html: 'Age'
|
||||
results_html: 'Results'
|
||||
status_html: 'Status'
|
||||
|
@ -1601,6 +1601,7 @@ en:
|
|||
archived_tasks: 'Go to Archived tasks'
|
||||
active_tasks: 'Go to Active tasks'
|
||||
add_tag: '+ Add tag'
|
||||
used_tags: 'Used tags'
|
||||
search: 'Manage assignees'
|
||||
not_set: 'not set'
|
||||
archive_group:
|
||||
|
@ -1630,17 +1631,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"
|
||||
|
@ -4096,7 +4100,7 @@ en:
|
|||
updated_on:
|
||||
label: "Modified date"
|
||||
archived_on:
|
||||
label: "Archived date"
|
||||
label: "Archived on"
|
||||
recent_searches_label: "Recent searches"
|
||||
show_btn:
|
||||
one: "Show results"
|
||||
|
|
|
@ -376,7 +376,9 @@ Rails.application.routes.draw do
|
|||
end
|
||||
get 'project_folders/:project_folder_id', to: 'projects#index', as: :project_folder_projects
|
||||
|
||||
resources :experiments, only: %i(index update) do
|
||||
get 'projects/:project_id', to: 'experiments#index'
|
||||
get 'projects/:project_id/experiments', to: 'experiments#index', as: :experiments
|
||||
resources :experiments, only: %i(update) do
|
||||
collection do
|
||||
get 'inventory_assigning_experiment_filter'
|
||||
get 'clone_modal', action: :clone_modal
|
||||
|
@ -421,7 +423,9 @@ Rails.application.routes.draw do
|
|||
|
||||
# Show action is a popup (JSON) for individual module in full-zoom canvas,
|
||||
# as well as 'module info' page for single module (HTML)
|
||||
resources :my_modules, path: '/modules', only: [:show, :update, :index] do
|
||||
get 'experiments/:experiment_id/table', to: 'my_modules#index'
|
||||
get 'experiments/:experiment_id/modules', to: 'my_modules#index', as: :my_modules
|
||||
resources :my_modules, path: '/modules', only: [:show, :update] do
|
||||
post 'save_table_state', on: :collection, defaults: { format: 'json' }
|
||||
|
||||
collection do
|
||||
|
|
|
@ -58,7 +58,8 @@ const entryList = {
|
|||
vue_reports_table: './app/javascript/packs/vue/reports_table.js',
|
||||
vue_open_locally_menu: './app/javascript/packs/vue/open_locally_menu.js',
|
||||
vue_scinote_edit_download: './app/javascript/packs/vue/scinote_edit_download.js',
|
||||
vue_design_system_modals: './app/javascript/packs/vue/design_system/modals.js'
|
||||
vue_design_system_modals: './app/javascript/packs/vue/design_system/modals.js',
|
||||
vue_legacy_tags_modal: './app/javascript/packs/vue/legacy/tags_modal.js'
|
||||
};
|
||||
|
||||
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
||||
|
|
BIN
vendor/assets/stylesheets/fonts/SN-icon-font.eot
vendored
BIN
vendor/assets/stylesheets/fonts/SN-icon-font.eot
vendored
Binary file not shown.
|
@ -171,7 +171,9 @@
|
|||
<glyph unicode="" glyph-name="history-search" data-tags="history seaerch" d="M512 789.333c-128.853 0-240.64-71.68-298.667-176.64v91.307h-42.667v-170.667h170.667v42.667h-98.987c48.213 100.693 150.613 170.667 269.653 170.667 164.693 0 298.667-133.973 298.667-298.667s-133.973-298.667-298.667-298.667c-134.827 0-249.173 90.027-286.293 213.333h-43.947c37.973-147.2 171.093-256 330.24-256 188.587 0 341.333 152.747 341.333 341.333s-152.747 341.333-341.333 341.333zM676.267 292.267l-29.867-29.867-177.067 177.067v221.867h42.667v-204.8l164.267-164.267z" />
|
||||
<glyph unicode="" glyph-name="item" data-tags="item" d="M682.667 661.333c0-94.257-76.412-170.667-170.667-170.667-94.257 0-170.667 76.41-170.667 170.667s76.41 170.667 170.667 170.667c94.255 0 170.667-76.41 170.667-170.667zM725.333 106.667c-70.694 0-128 57.306-128 128s57.306 128 128 128c70.694 0 128-57.306 128-128s-57.306-128-128-128zM725.333 64c94.255 0 170.667 76.412 170.667 170.667s-76.412 170.667-170.667 170.667c-94.255 0-170.667-76.412-170.667-170.667s76.412-170.667 170.667-170.667zM298.667 106.667c-70.692 0-128 57.306-128 128s57.308 128 128 128c70.692 0 128-57.306 128-128s-57.308-128-128-128zM298.667 64c94.257 0 170.667 76.412 170.667 170.667s-76.41 170.667-170.667 170.667c-94.257 0-170.667-76.412-170.667-170.667s76.41-170.667 170.667-170.667z" />
|
||||
<glyph unicode="" glyph-name="move-arrows" data-tags="move-arrows" d="M330.988 564.239l-81.66-81.675h134.673v-42.667h-134.69l81.677-81.694-30.165-30.165-133.184 133.184 133.184 133.182 30.165-30.165zM693.013 564.239l30.165 30.165 133.184-133.182-133.184-133.184-30.165 30.165 81.677 81.694h-134.69v42.667h134.673l-81.66 81.675zM512.597 520.956l60.352-60.331-60.352-60.352-60.331 60.352 60.331 60.331zM408.982 280.209l81.684-81.668v134.69h42.667v-134.694l81.685 81.673 30.165-30.165-133.184-133.184-133.183 133.184 30.165 30.165zM408.982 642.234l-30.165 30.165 133.183 133.184 133.184-133.184-30.165-30.165-81.685 81.669v-134.674h-42.667v134.673l-81.684-81.668z" />
|
||||
<glyph unicode="" glyph-name="teams-small" data-tags="teams-small" d="M881.493 424.747c-8.107 10.658-18.347 19.614-31.147 26.010-28.16 13.641-55.467 23.023-83.2 29.841-28.16 6.822-84.907 10.231-84.907 10.231l0.427-42.633c0 0 50.347-2.982 75.093-8.951s49.067-14.498 72.96-26.010c7.253-3.836 12.8-8.525 16.64-13.641 3.84-5.542 5.973-11.511 5.973-18.334v-18.33h-128v-42.637h170.667v60.966c0 15.774-5.12 30.699-14.507 43.486zM691.2 268.284c10.667-5.542 19.2-12.791 25.173-20.894 5.973-8.098 8.96-17.476 8.96-27.285v-27.712h-426.667v27.712c0 9.809 2.987 19.187 8.96 27.285 5.973 8.102 14.507 14.925 25.173 20.894 104.030 50.095 252.523 50.987 358.4 0zM314.453 306.227c-17.92-9.378-31.147-20.463-40.96-34.108-11.52-15.347-17.493-33.681-17.493-52.442v-70.345h512v70.345c0 18.761-5.973 37.094-17.067 52.442-9.813 13.218-23.040 24.303-39.253 33.254-119.223 57.647-276.817 58.982-397.227 0.853zM512 618.7c46.933 0 85.333-38.371 85.333-85.268 0-46.895-38.4-85.27-85.333-85.27s-85.333 38.374-85.333 85.27c0 46.897 38.4 85.268 85.333 85.268zM512 661.333c-70.4 0-128-57.556-128-127.901s57.6-127.902 128-127.902c70.4 0 128 57.557 128 127.902s-57.6 127.901-128 127.901zM170.667 362.931v18.33c0 6.822 2.133 12.791 5.973 18.334s9.387 10.231 16.64 13.641c23.893 11.511 48.213 20.041 72.96 26.010s49.92 8.951 75.093 8.951v42.633c-28.16 0-56.747-3.409-84.907-10.231-27.733-6.396-55.040-16.201-81.493-28.988-14.507-7.676-24.747-16.205-32.427-26.863-9.387-12.787-14.507-27.712-14.507-43.486v-60.966h170.667v42.637h-128zM341.333 661.366c12.373 0 21.333-3.837 29.867-12.364l30.72 29.844c-16.64 16.627-36.693 25.154-60.587 25.154s-43.947-8.527-60.587-25.154c-16.64-16.627-24.747-36.665-24.747-60.113s8.107-43.913 24.747-60.54c16.64-16.627 36.693-24.728 60.587-24.728v42.634c-12.373 0-21.76 3.837-30.293 12.364s-12.373 17.906-12.373 30.27c0 12.364 3.84 21.317 12.8 30.27 8.533 8.527 17.92 12.364 29.867 12.364zM682.667 661.366c12.373 0 21.333-3.837 29.867-12.364 8.533-8.953 12.8-18.333 12.8-30.27s-3.84-21.743-12.373-30.27c-8.533-8.527-17.92-12.364-30.293-12.364v-42.634c23.893 0 43.947 8.101 60.587 24.728s24.747 36.665 24.747 60.54c0 23.875-8.107 43.060-24.747 60.113-16.64 16.627-36.693 25.154-60.587 25.154s-43.947-8.527-60.587-25.154l30.72-29.844c8.533 8.527 17.92 12.364 29.867 12.364z" />
|
||||
<glyph unicode="" glyph-name="refresh" data-tags="refresh" d="M810.667 283.307c-58.027-104.96-169.813-176.64-298.667-176.64-188.587 0-341.333 152.747-341.333 341.333h42.667c0-164.693 133.973-298.667 298.667-298.667 119.040 0 221.44 69.973 269.653 170.667h-98.987v42.667h170.667v-170.667h-42.667v91.307zM213.333 612.693c58.027 104.96 169.813 176.64 298.667 176.64 188.587 0 341.333-152.747 341.333-341.333h-42.667c0 164.693-133.973 298.667-298.667 298.667-119.040 0-221.44-69.973-269.653-170.667h98.987v-42.667h-170.667v170.667h42.667v-91.307z" />
|
||||
<glyph unicode="" glyph-name="pin" data-tags="pin" d="M411.605 357.077l-99.584 99.541c0 0 0.683 21.333 14.293 39.893 29.141 39.68 86.443 41.813 129.195 33.536l67.371 66.304c-14.933 49.963 71.509 107.648 71.509 107.648 65.152-65.195 130.347-130.389 195.541-195.584-4.736-6.357-9.515-12.715-14.293-19.072-22.912-30.421-54.144-56.107-91.136-49.877l-70.827-68.651c0.981-7.339 0.896-0.64 2.517-24.576 3.072-45.44-13.653-92.245-56.149-113.92l-15.915-7.765-102.357 102.357-155.563-155.605-30.208 30.208 155.605 155.563zM587.392 648.747c-14.677-12.117-30.208-30.933-21.845-44.203l10.453-15.403-106.368-106.411c-42.411 10.795-92.715 13.568-109.995-13.099l192.427-192.683c2.005 1.408 3.925 2.901 5.803 4.48 26.155 23.040 16.299 64.853 10.624 104.533l102.187 102.187c0 0 31.36-20.011 62.635 16.555l-144.853 144.853c-0.384-0.256-0.725-0.555-1.067-0.811z" />
|
||||
<glyph unicode="" glyph-name="pinned" data-tags="pinned" d="M483.341 312.717l-140.803-0.030c0 0-14.602 15.569-18.102 38.319-7.452 48.661 31.558 90.688 67.641 115.068l0.754 94.52c-45.888 24.77-25.554 126.683-25.554 126.683 92.17-0.030 184.369-0.030 276.567-0.030 1.148-7.844 2.261-15.719 3.379-23.593 5.312-37.712 1.387-77.959-29.175-99.712l-1.536-98.624c5.884-4.497 1.084 0.179 19.157-15.599 34.304-29.961 55.573-74.88 40.849-120.256l-5.76-16.747h-144.755l0.030-220.028h-42.722l0.030 220.028zM401.399 643.26c-1.81-18.947 0.513-43.234 15.809-46.703l18.281-3.5 0.030-150.458c-37.62-22.353-75.151-55.966-68.514-87.040l272.311-0.179c0.422 2.415 0.725 4.826 0.939 7.27 2.202 34.786-34.334 57.382-66.406 81.429v144.513c0 0 36.326 8.025 32.585 55.995h-204.854c-0.090-0.453-0.12-0.905-0.181-1.327z" />
|
||||
<glyph unicode="" glyph-name="manage-columns" data-tags="manage-columns" d="M810.667 789.333c23.467 0 42.667-19.2 42.667-42.667v-597.333c0-23.467-19.2-42.667-42.667-42.667h-597.333c-23.467 0-42.667 19.2-42.667 42.667v597.333c0 23.467 19.2 42.667 42.667 42.667h597.333zM810.667 746.667h-597.333v-128h597.333v128zM810.667 576h-170.667v-426.667h170.667v426.667zM597.333 576h-170.667v-426.667h170.667v426.667zM384 576h-170.667v-426.667h170.667v426.667z" />
|
||||
</font></defs></svg>
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 114 KiB |
BIN
vendor/assets/stylesheets/fonts/SN-icon-font.ttf
vendored
BIN
vendor/assets/stylesheets/fonts/SN-icon-font.ttf
vendored
Binary file not shown.
BIN
vendor/assets/stylesheets/fonts/SN-icon-font.woff
vendored
BIN
vendor/assets/stylesheets/fonts/SN-icon-font.woff
vendored
Binary file not shown.
BIN
vendor/assets/stylesheets/fonts/SN-icon-font.woff2
vendored
BIN
vendor/assets/stylesheets/fonts/SN-icon-font.woff2
vendored
Binary file not shown.
18
vendor/assets/stylesheets/sn-icon-font.css
vendored
18
vendor/assets/stylesheets/sn-icon-font.css
vendored
|
@ -1,11 +1,11 @@
|
|||
@font-face {
|
||||
font-family: 'SN-icon-font';
|
||||
src: url('fonts/SN-icon-font.eot?h4j5vh');
|
||||
src: url('fonts/SN-icon-font.eot?h4j5vh#iefix') format('embedded-opentype'),
|
||||
url('fonts/SN-icon-font.woff2?h4j5vh') format('woff2'),
|
||||
url('fonts/SN-icon-font.ttf?h4j5vh') format('truetype'),
|
||||
url('fonts/SN-icon-font.woff?h4j5vh') format('woff'),
|
||||
url('fonts/SN-icon-font.svg?h4j5vh#SN-icon-font') format('svg');
|
||||
src: url('fonts/SN-icon-font.eot?5l6t28');
|
||||
src: url('fonts/SN-icon-font.eot?5l6t28#iefix') format('embedded-opentype'),
|
||||
url('fonts/SN-icon-font.woff2?5l6t28') format('woff2'),
|
||||
url('fonts/SN-icon-font.ttf?5l6t28') format('truetype'),
|
||||
url('fonts/SN-icon-font.woff?5l6t28') format('woff'),
|
||||
url('fonts/SN-icon-font.svg?5l6t28#SN-icon-font') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
|
@ -470,3 +470,9 @@
|
|||
.sn-icon-pinned:before {
|
||||
content: "\e994";
|
||||
}
|
||||
.sn-icon-teams-small:before {
|
||||
content: "\e991";
|
||||
}
|
||||
.sn-icon-manage-columns:before {
|
||||
content: "\e995";
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue