Skip to content

Commit

Permalink
Implement drag&drop
Browse files Browse the repository at this point in the history
  • Loading branch information
rcowsill committed May 15, 2024
1 parent 5434d7e commit bc90897
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 11 deletions.
106 changes: 106 additions & 0 deletions _site/components/file_drop_target.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* JPEG Sawmill - A viewer for JPEG progressive scans
* Copyright (C) 2024 Rob Cowsill
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { html } from "../external/preact-htm-3.1.1.js";


const mainClass = "file-drop-target";
const activeClass = "active";
const dragoverClass = "dragover";

(function initModule() {
const root = document;
root.addEventListener("dragenter", handleRootDragEnter);
root.addEventListener("dragleave", handleRootDragClear);
root.addEventListener("dragover", handleRootDragOver);
root.addEventListener("drop", handleRootDragClear);
})();

function handleRootDragEnter(e) {
const root = e.currentTarget;
for (const el of root.querySelectorAll(`.${mainClass}`)) {
el.classList.add(activeClass);
}

handleRootDragOver(e);
}

function handleRootDragClear(e) {
// Deactivate drop targets when drag leaves without entering another element
if (e.relatedTarget === null) {
const root = e.currentTarget;
for (const el of root.querySelectorAll(`.${mainClass}`)) {
el.classList.remove(activeClass);
}
}
}

function handleRootDragOver(e) {
const items = e.dataTransfer.items;
if (items.length > 0) {
// Chrome workaround: preventDefault makes file input's button non-dropzone
const targetIsInput = e.target instanceof HTMLInputElement;
const targetIsFileInput = (targetIsInput && e.target.type === "file");
if (!targetIsFileInput || items[0].kind !== "file") {
e.preventDefault();
}
}
}

function handleTargetDragOver(e) {
const items = e.dataTransfer.items;
if (items.length === 1 && items[0].kind === "file") {
e.currentTarget.classList.add(dragoverClass);
} else {
e.dataTransfer.dropEffect = "none";
}

e.preventDefault();
}

function handleTargetDragLeave(e) {
e.currentTarget.classList.remove(dragoverClass);
}


function FileDropTarget({ children, onFileDrop }) {
function handleTargetDrop(e) {
handleTargetDragLeave(e);

const items = e.dataTransfer.items;
if (items.length === 1 && items[0].kind === "file") {
onFileDrop(e);
}

e.preventDefault();
}

const targetEvents = {
onDragOver: handleTargetDragOver,
onDragLeave: handleTargetDragLeave,
onDrop: handleTargetDrop
};

return html`
<div class=${mainClass} ...${targetEvents}>
${children}
</div>
`;
}

export default FileDropTarget;
7 changes: 5 additions & 2 deletions _site/components/sawmill_about_box.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ import { html } from "../external/preact-htm-3.1.1.js";

const repositoryUrl = "https://github.com/rcowsill/JPEGSawmill";

function SawmillAboutBox(){
function SawmillAboutBox() {
return html`
<div class="sawmill-about-box">
<h1>JPEG Sawmill</h1>
<h2><a href=${repositoryUrl}>${repositoryUrl}</a></h2>
<p>Load a JPEG file using the "Browse..." button in the toolbar</p>
<p>
Load a JPEG file using drag & drop or
the file selector in the toolbar
</p>
</div>
`;
}
Expand Down
15 changes: 14 additions & 1 deletion _site/components/sawmill_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,21 @@ function SawmillApp() {
loadJPEG(e.target.files, setFileData);
}

function onFileDrop(e) {
// Apply dropped file to file input
const fileInput = document.querySelector(".sawmill-toolbar .input-file");
fileInput.files = e.dataTransfer.files;

loadJPEG(e.dataTransfer.files, setFileData);
}

const uiEvents = {
onFileInputChange,
onFileDrop
};

return html`
<${SawmillUI} ...${fileData} onFileInputChange=${onFileInputChange} />
<${SawmillUI} ...${fileData} uiEvents=${uiEvents} />
`;
}

Expand Down
40 changes: 40 additions & 0 deletions _site/components/sawmill_file_drop_target.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* JPEG Sawmill - A viewer for JPEG progressive scans
* Copyright (C) 2024 Rob Cowsill
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { html } from "../external/preact-htm-3.1.1.js";
import FileDropTarget from "./file_drop_target.js";


function SawmillFileDropTarget({ onFileDrop }) {
return html`
<div class="sawmill-file-drop-target">
<${FileDropTarget} onFileDrop=${onFileDrop}>
<div class="sawmill-file-drop-overlay">
<div class=drop-filter />
<div class=drop-background />
<div class=drop-vignette />
<div class=drop-outline />
<h1 class=drop-active-label>Drop a JPEG file here to open it</h1>
<h1 class=drop-dragover-label>Open file...</h1>
</div>
<//>
</div>
`;
}

export default SawmillFileDropTarget;
7 changes: 4 additions & 3 deletions _site/components/sawmill_ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ function handleWheelEvent(e, { zoomLevel }, setZoom) {
}


