Skip to content
Snippets Groups Projects
Unverified Commit 430cc832 authored by David Sehnal's avatar David Sehnal Committed by GitHub
Browse files

Merge pull request #311 from molstar/upload-overlay

Drag and Drop Overlay
parents 93215b6b 3cd3afb7
No related branches found
No related tags found
No related merge requests found
......@@ -12,6 +12,8 @@ Note that since we don't clearly distinguish between a public and private interf
- Fix false positives in Model.isFromPdbArchive
- Add drag and drop support for loading any file, including multiple at once
- If there are session files (.molx or .molj) among the dropped files, only the first session will be loaded
- Add drag and drop overlay
- Safari 15.1 - 15.3 WebGL 2 support workaround
## [v3.0.0-dev.3] - 2021-12-4
......
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-21 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
interface Behavior<T> {
value: T;
......@@ -16,26 +16,20 @@ export function useBehavior<T>(s: Behavior<T>): T;
export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined;
// eslint-disable-next-line
export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined {
const [value, setValue] = useState(s?.value);
const [, next] = useState({});
const current = useRef<T>();
current.current = s?.value;
useEffect(() => {
if (!s) {
if (value !== void 0) setValue(void 0);
return;
}
let fst = true;
const sub = s.subscribe((v) => {
if (fst) {
fst = false;
if (v !== value) setValue(v);
} else setValue(v);
if (current.current !== v) next({});
});
return () => {
sub.unsubscribe();
};
// eslint-disable-next-line
return () => sub.unsubscribe();
}, [s]);
return value;
return s?.value;
}
\ No newline at end of file
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
......@@ -20,6 +20,8 @@ import { PluginCommands } from '../mol-plugin/commands';
import { PluginUIContext } from './context';
import { OpenFiles } from '../mol-plugin-state/actions/file';
import { Asset } from '../mol-util/assets';
import { BehaviorSubject } from 'rxjs';
import { useBehavior } from './hooks/use-behavior';
export class Plugin extends React.Component<{ plugin: PluginUIContext }, {}> {
region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) {
......@@ -139,13 +141,16 @@ class Layout extends PluginUIComponent {
ev.preventDefault();
}
private showDragOverlay = new BehaviorSubject(false);
onDragEnter = () => this.showDragOverlay.next(true);
render() {
const layout = this.plugin.layout.state;
const controls = this.plugin.spec.components?.controls || {};
const viewport = this.plugin.spec.components?.viewport?.view || DefaultViewport;
return <div className='msp-plugin' onDrop={this.onDrop} onDragOver={this.onDragOver}>
<div className={this.layoutClassName}>
return <div className='msp-plugin'>
<div className={this.layoutClassName} onDragEnter={this.onDragEnter}>
<div className={this.layoutVisibilityClassName}>
{this.region('main', viewport)}
{layout.showControls && controls.top !== 'none' && this.region('top', controls.top || SequenceView)}
......@@ -154,11 +159,69 @@ class Layout extends PluginUIComponent {
{layout.showControls && controls.bottom !== 'none' && this.region('bottom', controls.bottom || Log)}
</div>
{!this.plugin.spec.components?.hideTaskOverlay && <OverlayTaskProgress />}
<DragOverlay plugin={this.plugin} showDragOverlay={this.showDragOverlay} />
</div>
</div>;
}
}
function dropFiles(ev: React.DragEvent<HTMLDivElement>, plugin: PluginUIContext, showDragOverlay: BehaviorSubject<boolean>) {
ev.preventDefault();
ev.stopPropagation();
showDragOverlay.next(false);
const files: File[] = [];
if (ev.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
for (let i = 0; i < ev.dataTransfer.items.length; i++) {
if (ev.dataTransfer.items[i].kind !== 'file') continue;
const file = ev.dataTransfer.items[i].getAsFile();
if (file) files.push(file);
}
} else {
for (let i = 0; i < ev.dataTransfer.files.length; i++) {
const file = ev.dataTransfer.files[0];
if (file) files.push(file);
}
}
const sessions = files.filter(f => {
const fn = f.name.toLowerCase();
return fn.endsWith('.molx') || fn.endsWith('.molj');
});
if (sessions.length > 0) {
PluginCommands.State.Snapshots.OpenFile(plugin, { file: sessions[0] });
} else {
plugin.runTask(plugin.state.data.applyAction(OpenFiles, {
files: files.map(f => Asset.File(f)),
format: { name: 'auto', params: {} },
visuals: true
}));
}
}
function DragOverlay({ plugin, showDragOverlay }: { plugin: PluginUIContext, showDragOverlay: BehaviorSubject<boolean> }) {
const show = useBehavior(showDragOverlay);
const preventDrag = (e: React.DragEvent) => {
e.dataTransfer.dropEffect = 'copy';
e.preventDefault();
e.stopPropagation();
};
return <div
className='msp-drag-drop-overlay'
style={{ display: show ? 'flex' : 'none' }}
onDragEnter={preventDrag}
onDragOver={preventDrag}
onDragLeave={() => showDragOverlay.next(false)}
onDrop={e => dropFiles(e, plugin, showDragOverlay)}
>
Upload File(s)
</div>;
}
export class ControlsWrapper extends PluginUIComponent {
render() {
const StructureTools = this.plugin.spec.components?.structureTools || DefaultStructureTools;
......
......@@ -626,4 +626,19 @@
.msp-list-unstyled {
padding-left: 0;
list-style: none;
}
.msp-drag-drop-overlay {
border: 12px dashed $font-color;
background: rgba(0, 0, 0, 0.36);
display: flex;
align-items: center;
justify-content: center;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
font-size: 48px;
font-weight: bold;
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment