Skip to content
Snippets Groups Projects
Commit 3cd3afb7 authored by dsehnal's avatar dsehnal
Browse files

drag and drop overlay

parent 93215b6b
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 ...@@ -12,6 +12,8 @@ Note that since we don't clearly distinguish between a public and private interf
- Fix false positives in Model.isFromPdbArchive - Fix false positives in Model.isFromPdbArchive
- Add drag and drop support for loading any file, including multiple at once - 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 - 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 ## [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> * @author David Sehnal <david.sehnal@gmail.com>
*/ */
import { useEffect, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
interface Behavior<T> { interface Behavior<T> {
value: T; value: T;
...@@ -16,26 +16,20 @@ export function useBehavior<T>(s: Behavior<T>): T; ...@@ -16,26 +16,20 @@ export function useBehavior<T>(s: Behavior<T>): T;
export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined; export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined;
// eslint-disable-next-line // eslint-disable-next-line
export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined { 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(() => { useEffect(() => {
if (!s) { if (!s) {
if (value !== void 0) setValue(void 0);
return; return;
} }
let fst = true;
const sub = s.subscribe((v) => { const sub = s.subscribe((v) => {
if (fst) { if (current.current !== v) next({});
fst = false;
if (v !== value) setValue(v);
} else setValue(v);
}); });
return () => { return () => sub.unsubscribe();
sub.unsubscribe();
};
// eslint-disable-next-line
}, [s]); }, [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 David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Alexander Rose <alexander.rose@weirdbyte.de>
...@@ -20,6 +20,8 @@ import { PluginCommands } from '../mol-plugin/commands'; ...@@ -20,6 +20,8 @@ import { PluginCommands } from '../mol-plugin/commands';
import { PluginUIContext } from './context'; import { PluginUIContext } from './context';
import { OpenFiles } from '../mol-plugin-state/actions/file'; import { OpenFiles } from '../mol-plugin-state/actions/file';
import { Asset } from '../mol-util/assets'; import { Asset } from '../mol-util/assets';
import { BehaviorSubject } from 'rxjs';
import { useBehavior } from './hooks/use-behavior';
export class Plugin extends React.Component<{ plugin: PluginUIContext }, {}> { export class Plugin extends React.Component<{ plugin: PluginUIContext }, {}> {
region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) { region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) {
...@@ -139,13 +141,16 @@ class Layout extends PluginUIComponent { ...@@ -139,13 +141,16 @@ class Layout extends PluginUIComponent {
ev.preventDefault(); ev.preventDefault();
} }
private showDragOverlay = new BehaviorSubject(false);
onDragEnter = () => this.showDragOverlay.next(true);
render() { render() {
const layout = this.plugin.layout.state; const layout = this.plugin.layout.state;
const controls = this.plugin.spec.components?.controls || {}; const controls = this.plugin.spec.components?.controls || {};
const viewport = this.plugin.spec.components?.viewport?.view || DefaultViewport; const viewport = this.plugin.spec.components?.viewport?.view || DefaultViewport;
return <div className='msp-plugin' onDrop={this.onDrop} onDragOver={this.onDragOver}> return <div className='msp-plugin'>
<div className={this.layoutClassName}> <div className={this.layoutClassName} onDragEnter={this.onDragEnter}>
<div className={this.layoutVisibilityClassName}> <div className={this.layoutVisibilityClassName}>
{this.region('main', viewport)} {this.region('main', viewport)}
{layout.showControls && controls.top !== 'none' && this.region('top', controls.top || SequenceView)} {layout.showControls && controls.top !== 'none' && this.region('top', controls.top || SequenceView)}
...@@ -154,11 +159,69 @@ class Layout extends PluginUIComponent { ...@@ -154,11 +159,69 @@ class Layout extends PluginUIComponent {
{layout.showControls && controls.bottom !== 'none' && this.region('bottom', controls.bottom || Log)} {layout.showControls && controls.bottom !== 'none' && this.region('bottom', controls.bottom || Log)}
</div> </div>
{!this.plugin.spec.components?.hideTaskOverlay && <OverlayTaskProgress />} {!this.plugin.spec.components?.hideTaskOverlay && <OverlayTaskProgress />}
<DragOverlay plugin={this.plugin} showDragOverlay={this.showDragOverlay} />
</div> </div>
</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 { export class ControlsWrapper extends PluginUIComponent {
render() { render() {
const StructureTools = this.plugin.spec.components?.structureTools || DefaultStructureTools; const StructureTools = this.plugin.spec.components?.structureTools || DefaultStructureTools;
......
...@@ -627,3 +627,18 @@ ...@@ -627,3 +627,18 @@
padding-left: 0; padding-left: 0;
list-style: none; 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