mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-21 12:34:39 +08:00
Merge branch 'develop' into features/sso-improvements
This commit is contained in:
commit
6e95ba13f9
36 changed files with 180 additions and 92 deletions
|
@ -603,7 +603,7 @@ GEM
|
|||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rdoc (6.3.3)
|
||||
rdoc (6.3.4.1)
|
||||
recaptcha (5.14.0)
|
||||
regexp_parser (2.8.1)
|
||||
responders (3.1.1)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.31.0.1
|
||||
1.32.0
|
||||
|
|
BIN
app/assets/images/scinote_logo.png
Normal file
BIN
app/assets/images/scinote_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -31,7 +31,6 @@ var CommentsSidebar = (function() {
|
|||
// Replace the number in comment element
|
||||
commentsCounter.text(commentsCounter.text().replace(/[\d\\+]+/g, commentsAmount));
|
||||
commentsCounter.removeClass('hidden');
|
||||
commentsCounter.css('display', 'flex');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,20 +22,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.btn-beta-icon {
|
||||
&::after {
|
||||
background-color: var(--sn-coral);
|
||||
border-radius: 1px;
|
||||
color: var(--sn-white);
|
||||
content: "BETA";
|
||||
font-size: .5rem;
|
||||
left: 7rem;
|
||||
padding: .125rem .25rem;
|
||||
position: absolute;
|
||||
top: .1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
border: 0;
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
default_public_user_role_name_before_update = @project.default_public_user_role&.name
|
||||
@project.assign_attributes(project_update_params)
|
||||
return_error = false
|
||||
flash_error = t('projects.update.error_flash', name: escape_input(@project.name))
|
||||
|
@ -127,7 +128,7 @@ class ProjectsController < ApplicationController
|
|||
log_activity(:project_remove_access_from_all_team_members,
|
||||
@project,
|
||||
{ visibility: message_visibility,
|
||||
role: @project.default_public_user_role.name,
|
||||
role: default_public_user_role_name_before_update,
|
||||
team: @project.team.id })
|
||||
end
|
||||
|
||||
|
|
|
@ -129,13 +129,13 @@ class ProtocolsController < ApplicationController
|
|||
project_folder = record.my_module.experiment.project.project_folder
|
||||
|
||||
{
|
||||
my_module_name: record.my_module.name,
|
||||
experiment_name: record.my_module.experiment.name,
|
||||
project_name: record.my_module.experiment.project.name,
|
||||
my_module_name: record.my_module.name_with_label,
|
||||
experiment_name: record.my_module.experiment.name_with_label,
|
||||
project_name: record.my_module.experiment.project.name_with_label,
|
||||
my_module_url: protocols_my_module_path(record.my_module),
|
||||
experiment_url: my_modules_path(experiment_id: record.my_module.experiment.id),
|
||||
project_url: experiments_path(project_id: record.my_module.experiment.project.id),
|
||||
project_folder_name: project_folder.present? ? project_folder.name : nil,
|
||||
project_folder_name: project_folder.present? ? project_folder.name_with_label : nil,
|
||||
project_folder_url: project_folder.present? ? project_folder_projects_url(project_folder) : nil
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<template>
|
||||
<div class="px-3 pt-3 pb-4 rounded border-solid border border-sn-gray flex flex-col h-56"
|
||||
<div class="px-3 pt-3 pb-4 rounded border-solid border border-sn-gray flex flex-col"
|
||||
:class="{ 'bg-sn-light-grey': dtComponent.currentViewMode === 'archived', [cardMinWidth]: true}">
|
||||
<div class="flex items-center gap-4 mb-2">
|
||||
<div class="sci-checkbox-container">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="sci-checkbox"
|
||||
:checked="cardSelected"
|
||||
@change="itemSelected"
|
||||
/>
|
||||
<label :for="params.id" class="sci-checkbox-label"></label>
|
||||
|
@ -16,7 +17,7 @@
|
|||
<a :href="params.urls.show"
|
||||
:title="params.name"
|
||||
:class="{'pointer-events-none text-sn-grey': !params.urls.show}"
|
||||
class="font-bold mb-auto text-sn-blue hover:no-underline line-clamp-2 hover:text-sn-blue h-10">
|
||||
class="font-bold mb-4 shrink-0 text-sn-blue hover:no-underline line-clamp-2 hover:text-sn-blue h-10">
|
||||
{{ params.name }}
|
||||
</a>
|
||||
<div class="flex gap-4 mb-2.5">
|
||||
|
|
|
@ -199,7 +199,7 @@ export default {
|
|||
label: this.i18n.t('filters_modal.created_on.label')
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
key: 'updated_on',
|
||||
type: 'DateRange',
|
||||
label: this.i18n.t('filters_modal.updated_on.label')
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
<template>
|
||||
<div class="group relative flex items-center group-hover:marker text-xs">
|
||||
<div class="flex items-end gap-2">
|
||||
<div class="group relative flex items-center group-hover:marker text-xs h-full w-full"
|
||||
:style="{ lineHeight: 'unset' }">
|
||||
<div class="flex gap-2"
|
||||
:style="{ lineHeight: 'unset' }"
|
||||
:class="{
|
||||
'items-center text-sm': params.dtComponent.currentViewRender === 'table',
|
||||
'items-end text-xs': params.dtComponent.currentViewRender === 'cards'
|
||||
}">
|
||||
<span v-if="shouldTruncateText"
|
||||
class="cursor-pointer grow"
|
||||
:class="{
|
||||
|
@ -11,7 +17,14 @@
|
|||
v-html="params.data.sa_description">
|
||||
</span>
|
||||
<span v-else class="grow" v-html="params.data.sa_description"></span>
|
||||
<span v-if="shouldTruncateText" @click.stop="showDescriptionModal" class="text-sn-blue cursor-pointer shrink-0 text-xs">{{ i18n.t('experiments.card.more') }}</span>
|
||||
<span v-if="shouldTruncateText" @click.stop="showDescriptionModal" class="text-sn-blue cursor-pointer shrink-0 inline-block"
|
||||
:style="{ lineHeight: 'unset' }"
|
||||
:class="{
|
||||
'text-xs': params.dtComponent.currentViewRender === 'cards',
|
||||
'text-sm': params.dtComponent.currentViewRender === 'table'
|
||||
}">
|
||||
{{ i18n.t('experiments.card.more') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -126,7 +126,8 @@ export default {
|
|||
field: 'due_date',
|
||||
headerName: this.i18n.t('experiments.table.column.due_date_html'),
|
||||
sortable: true,
|
||||
cellRenderer: DueDateRenderer
|
||||
cellRenderer: DueDateRenderer,
|
||||
minWidth: 200
|
||||
},
|
||||
{
|
||||
field: 'results',
|
||||
|
@ -143,7 +144,8 @@ export default {
|
|||
field: 'status',
|
||||
headerName: this.i18n.t('experiments.table.column.status_html'),
|
||||
sortable: true,
|
||||
cellRenderer: this.statusRenderer
|
||||
cellRenderer: this.statusRenderer,
|
||||
minWidth: 120
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-if="params.data.designated_users.length > 0 || params.data.permissions.manage_designated_users">
|
||||
<GeneralDropdown @open="loadUsers" @close="closeFlyout">
|
||||
<GeneralDropdown @open="loadUsers" @close="closeFlyout" position="right">
|
||||
<template v-slot:field>
|
||||
<div v-if="!params.data.folder" class="flex items-center gap-1 cursor-pointer h-9" @click="openAccessModal">
|
||||
<div v-for="(user, i) in visibleUsers" :key="i" :title="user.full_name">
|
||||
|
@ -17,7 +17,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<div v-if="canManage" class="sci-input-container-v2 left-icon mb-1">
|
||||
<div v-if="canManage" class="sci-input-container-v2 left-icon mb-1 -mx-2.5">
|
||||
<input type="text"
|
||||
v-model="query"
|
||||
class="sci-input-field"
|
||||
|
@ -25,7 +25,7 @@
|
|||
:placeholder="i18n.t('general.search')" />
|
||||
<i class="sn-icon sn-icon-search"></i>
|
||||
</div>
|
||||
<perfect-scrollbar class="flex flex-col relative max-h-96 overflow-y-auto max-w-[280px] pr-4 pl-2 gap-y-px">
|
||||
<perfect-scrollbar class="flex flex-col relative max-h-80 overflow-y-auto max-w-[280px] pt-1 -mx-2.5 pr-4 pl-2 gap-y-px">
|
||||
<div v-for="user in allUsers"
|
||||
:key="user.value"
|
||||
@click="selectUser(user)"
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<template>
|
||||
<div v-if="!params.folder"
|
||||
:class="{ 'bg-sn-light-grey': dtComponent.currentViewMode === 'archived', [cardMinWidth]: true }"
|
||||
class="px-3 pt-3 pb-4 rounded border-solid border border-sn-gray flex flex-col h-56" >
|
||||
class="px-3 pt-3 pb-4 rounded border-solid border border-sn-gray flex flex-col" >
|
||||
<div class="flex items-center gap-4 mb-2">
|
||||
<div class="sci-checkbox-container">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="sci-checkbox"
|
||||
:checked="cardSelected"
|
||||
@change="itemSelected"
|
||||
/>
|
||||
<label :for="params.id" class="sci-checkbox-label"></label>
|
||||
|
@ -17,7 +18,7 @@
|
|||
<a :href="params.urls.show"
|
||||
:title="params.name"
|
||||
:class="{'pointer-events-none text-sn-grey': !params.urls.show}"
|
||||
class="font-bold mb-4 text-sn-blue hover:no-underline line-clamp-3 hover:text-sn-blue h-[60px]">
|
||||
class="font-bold mb-4 text-sn-blue shrink-0 hover:no-underline line-clamp-3 hover:text-sn-blue h-[60px]">
|
||||
{{ params.name }}
|
||||
</a>
|
||||
<div class="grid gap-x-2 gap-y-3 grid-cols-[90px_auto] mt-auto text-xs">
|
||||
|
@ -49,6 +50,7 @@
|
|||
<input
|
||||
type="checkbox"
|
||||
class="sci-checkbox"
|
||||
:checked="cardSelected"
|
||||
@change="itemSelected"
|
||||
/>
|
||||
<label :for="params.id" class="sci-checkbox-label"></label>
|
||||
|
|
|
@ -337,7 +337,7 @@ export default {
|
|||
project_folder_ids: rows.filter((row) => row.folder).map((row) => row.id)
|
||||
}).then((response) => {
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
HelperModule.flashAlertMsg(response.data.flash, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
|
|
|
@ -86,9 +86,9 @@
|
|||
@click="openCommentsSidebar"
|
||||
:data-object-id="step.id">
|
||||
<i class="sn-icon sn-icon-comments"></i>
|
||||
<span class="comments-counter" v-show="step.attributes.comments_count"
|
||||
<span class="comments-counter"
|
||||
:id="`comment-count-${step.id}`"
|
||||
:class="{'unseen': step.attributes.unseen_comments}"
|
||||
:class="{'unseen': step.attributes.unseen_comments, 'hidden': !step.attributes.comments_count}"
|
||||
>
|
||||
{{ step.attributes.comments_count }}
|
||||
</span>
|
||||
|
|
|
@ -211,7 +211,10 @@ export default {
|
|||
if (this.docxParserEnabled) {
|
||||
importMenu.menuItems.push({
|
||||
emit: 'import_docx',
|
||||
text: this.i18n.t('protocols.index.import_docx')
|
||||
text: `<span>${this.i18n.t('protocols.index.import_docx')}</span>
|
||||
<span class="bg-sn-coral text-sn-white text-[8px] absolute leading-none p-1 top-px rounded-[1px] right-px">
|
||||
${this.i18n.t('protocols.index.beta')}
|
||||
</span>`
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
{{ i18n.t('projects.reports.index.docx') }}
|
||||
</a>
|
||||
</template>
|
||||
<a v-else class="hidden group-hover:!block" href="#" @click.prevent="generate">
|
||||
<a v-else class="opacity-0 group-hover:opacity-100" href="#" @click.prevent="generate">
|
||||
{{ i18n.t('projects.reports.index.generate') }}
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -272,7 +272,7 @@ export default {
|
|||
templateOption(option) {
|
||||
return `
|
||||
<div class="label-template-option" data-toggle="tooltip" data-placement="right" title="${option.params.description}">
|
||||
<img src="${option.params.icon}" style=""></img>
|
||||
${option.params.icon}
|
||||
${option.label}
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
data-object-type="Result"
|
||||
:data-object-id="result.id">
|
||||
<i class="sn-icon sn-icon-comments"></i>
|
||||
<span class="comments-counter" v-show="result.attributes.comments_count"
|
||||
<span class="comments-counter" :class="{ 'hidden': !result.attributes.comments_count }"
|
||||
:id="`comment-count-${result.id}`">
|
||||
{{ result.attributes.comments_count }}
|
||||
</span>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<MenuDropdown
|
||||
v-if="params.object.top_level_assignable && params.object.urls.update_access"
|
||||
class="ml-auto"
|
||||
:listItems="rolesFromatted"
|
||||
:listItems="rolesFromatted(default_role)"
|
||||
:btnText="this.roles.find((role) => role[0] == default_role)[1]"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
|
@ -57,9 +57,9 @@
|
|||
<div class="text-xs text-sn-grey text-nowrap">{{ userAssignment.attributes.inherit_message }}</div>
|
||||
</div>
|
||||
<MenuDropdown
|
||||
v-if="!userAssignment.attributes.last_owner && params.object.urls.update_access"
|
||||
v-if="!userAssignment.attributes.last_owner && params.object.urls.update_access && !(userAssignment.attributes.current_user && userAssignment.attributes.inherit_message)"
|
||||
class="ml-auto"
|
||||
:listItems="rolesFromatted(userAssignment)"
|
||||
:listItems="rolesFromatted(userAssignment.attributes.user_role.id)"
|
||||
:btnText="userAssignment.attributes.user_role.name"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
|
@ -140,7 +140,7 @@ export default {
|
|||
this.$emit('usersReloaded');
|
||||
});
|
||||
},
|
||||
rolesFromatted(userAssignment = null) {
|
||||
rolesFromatted(activeRoleId = null) {
|
||||
let roles = [];
|
||||
|
||||
if (!this.params.object.top_level_assignable) {
|
||||
|
@ -157,7 +157,7 @@ export default {
|
|||
emit: 'setRole',
|
||||
text: role[1],
|
||||
params: role[0],
|
||||
active: (userAssignment?.attributes?.user_role?.id === role[0])
|
||||
active: (activeRoleId === role[0])
|
||||
}
|
||||
)));
|
||||
|
||||
|
|
|
@ -111,6 +111,23 @@ export default {
|
|||
this.extension = extension;
|
||||
this.imageBlob = imageBlob;
|
||||
}
|
||||
},
|
||||
uploadImage() {
|
||||
const newName = this.fileName;
|
||||
const imageBlog = this.imageBlob;
|
||||
// check if the name is set
|
||||
if (newName && newName.length > 0) {
|
||||
const extension = imageBlog.name.slice(
|
||||
(Math.max(0, imageBlog.name.lastIndexOf('.')) || Infinity) + 1
|
||||
);
|
||||
// hack to inject custom name in File object
|
||||
const name = newName + '.' + extension;
|
||||
const blob = imageBlog.slice(0, imageBlog.size, imageBlog.type);
|
||||
// make new blob with the correct name;
|
||||
this.imageBlob = new File([blob], name, { type: imageBlog.type });
|
||||
}
|
||||
$(this.$refs.modal).modal('hide');
|
||||
this.$emit('files', this.imageBlob, this.target);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,8 +8,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
localAppName: null,
|
||||
scinoteEditRunning: false,
|
||||
scinoteEditVersion: null,
|
||||
scinoteEditRunning: null,
|
||||
showNoPredefinedAppModal: false,
|
||||
showUpdateVersionModal: false,
|
||||
editAppModal: false,
|
||||
|
@ -36,34 +35,65 @@ export default {
|
|||
this.stopPolling();
|
||||
},
|
||||
methods: {
|
||||
async fetchLocalAppInfo() {
|
||||
async checkScinoteEditRunning() {
|
||||
// responses will be cached on window, so the requests only run once per page load
|
||||
if (window.scinoteEditRunning !== undefined) {
|
||||
this.scinoteEditRunning = window.scinoteEditRunning;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const statusResponse = await axios.get(
|
||||
`${this.attributes.urls.open_locally_api}/status`
|
||||
);
|
||||
|
||||
if (statusResponse.status === 200) {
|
||||
this.scinoteEditRunning = true;
|
||||
this.scinoteEditVersion = statusResponse.data.version;
|
||||
window.scinoteEditRunning = true;
|
||||
window.scinoteEditVersion = statusResponse.data.version;
|
||||
} else {
|
||||
return;
|
||||
window.scinoteEditRunning = false;
|
||||
}
|
||||
} catch (error) {
|
||||
window.scinoteEditRunning = false;
|
||||
}
|
||||
|
||||
this.scinoteEditRunning = window.scinoteEditRunning;
|
||||
},
|
||||
async fetchLocalAppInfo() {
|
||||
this.checkScinoteEditRunning();
|
||||
|
||||
// responses will be cached on window, so the requests only run once per page load
|
||||
if (window.scinoteEditRunning === false) return;
|
||||
|
||||
if (window.scinoteEditLocalApps === undefined) {
|
||||
window.scinoteEditLocalApps = {};
|
||||
}
|
||||
|
||||
if (window.scinoteEditLocalApps[this.attributes.file_extension] !== undefined) {
|
||||
this.localAppName = window.scinoteEditLocalApps[this.attributes.file_extension];
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${this.attributes.urls.open_locally_api}/default-application/${this.attributes.file_extension}`
|
||||
);
|
||||
|
||||
if (response.data.application.toLowerCase() !== 'pick an app') {
|
||||
window.scinoteEditLocalApps[this.attributes.file_extension] = response.data.application;
|
||||
this.localAppName = response.data.application;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response?.status === 404) return; // all good, no app was found for the file
|
||||
if (error.response?.status === 404) {
|
||||
window.scinoteEditLocalApps[this.attributes.file_extension] = null;
|
||||
return; // all good, no app was found for the file
|
||||
}
|
||||
|
||||
console.error('Error in request: ', error);
|
||||
}
|
||||
},
|
||||
async openLocally() {
|
||||
if (this.isWrongVersion(this.scinoteEditVersion)) {
|
||||
if (this.isWrongVersion(window.scinoteEditVersion)) {
|
||||
this.showUpdateVersionModal = true;
|
||||
return;
|
||||
} else if (this.localAppName === null) {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
<template>
|
||||
<div class="sn-open-locally-menu" @mouseenter="fetchLocalAppInfo">
|
||||
<button class="btn btn-light" v-if="!editWopiSupported" style="pointer-events: all;"
|
||||
:title="attachment.attributes.wopi_context.title" disabled="true">
|
||||
{{ attachment.attributes.wopi_context.button_text }}
|
||||
</button>
|
||||
<div v-else-if="!canOpenLocally && (attachment.attributes.wopi && attachment.attributes.urls.edit_asset)">
|
||||
<a :href="`${attachment.attributes.urls.edit_asset}`" target="_blank"
|
||||
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">
|
||||
<div v-if="!canOpenLocally && (attachment.attributes.wopi && attachment.attributes.urls.edit_asset)">
|
||||
<a :href="editWopiSupported ? attachment.attributes.urls.edit_asset : null" target="_blank"
|
||||
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"
|
||||
:class="{ 'disabled': !editWopiSupported }"
|
||||
:title="editWopiSupported ? null : attachment.attributes.wopi_context.title"
|
||||
style="pointer-events: all"
|
||||
>
|
||||
{{ attachment.attributes.wopi_context.button_text }}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -75,7 +74,9 @@ export default {
|
|||
if (this.attachment.attributes.wopi && this.attachment.attributes.urls.edit_asset) {
|
||||
menu.push({
|
||||
text: this.attachment.attributes.wopi_context.button_text,
|
||||
url: this.attachment.attributes.urls.edit_asset,
|
||||
url: this.editWopiSupported ? this.attachment.attributes.urls.edit_asset : null,
|
||||
disabled: !this.editWopiSupported && 'style-only',
|
||||
title: this.editWopiSupported ? null : this.attachment.attributes.wopi_context.title,
|
||||
url_target: '_blank'
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
export default {
|
||||
computed: {
|
||||
cardSelected() {
|
||||
const item = this.dtComponent.selectedRows.find((i) => (i.code === this.params.code));
|
||||
|
||||
return !!item;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
itemSelected() {
|
||||
const item = this.dtComponent.selectedRows.find((i) => (i.id === this.params.id));
|
||||
const item = this.dtComponent.selectedRows.find((i) => (i.code === this.params.code));
|
||||
|
||||
if (item) {
|
||||
this.dtComponent.selectedRows = this.dtComponent.selectedRows
|
||||
.filter((i) => (i.id !== this.params.id));
|
||||
.filter((i) => (i.code !== this.params.code));
|
||||
} else {
|
||||
this.dtComponent.selectedRows.push(this.params);
|
||||
}
|
||||
|
|
|
@ -302,6 +302,7 @@ export default {
|
|||
this.updateTable();
|
||||
this.$nextTick(() => {
|
||||
this.selectedRows = [];
|
||||
this.gridApi?.deselectAll();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -352,6 +353,8 @@ export default {
|
|||
const gap = (maxGridCols - 1) * GLOBAL_CONSTANTS.TABLE_CARD_GAP;
|
||||
maxGridCols = Math.floor((availableGridWidth - gap - padding) / GLOBAL_CONSTANTS.TABLE_CARD_MIN_WIDTH);
|
||||
}
|
||||
// grid-cols-2 grid-cols-3 grid-cols-4 grid-cols-5 grid-cols-6 grid-cols-7 grid-cols-8 grid-cols-9 grid-cols-10
|
||||
if (maxGridCols > 10) maxGridCols = 10;
|
||||
|
||||
if (this.navigatorCollapsed) {
|
||||
this.gridColsClass = 'grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4';
|
||||
|
@ -477,7 +480,10 @@ export default {
|
|||
if (this.dataLoading) return;
|
||||
|
||||
this.dataLoading = true;
|
||||
if (clearSelection) this.selectedRows = [];
|
||||
if (clearSelection) {
|
||||
this.selectedRows = [];
|
||||
this.gridApi?.deselectAll();
|
||||
}
|
||||
this.page = 1;
|
||||
this.loadData(true);
|
||||
},
|
||||
|
@ -605,6 +611,7 @@ export default {
|
|||
if (this.currentViewRender === view) return;
|
||||
this.currentViewRender = view;
|
||||
this.columnApi = null;
|
||||
this.gridApi?.deselectAll();
|
||||
this.gridApi = null;
|
||||
this.saveTableState();
|
||||
this.initializing = true;
|
||||
|
|
|
@ -18,15 +18,17 @@
|
|||
<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"
|
||||
v-html="item.text"
|
||||
:target="item.url_target || '_self'"
|
||||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
:class="{ 'bg-sn-super-light-blue': item.active, 'disabled': item.disabled }"
|
||||
:style="item.disabled === 'style-only' && 'pointer-events: all'"
|
||||
:title="item.title"
|
||||
:data-toggle="item.modalTarget && 'modal'"
|
||||
:data-target="item.modalTarget"
|
||||
:data-e2e="item.data_e2e"
|
||||
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"
|
||||
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 relative"
|
||||
@click="handleClick($event, item)"
|
||||
>
|
||||
{{ item.text }}
|
||||
</a>
|
||||
<div v-else class="-mx-2.5 px-2.5 group relative">
|
||||
<span
|
||||
|
|
|
@ -45,7 +45,6 @@ export default {
|
|||
if (!field || !flyout) return;
|
||||
|
||||
const rect = field.getBoundingClientRect();
|
||||
const flyoutRect = flyout.getBoundingClientRect();
|
||||
const screenHeight = window.innerHeight;
|
||||
|
||||
const windowHasScroll = document.documentElement.scrollHeight > document.documentElement.clientHeight;
|
||||
|
@ -64,10 +63,7 @@ export default {
|
|||
flyout.style.minWidth = `${width}px`;
|
||||
}
|
||||
|
||||
if (window.innerWidth - (rect.x + flyoutRect.width) < 0) { // when flyout is out of screen
|
||||
flyout.style.left = 'unset';
|
||||
flyout.style.right = `${width - Math.abs(right)}px`;
|
||||
} else if (this.position === 'right') {
|
||||
if (this.position === 'right') {
|
||||
flyout.style.right = `${right - rightScrollOffset}px`;
|
||||
flyout.style.left = 'unset';
|
||||
} else {
|
||||
|
|
|
@ -322,6 +322,10 @@ class Project < ApplicationRecord
|
|||
report.destroy if report.present?
|
||||
end
|
||||
|
||||
def archived_branch?
|
||||
archived?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project_folder_team
|
||||
|
|
|
@ -5,10 +5,4 @@ class RepositoryChecklistItemsValue < ApplicationRecord
|
|||
belongs_to :repository_checklist_value, inverse_of: :repository_checklist_items_values
|
||||
|
||||
validates :repository_checklist_item, :repository_checklist_value, presence: true
|
||||
|
||||
after_destroy :destroy_empty_value
|
||||
|
||||
def destroy_empty_value
|
||||
repository_checklist_value.destroy! if repository_checklist_value.repository_checklist_items_values.blank?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,8 @@ module Lists
|
|||
|
||||
attributes :name, :language_type, :urls, :type,
|
||||
:default, :format, :modified_by, :created_by,
|
||||
:created_at, :updated_at, :id, :icon_url, :description
|
||||
:created_at, :updated_at, :id, :icon_url, :description,
|
||||
:width_mm, :height_mm, :unit, :density
|
||||
|
||||
def icon_url
|
||||
ActionController::Base.helpers.image_tag(
|
||||
|
@ -35,7 +36,7 @@ module Lists
|
|||
end
|
||||
|
||||
def urls
|
||||
return {} unless can_manage_label_templates?(object.team)
|
||||
return {} unless can_view_label_templates?(object.team)
|
||||
|
||||
{
|
||||
show: label_template_path(object)
|
||||
|
|
|
@ -75,11 +75,31 @@ module Lists
|
|||
created_at: 'experiments.created_at',
|
||||
name: 'experiments.name',
|
||||
code: 'experiments.id',
|
||||
archived_on: 'COALESCE(experiments.archived_on, projects.archived_on)',
|
||||
archived_on: 'archived_on',
|
||||
updated_at: 'experiments.updated_at',
|
||||
completed_tasks: 'completed_task_count',
|
||||
description: 'experiments.description'
|
||||
}
|
||||
end
|
||||
|
||||
def sort_records
|
||||
return unless @params[:order] && sortable_columns[order_params[:column].to_sym].present?
|
||||
|
||||
@records = case order_params[:column]
|
||||
when 'archived_on'
|
||||
if order_params[:dir] == 'asc'
|
||||
@records.order(Arel.sql('COALESCE(experiments.archived_on, projects.archived_on) ASC'))
|
||||
.group('experiments.archived_on', 'projects.archived_on')
|
||||
else
|
||||
@records.order(Arel.sql('COALESCE(experiments.archived_on, projects.archived_on) DESC'))
|
||||
.group('experiments.archived_on', 'projects.archived_on')
|
||||
end
|
||||
else
|
||||
sort_by = "#{sortable_columns[order_params[:column].to_sym]} #{sort_direction(order_params)}"
|
||||
@records.order(sort_by)
|
||||
end
|
||||
|
||||
@records = @records.order(:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,9 +30,9 @@ module Reports::Docx::PrivateMethods
|
|||
end
|
||||
|
||||
def insert_logo
|
||||
logo_data = File.read(Rails.root.join('app/assets/images/scinote_logo.svg'))
|
||||
logo_data = File.read(Rails.root.join('app/assets/images/scinote_logo.png'))
|
||||
|
||||
@docx.img 'logo.svg' do
|
||||
@docx.img 'logo.png' do
|
||||
data logo_data
|
||||
height 20
|
||||
width 100
|
||||
|
|
|
@ -128,7 +128,8 @@ module Reports
|
|||
image = TinyMceAsset.find_by(id: Base62.decode(elem.attributes['data-mce-token'].value))
|
||||
return unless image
|
||||
|
||||
image_path = Reports::Utils.image_prepare(image).url
|
||||
image_path = Reports::Utils.image_prepare(image).processed.url
|
||||
|
||||
dimension = FastImage.size(image_path)
|
||||
|
||||
return unless dimension
|
||||
|
|
|
@ -11,12 +11,12 @@ module Reports
|
|||
def self.image_prepare(asset)
|
||||
if asset.class == Asset
|
||||
if asset.inline?
|
||||
asset.large_preview
|
||||
asset.preview_attachment.representation(resize_to_limit: Constants::MEDIUM_PIC_FORMAT, format: :png)
|
||||
else
|
||||
asset.medium_preview
|
||||
asset.preview_attachment.representation(resize_to_limit: Constants::LARGE_PIC_FORMAT, format: :png)
|
||||
end
|
||||
elsif asset.class == TinyMceAsset
|
||||
asset.image
|
||||
asset.image.representation(format: :png)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -239,7 +239,7 @@ class Constants
|
|||
}
|
||||
|
||||
SCINOTE_FLUICS_URL = 'https://www.scinote.net/fluics/'.freeze
|
||||
SCINOTE_ZEBRA_DOWNLOAD_URL = 'https://www.zebra.com/us/en/products/software/barcode-printers/link-os/browser-print.html'.freeze
|
||||
SCINOTE_ZEBRA_DOWNLOAD_URL = 'https://www.zebra.com/us/en/software/printer-software/browser-print.html'.freeze
|
||||
SCINOTE_ZEBRA_BLOG_URL = 'https://www.scinote.net/blog/connect-zebra-printers/'.freeze
|
||||
SCINOTE_ZEBRA_SUPPORT_URL = 'https://www.zebra.com/us/en/about-zebra/contact-zebra/contact-tech-support.html'.freeze
|
||||
TWO_FACTOR_RECOVERY_CODE_COUNT = 6
|
||||
|
|
|
@ -1561,7 +1561,7 @@ en:
|
|||
column:
|
||||
id_html: 'ID'
|
||||
task_name_html: 'Task name'
|
||||
due_date_html: 'Due'
|
||||
due_date_html: 'Due date'
|
||||
archived_html: 'Archived on'
|
||||
age_html: 'Age'
|
||||
results_html: 'Results'
|
||||
|
@ -3240,6 +3240,7 @@ en:
|
|||
import: "Import"
|
||||
import_eln: "SciNote file (.eln)"
|
||||
import_docx: "MS Word (.docx)"
|
||||
beta: "BETA"
|
||||
import_protocols_io: "From Protocols.io"
|
||||
modal_import_json_upload: "Upload"
|
||||
modal_import_json_title: "Import protocols.io file"
|
||||
|
|
Loading…
Add table
Reference in a new issue