function SawmillUI({ onFileInputChange, uint8Array, scanEndOffsets }) {
function SawmillUI({ uint8Array, scanEndOffsets, uiEvents }) {
const [brightness, onBrightnessSet] = useValueState(0);
const [diffView, onDiffViewSet, setDiffView] = useCheckedState(false);
const [duration, onDurationSet] = useValueState(10);
Expand Down Expand Up @@ -188,12 +188,13 @@ function SawmillUI({ onFileInputChange, uint8Array, scanEndOffsets }) {
onDiffViewSet,
onBrightnessSet,
onScanlinesSet,
onFileInputChange
onFileInputChange: uiEvents.onFileInputChange
};

const viewerEvents = {
onAnimationEnd: (e) => handleAnimationEvent(e, animationIndex, scanData, playbackSetters),
onWheel: (e) => handleWheelEvent(e, zoom, setZoom)
onWheel: (e) => handleWheelEvent(e, zoom, setZoom),
onFileDrop: uiEvents.onFileDrop
};

return html`
Expand Down
2 changes: 2 additions & 0 deletions _site/components/sawmill_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import { html } from "../external/preact-htm-3.1.1.js";
import useTargetedZoom from "../hooks/targeted_zoom.js";
import SawmillFileDropTarget from "./sawmill_file_drop_target.js";
import SawmillMeter from "./sawmill_meter.js";
import SawmillViewerFilter from "./sawmill_viewer_filter.js";

Expand Down Expand Up @@ -74,6 +75,7 @@ function SawmillViewer({ playback, scanData=[], selected=0, settings, viewerEven
<div class=scroll-box ref=${scrollRef} onwheel=${viewerEvents.onWheel}>
<${SawmillViewerFilter} ...${{ scanData, selected, settings }}/>
</div>
<${SawmillFileDropTarget} onFileDrop=${viewerEvents.onFileDrop} />
</div>
`;
}
Expand Down
96 changes: 91 additions & 5 deletions _site/css/jpeg_sawmill.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

button[disabled],
fieldset[disabled],
input[disabled] {
pointer-events: none;
}

.sawmill-ui {
display: grid;
grid-template-areas:
Expand All @@ -29,6 +35,10 @@
margin-block: 1.2em;
}

.sawmill-ui h1 {
font-family: sans-serif;
}

.sawmill-ui .sawmill-toolbar {
grid-area: toolbar;
}
Expand Down Expand Up @@ -211,7 +221,7 @@
clip-path: inset(0 100% 0 0);
}
to {
clip-path: inset(0)
clip-path: inset(0);
}
}

Expand All @@ -222,26 +232,102 @@
animation-duration: var(--anim-total-duration);
}

.sawmill-file-drop-target {
grid-area: scans;
pointer-events: none;
}

.sawmill-file-drop-overlay {
--dragover-transition-duration: 300ms;

display: grid;
grid-template-areas: "stack";
height: 100%;
position: relative;
z-index: 3;
}

.sawmill-file-drop-overlay > * {
grid-area: stack;
position: relative;
}

.sawmill-file-drop-overlay .drop-filter {
backdrop-filter: saturate(40%) brightness(70%) blur(6px);
}

.sawmill-file-drop-overlay .drop-background {
background-color: darkslateblue;
opacity: 0.5;
transition: opacity var(--dragover-transition-duration);
}

.sawmill-file-drop-overlay .drop-vignette {
box-shadow: inset 0 0 24px 20px black;
opacity: 0.8;
transition: box-shadow var(--dragover-transition-duration);
}

.sawmill-file-drop-overlay .drop-outline {
border: 6px dashed blue;
}

.sawmill-file-drop-overlay :is(.drop-active-label, .drop-dragover-label) {
margin: 0;
padding-top: 1.5em;
color: white;
text-align: center;
text-shadow: 0 0 6px black;
transition: opacity var(--dragover-transition-duration);
}

.file-drop-target:not(.active) .sawmill-file-drop-overlay {
visibility: hidden;
}

.file-drop-target.dragover .sawmill-file-drop-overlay .drop-active-label,
.file-drop-target:not(.dragover) .sawmill-file-drop-overlay .drop-dragover-label {
opacity: 0;
}

.file-drop-target.dragover .sawmill-file-drop-overlay .drop-background {
opacity: 0.9;
}

.file-drop-target.dragover .sawmill-file-drop-overlay .drop-vignette {
box-shadow: inset 0 0 24px 6px black;
}

.sawmill-about-box {
background-color: gainsboro;
padding: 1.2em 2em;
border-radius: 6px;
border: 1px solid white;
width: min-content;
}

.sawmill-about-box > * {
margin: 0;
}

.sawmill-about-box h1 {
font-family: sans-serif;
}

.sawmill-about-box h2 {
margin-bottom: 1.5em;
font-size: 1em;
}

.sawmill-about-box a {
white-space: nowrap;
}

.file-drop-target {
height: 100%;
pointer-events: none;
}

.file-drop-target.active {
pointer-events: auto;
}

.graduated-meter {
position: relative;
display: grid;
Expand Down

0 comments on commit bc90897

Please sign in to comment.