Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ove and issues with uploadsDiv and the observer #4867

Merged
merged 7 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
steps:
- checkout
- setup_remote_docker:
version: 20.10.23
version: docker23
# docker_layer_caching: true # DLC will explicitly cache layers here and try to avoid rebuilding.
# docker_layer_caching is behind a paywall
- run:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"dependencies": {
"3dmol": "^2.0.6",
"@deltablot/chemdoodle-web-mini": "^9.5.0",
"@deltablot/chemdoodle-web-mini": "^9.5.1",
"@deltablot/dropzone": "7.0.0-alpha2",
"@deltablot/malle": "^2.5.2",
"@fancyapps/fancybox": "^3.5.7",
Expand Down
42 changes: 24 additions & 18 deletions src/templates/uploads.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ <h3 title='{{ 'Toggle visibility'|trans }}' data-action='toggle-next' data-toggl
</div>
</div>

<div class='row mt-2' id='uploadsDiv' data-save-hidden='uploadsDiv'>
<div class='row mt-2 mx-0' id='uploadsDiv' data-save-hidden='uploadsDiv'>
{# ADD NEW FILE #}
{% if mode == 'edit' %}
<div class='col-md-4 col-sm-6'>
Expand Down Expand Up @@ -90,7 +90,10 @@ <h3 title='{{ 'Toggle visibility'|trans }}' data-action='toggle-next' data-toggl
<td>{{ upload.filesize|formatBytes }}</td>
{# ACTIONS #}
<td>
<a href='app/download.php?f={{ upload.long_name }}&amp;name={{ upload.real_name }}&amp;storage={{ upload.storage }}' rel='noopener' title='{{ 'Download'|trans }}' aria-label='{{ 'Download'|trans }}' class='btn hl-hover-gray p-1 mr-1' {{ loop.first ? 'id="last-uploaded-link"' }}>
{% if loop.first %}
<div id='last-uploaded-link' data-url='app/download.php?f={{ upload.long_name }}' hidden></div>
{% endif %}
<a href='app/download.php?f={{ upload.long_name }}&amp;name={{ upload.real_name }}&amp;storage={{ upload.storage }}' rel='noopener' title='{{ 'Download'|trans }}' aria-label='{{ 'Download'|trans }}' class='btn hl-hover-gray p-1 mr-1'>
<i class='fas fa-download fa-fw'></i>
</a>
{% if not upload.immutable %}
Expand All @@ -105,28 +108,31 @@ <h3 title='{{ 'Toggle visibility'|trans }}' data-action='toggle-next' data-toggl
</button>
{% include('upload-replace-form.html') %}
{% endif %}
{% if not Entity.isReadOnly %}
{# ARCHIVE #}
<button type='button' title='{{ 'Archive/Unarchive'|trans }}' aria-label='{{ 'Archive/Unarchive'|trans }}' class='btn hover-warning p-1 mr-1' data-action='archive-upload' data-uploadid='{{ upload.id }}'>
<i class='fas fa-box-archive'></i>
</button>
{# DESTROY #}
<button type='button' title='{{ 'Delete'|trans }}' aria-label='{{ 'Delete'|trans }}'class='btn hover-danger p-1 mr-1' data-action='destroy-upload' data-uploadid='{{ upload.id }}'>
<i class='fas fa-trash-alt'></i>
</button>
{% if not Entity.isReadOnly %}
{# ARCHIVE #}
<button type='button' title='{{ 'Archive/Unarchive'|trans }}' aria-label='{{ 'Archive/Unarchive'|trans }}' class='btn hover-warning p-1 mr-1' data-action='archive-upload' data-uploadid='{{ upload.id }}'>
<i class='fas fa-box-archive'></i>
</button>
{# DESTROY #}
<button type='button' title='{{ 'Delete'|trans }}' aria-label='{{ 'Delete'|trans }}'class='btn hover-danger p-1 mr-1' data-action='destroy-upload' data-uploadid='{{ upload.id }}'>
<i class='fas fa-trash-alt'></i>
</button>
{% endif %}
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>

{# DEFAULT LAYOUT (1) #}
{% else %}
{% for upload in Entity.entityData.uploads %}
{% set ext = upload.real_name|getExt %}
{# DEFAULT LAYOUT (1) #}
{% else %}
{% for upload in Entity.entityData.uploads %}
{% set ext = upload.real_name|getExt %}
<div class='col-md-4 col-sm-6' id='uploadDiv_{{ upload.id }}'>
{% if loop.first %}
<div id='last-uploaded-link' data-url='app/download.php?f={{ upload.long_name }}' hidden></div>
{% endif %}
<div class='thumbnail box {{ upload.state == constant('Elabftw\\Enums\\State::Archived').value ? 'bg-light' }}' data-type='{{ Entity.type }}' data-id='{{ Entity.id }}' style='overflow: visible'>
{% include('upload-dropdown-menu.html') %}
{% if upload.state == constant('Elabftw\\Enums\\State::Archived').value %}
Expand All @@ -139,7 +145,7 @@ <h3 title='{{ 'Toggle visibility'|trans }}' data-action='toggle-next' data-toggl
{% if ext matches '/(jpg|jpeg|png|gif|pdf|eps|svg|heic)$/i' %}
<div class='text-center'>
<a class='text-break' href='app/download.php?f={{ upload.long_name }}&amp;storage={{ upload.storage }}&amp;name={{ upload.real_name }}'
{% if upload.real_name matches '/(jpg|jpeg|png|gif)$/i' %}data-fancybox='group' data-caption='{{ upload.real_name }}'{% endif %}
{% if upload.real_name matches '/(jpg|jpeg|png|gif)$/i' %}data-fancybox='group' data-caption='{{ upload.real_name }}'{% endif %}
{% if upload.comment %}title='{{ upload.comment }}' data-caption='{{ upload.comment }}'{% endif %}
>
{% endif %}
Expand Down Expand Up @@ -223,7 +229,7 @@ <h3 title='{{ 'Toggle visibility'|trans }}' data-action='toggle-next' data-toggl
{% elseif ext matches '/(mp4|webm)$/i' %}
<div class='text-center'>
<video controls width='300' preload='metadata'>
<source src='app/download.php?f={{ upload.long_name }}&storage={{ upload.storage }}&name={{ upload.real_name }}' type='video/{{ ext }}'>
<source src='app/download.php?f={{ upload.long_name }}&amp;storage={{ upload.storage }}&amp;name={{ upload.real_name }}' type='video/{{ ext }}'>
</video>
</div>

Expand Down
4 changes: 2 additions & 2 deletions src/ts/JsonEditorHelper.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Metadata } from './Metadata.class';
import JSONEditor from 'jsoneditor';
import $ from 'jquery';
import i18next from 'i18next';
import { notif, notifSaved, reloadUploads } from './misc';
import { notif, notifSaved, reloadElement } from './misc';
import { Action, Entity, Model } from './interfaces';
import { Api } from './Apiv2.class';
import { ValidMetadata } from './metadataInterfaces';
Expand Down Expand Up @@ -173,7 +173,7 @@ export default class JsonEditorHelper {
};
this.api.post(`${this.entity.type}/${this.entity.id}/${Model.Upload}`, params).then(resp => {
const location = resp.headers.get('location').split('/');
reloadUploads();
reloadElement('uploadsDiv');
this.currentUploadId = String(location[location.length - 1]);
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/ts/doodle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @license AGPL-3.0
* @package elabftw
*/
import { reloadUploads } from './misc';
import { reloadElement } from './misc';
import i18next from 'i18next';
import { Action, Model } from './interfaces';
import { Api } from './Apiv2.class';
Expand Down Expand Up @@ -87,7 +87,7 @@ document.addEventListener('DOMContentLoaded', () => {
'real_name': realName,
'content': image,
};
ApiC.post(`${elDataset.type}/${elDataset.id}/${Model.Upload}`, params).then(() => reloadUploads());
ApiC.post(`${elDataset.type}/${elDataset.id}/${Model.Upload}`, params).then(() => reloadElement('uploadsDiv'));
});


Expand Down
8 changes: 4 additions & 4 deletions src/ts/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @license AGPL-3.0
* @package elabftw
*/
import { getEntity, notif, updateCatStat, escapeRegExp, notifError, reloadUploads } from './misc';
import { getEntity, notif, updateCatStat, escapeRegExp, notifError, reloadElement } from './misc';
import { getTinymceBaseConfig, quickSave } from './tinymce';
import { EntityType, Target, Upload, Model, Action } from './interfaces';
import { DateTime } from 'luxon';
Expand Down Expand Up @@ -217,7 +217,7 @@ document.addEventListener('DOMContentLoaded', () => {
'real_name': realName,
'content': content,
};
ApiC.post(`${entity.type}/${entity.id}/${Model.Upload}`, params).then(() => reloadUploads());
ApiC.post(`${entity.type}/${entity.id}/${Model.Upload}`, params).then(() => reloadElement('uploadsDiv'));

// ANNOTATE IMAGE
} else if (el.matches('[data-action="annotate-image"]')) {
Expand Down Expand Up @@ -361,7 +361,7 @@ document.addEventListener('DOMContentLoaded', () => {
resolve(`app/download.php?f=${json.long_name}&storage=${json.storage}`);
// save here because using the old real_name will not return anything from the db (status is archived now)
updateEntityBody();
reloadUploads();
reloadElement('uploadsDiv');
});
});
} else {
Expand Down Expand Up @@ -457,7 +457,7 @@ document.addEventListener('DOMContentLoaded', () => {
method: 'POST',
body: formData,
}).then(resp => {
reloadUploads();
reloadElement('uploadsDiv');
// return early if longName is not found in body
if ((editorCurrentContent.indexOf(searchPrefixSrc + formElement.dataset.longName) === -1)
&& (editorCurrentContent.indexOf(searchPrefixMd + formElement.dataset.longName) === -1)
Expand Down
7 changes: 0 additions & 7 deletions src/ts/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* @package elabftw
*/
import 'jquery-ui/ui/widgets/sortable';
import * as $3Dmol from '3dmol';
import { Action, CheckableItem, ResponseMsg, EntityType, Entity, Model, Target } from './interfaces';
import { DateTime } from 'luxon';
import { MathJaxObject } from 'mathjax-full/js/components/startup';
Expand Down Expand Up @@ -217,12 +216,6 @@ export function displayMolFiles(): void {
});
}

// this exists here because in the Observer of uploads.ts for filesdiv it makes the browser crash
// FIXME
export async function reloadUploads(): Promise<void> {
return reloadElement('filesdiv').then(() => $3Dmol.autoload());
}

// insert a get param in the url and reload the page
export function insertParamAndReload(key: string, value: string): void {
const params = new URLSearchParams(document.location.search.slice(1));
Expand Down
11 changes: 3 additions & 8 deletions src/ts/ove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ declare global {
import '@teselagen/ove';
import '@teselagen/ove/style.css';
import { anyToJson } from '@teselagen/bio-parsers';
import { notif, reloadUploads } from './misc';
import { notif, reloadElement } from './misc';
import { Action, Model } from './interfaces';
import { Api } from './Apiv2.class';

Expand Down Expand Up @@ -49,7 +49,7 @@ export function displayPlasmidViewer(about: DOMStringMap): void {
'real_name': realName + '.png',
'content': reader.result,
};
ApiC.post(`${about.type}/${about.id}/${Model.Upload}`, params).then(() => reloadUploads());
ApiC.post(`${about.type}/${about.id}/${Model.Upload}`, params).then(() => reloadElement('uploadsDiv'));
};
}

Expand All @@ -72,10 +72,6 @@ export function displayPlasmidViewer(about: DOMStringMap): void {
throw msg;
}

if (parsedData[0].messages.length !== 0) {
throw 'File: ' + realName + '\n' + parsedData[0].messages.join('\n');
}

const parsedSequence = parsedData[0].parsedSequence;

/* eslint-disable-next-line */
Expand Down Expand Up @@ -136,7 +132,7 @@ export function displayPlasmidViewer(about: DOMStringMap): void {
generatePng: true,
handleFullscreenClose: function(): void { // event could be used as parameter
editor[viewerID].close();
reloadUploads();
reloadElement('uploadsDiv');
},
onCopy: function(event, copiedSequenceData, editorState): void {
// the copiedSequenceData is the subset of the sequence that has been copied in the teselagen sequence format
Expand Down Expand Up @@ -207,7 +203,6 @@ export function displayPlasmidViewer(about: DOMStringMap): void {
readOnly: true,
// Open Vector Editor data model
sequenceData: parsedSequence,
updateSequenceData: {},
// clear the sequenceDataHistory if there is any left over from a previous sequence
sequenceDataHistory: {},
annotationVisibility: {
Expand Down
4 changes: 2 additions & 2 deletions src/ts/toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @license AGPL-3.0
* @package elabftw
*/
import { getEntity, reloadElement, reloadUploads } from './misc';
import { getEntity, reloadElement } from './misc';
import { Api } from './Apiv2.class';
import EntityClass from './Entity.class';
import i18next from 'i18next';
Expand Down Expand Up @@ -97,7 +97,7 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('container').append(overlay);
ApiC.patch(`${entity.type}/${entity.id}`, {'action': Action.Bloxberg})
// reload uploaded files on success
.then(() => reloadUploads())
.then(() => reloadElement('uploadsDiv'))
// remove overlay in all cases
.finally(() => document.getElementById('container').removeChild(document.getElementById('loadingOverlay')));
// ARCHIVE ENTITY
Expand Down
34 changes: 10 additions & 24 deletions src/ts/uploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,14 @@
* @package elabftw
*/
import Dropzone from '@deltablot/dropzone';
import { reloadUploads } from './misc';
import { reloadElement } from './misc';
import i18next from 'i18next';

export class Uploader
{
targetElement = '#elabftw-dropzone';
// holds the resolve function of tinymce image handler
tinyImageSuccess: (value: string | PromiseLike<string>) => void;

getElement(): string {
return this.targetElement;
}

getOptions() {
const maxsize = parseInt(document.getElementById('info').dataset.maxsize, 10); // MB
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
Expand All @@ -32,27 +27,14 @@ export class Uploader
// once upload is finished
this.on('complete', function() {
if (this.getUploadingFiles().length === 0 && this.getQueuedFiles().length === 0) {
// reload the #filesdiv
reloadUploads().then(() => {
reloadElement('uploadsDiv').then(() => {
// Now grab the url of the image to give it to tinymce if needed
// first make sure the success function is set by tinymce and we are dealing with an image drop and not a regular upload
if (typeof that.tinyImageSuccess !== 'undefined' && that.tinyImageSuccess !== null) {
// Uses the newly updated HTML element for the uploads section to find the last file uploaded and use that to get the remote url for the image.
const uploadElements = Array.from(document.getElementById('uploadsDiv').children);
let href: string;
// if the display is TABLE, we need to get the information differently
if (uploadElements[1].tagName === 'TABLE') {
// this particular ID is added by twig for the first row
href = (document.getElementById('last-uploaded-link') as HTMLLinkElement).getAttribute('href');
} else {
// index 0 is the drop zone
href = uploadElements[1].querySelector('[id^=upload-filename]').getAttribute('href');
}
// Slices out the url by finding the &name query param from the download link. This does not care about extensions or thumbnails.
const url = href.slice(0, href.indexOf('&name='));
// This gives TinyMCE the actual url of the uploaded image. TinyMce updates its editor to link to this rather than the temp location it sets up initially.
// fun fact: if the upload failed for some reason, the blob in the text will get replaced by the previous image. So if you're looking at this code wondering why from time to time dropping image B in the text makes image A appear, that's because image B failed to upload and the code looks for the last upload!
that.tinyImageSuccess(url);
that.tinyImageSuccess(document.getElementById('last-uploaded-link').dataset.url);
// This is to make sure that we do not end up adding a file to TinyMCE if a previous file was pasted and a consecutive file was uploaded using Dropzone.
// The 'undefined' check is not enough. That is just for before any file was pasted.
that.tinyImageSuccess = null;
Expand All @@ -66,9 +48,13 @@ export class Uploader
}

init(): Dropzone {
// the dz-clickable class is present if Dropzone is active on this element
if (document.getElementById('elabftw-dropzone') && document.getElementById('elabftw-dropzone').classList.contains('dz-clickable') === false) {
return new Dropzone(this.getElement(), this.getOptions());
const dropzoneEl = document.getElementById('elabftw-dropzone');
if (dropzoneEl) {
// Dropzone can be initialized in edit.ts and uploads.ts but we should only init it once
if (Object.prototype.hasOwnProperty.call(dropzoneEl, 'dropzone')) {
return dropzoneEl.dropzone;
}
return new Dropzone(dropzoneEl, this.getOptions());
}
}
}
Loading
Loading