From 9b6562169aa1ee834c6675b641ff984b7e25de4a Mon Sep 17 00:00:00 2001 From: Spencer Rose Date: Sat, 3 Aug 2024 20:34:19 -0700 Subject: [PATCH 1/2] Updates to comparison slider for capture images. --- client/.gitignore | 2 + client/src/_components/common/comparator.js | 65 +---- client/src/_components/common/icon.js | 1 + client/src/_components/common/slider.js | 253 +++++++++++++------- client/src/index.css | 4 + src/controllers/model.controller.js | 3 + src/services/files.services.js | 1 - src/services/import.services.js | 72 +++--- src/services/model.services.js | 1 - 9 files changed, 230 insertions(+), 172 deletions(-) diff --git a/client/.gitignore b/client/.gitignore index 4d29575d..fc81720f 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +.vscode \ No newline at end of file diff --git a/client/src/_components/common/comparator.js b/client/src/_components/common/comparator.js index 59cd8e9f..d37f7091 100644 --- a/client/src/_components/common/comparator.js +++ b/client/src/_components/common/comparator.js @@ -85,7 +85,6 @@ const Comparator = ({ // selected slide state const [selectedIndex, setSelectedIndex] = React.useState(0); const [pairToggle, setPairToggle] = React.useState(false); - const [viewerType, setViewerType] = React.useState('overlay'); const [expandImage, setExpandImage] = React.useState(false); const [panelWidth, setPanelWidth] = React.useState(0); const [panelHeight, setPanelHeight] = React.useState(0); @@ -136,44 +135,16 @@ const Comparator = ({ setSelectedIndex(nextIndex); }; - const getViewer = function() { - const viewers = { - slider: () => { - return - }, - default: () => { - return <> -
- -
-
- -
- - } - } - return viewers.hasOwnProperty(viewerType) ? viewers[viewerType]() : viewers.default(); - } - return (
- { images.length > 0 ? getViewer() : } + { images.length > 0 ? : }
{ selectedIndex + 1 }/{images.length}
{ - expandable && viewerType === 'overlay' && + expandable &&
@@ -184,36 +155,18 @@ const Comparator = ({
  • -
  • - { - viewerType === 'overlay' &&
  • - }
  • -
  • {label}
diff --git a/client/src/_components/common/icon.js b/client/src/_components/common/icon.js index ef2d597d..e028c00b 100644 --- a/client/src/_components/common/icon.js +++ b/client/src/_components/common/icon.js @@ -289,6 +289,7 @@ const getIconClass = (iconType) => { externalLink: 'external-link-square-alt', align: 'crosshairs', overlay: 'layer-group', + slider: 'columns', clustered: 'map-marker', boundaries: 'vector-square', filter: 'filter', diff --git a/client/src/_components/common/slider.js b/client/src/_components/common/slider.js index 5fedef27..41d86ed9 100644 --- a/client/src/_components/common/slider.js +++ b/client/src/_components/common/slider.js @@ -13,17 +13,19 @@ * --------- * Revisions * - 14-07-2023 Redo of slider canvases. + * - 03-08-2024 Update slider to include image swap controls; centred view. */ -import React, {useLayoutEffect, useRef, useState} from 'react'; +import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { schema } from '../../schema'; import Loading from './loading'; import { UserMessage } from './message'; -import {scaleToFit} from '../alignment/tools/scaler.alignment'; +import { scaleToFit } from '../alignment/tools/scaler.alignment'; import InputSelector from "../selectors/input.selector"; +import Button from './button'; import Canvas from "../alignment/canvas/default.canvas.alignment"; import styles from '../styles/slider.module.css'; -import {useWindowSize} from "../../_utils/events.utils.client"; +import { useWindowSize } from "../../_utils/events.utils.client"; /** * Image slider component. @@ -32,10 +34,11 @@ import {useWindowSize} from "../../_utils/events.utils.client"; * @return */ -const Slider = ({ images = []}) => { +const Slider = ({ images = [], toggle=false }) => { - const canvasWidth = 800; - const canvasHeight = 600; + // default canvas sizes + const DEFAULT_CANVAS_WIDTH = 900; + const DEFAULT_CANVAS_HEIGHT = 900; // window dimensions const [winWidth, winHeight] = useWindowSize(); @@ -43,9 +46,6 @@ const Slider = ({ images = []}) => { // input image data const [image1, image2] = images || []; - // mounted status - const _isMounted = React.useRef(false); - // image data layers const renderLayer1 = useRef(null); const renderLayer2 = useRef(null); @@ -58,12 +58,16 @@ const Slider = ({ images = []}) => { let slideWidth = 0; let slideInit = 0; + const [canvasWidth, setCanvasWidth] = React.useState(DEFAULT_CANVAS_WIDTH); + const [canvasHeight, setCanvasHeight] = React.useState(DEFAULT_CANVAS_HEIGHT); + const [status1, setStatus1] = React.useState('empty'); const [status2, setStatus2] = React.useState('empty'); const [message, setMessage] = React.useState('empty'); const [viewWidth, setViewWidth] = React.useState(0); const [viewDims, setViewDims] = React.useState(null); + const [viewSlider, setViewSlider] = React.useState(toggle); // Image labels const label1 = image1 && image1.hasOwnProperty('label') ? image1.label : 'Image 1'; @@ -73,13 +77,86 @@ const Slider = ({ images = []}) => { const [opacity, setOpacity] = useState(100); + /** + * Render image 1 data on canvas layer 1 + * + * @private + */ + + const _drawImage1 = () => { + + if (!imageLayer1.current) return; + + // compute scaled dimensions for image 1 to fit view canvas + const viewDims1 = scaleToFit( + imageLayer1.current.width, + imageLayer1.current.height, + canvasWidth, + canvasHeight, + ); + + // compute image x offset + const viewOffset1 = (canvasWidth - viewDims1.w) / 2; + + // render image to canvas + renderLayer1.current.draw(imageLayer1.current, { + view: { x: viewOffset1, y: 0, w: viewSlider && toggle ? viewDims1.w / 2 : viewDims1.w, h: viewDims1.h }, + source: { x: 0, y: 0, w: viewSlider && toggle ? imageLayer1.current.width / 2 : imageLayer1.current.width, h: imageLayer1.current.height } + }); + + // set initial view dims + if (toggle) { + setCanvasWidth(viewDims1.w); + setCanvasHeight(viewDims1.h); + setViewWidth(viewSlider ? viewDims1.w / 2 : viewDims1.w); + setViewDims(viewDims1); + } + } + + /** + * Render image 2 data on canvas layer 2 + * + * @private + */ + + const _drawImage2 = () => { + + if (!imageLayer2.current) return; + + // compute scaled dimensions for image 2 to fit view canvas + const viewDims2 = scaleToFit( + imageLayer2.current.width, + imageLayer2.current.height, + canvasWidth, + canvasHeight, + ); + + // compute image x offset + const viewOffset2 = (canvasWidth - viewDims2.w) / 2; + + // render image to canvas (toggle sets layer 1 as top layer) + renderLayer2.current.draw(imageLayer2.current, { + view: { x: viewOffset2, y: 0, w: viewSlider && !toggle ? viewDims2.w / 2 : viewDims2.w, h: viewDims2.h }, + source: { x: 0, y: 0, w: viewSlider && !toggle ? imageLayer2.current.width / 2 : imageLayer2.current.width, h: imageLayer2.current.height } + }); + + // set initial view dims + if (!toggle) { + setCanvasWidth(viewDims2.w); + setCanvasHeight(viewDims2.h); + setViewWidth(viewSlider ? viewDims2.w / 2 : viewDims2.w); + setViewDims(viewDims2); + } + } + + /** * Load image data in comparator panel * * @private */ - const load = () => { + const _load = () => { // status = image loading has started setStatus1('loading'); @@ -92,55 +169,29 @@ const Slider = ({ images = []}) => { imageLayer1.current.src = url1; imageLayer2.current.src = url2; - // load image 1 (overlay) + // load image 1 + imageLayer1.current.onload = function () { + // load image data to canvas layer + _drawImage1(); + // update load status + setStatus1('loaded'); + }; imageLayer1.current.onerror = () => { setMessage({ msg: 'Error: Image could not be loaded.', type: 'error' }); imageLayer1.current.src = schema.errors.image.fallbackSrc; }; - imageLayer1.current.onload = function() { - - // compute scaled dimensions to fit view canvas - const viewDims1 = scaleToFit(imageLayer1.current.width, imageLayer1.current.height, canvasWidth, canvasHeight); - - // load data into canvas layer - // - initially show half of top layer image - const initWidth = viewDims1.w / 2; - renderLayer1.current.draw(imageLayer1.current, { - view: {x: 0, y: 0, w: viewDims1.w / 2, h: viewDims1.h}, - source: {x: 0, y: 0, w: imageLayer1.current.width / 2, h: imageLayer1.current.height} - }); - - // set initial view dims - setViewWidth(initWidth); - setViewDims(viewDims1); + // load image 2 + imageLayer2.current.onload = function () { + // load image data to canvas layer + _drawImage2(); // update load status - setStatus1('loaded'); - + setStatus2('loaded'); }; - - // load image 2 (underlay) imageLayer2.current.onerror = () => { setMessage({ msg: 'Error: Image could not be loaded.', type: 'error' }); imageLayer2.current.src = schema.errors.image.fallbackSrc; }; - imageLayer2.current.onload = function() { - // compute scaled dimensions to fit view canvas - const viewDims2 = scaleToFit( - imageLayer2.current.width, - imageLayer2.current.height, - canvasWidth, - canvasHeight, - ); - - // load data into canvas layer - renderLayer2.current.draw(imageLayer2.current, { - view: {x: 0, y: 0, w: viewDims2.w, h: viewDims2.h}, - source: {x: 0, y: 0, w: imageLayer2.current.width, h: imageLayer2.current.height} - }); - }; - - setStatus2('loaded'); } /** @@ -154,7 +205,19 @@ const Slider = ({ images = []}) => { const { value = 100 } = target; setOpacity(value); // set canvas layer alpha value for opacity - renderLayer1.current.alpha( value / 100 ); + toggle ? renderLayer1.current.alpha(value / 100) : renderLayer2.current.alpha(value / 100); + } + + /** + * reset opacity on both layers + * + * @private + */ + + const _resetOpacity = () => { + setOpacity(100); + renderLayer1.current.alpha(100); + renderLayer2.current.alpha(100); } /** @@ -165,20 +228,26 @@ const Slider = ({ images = []}) => { * @private */ - useLayoutEffect(()=>{ + useLayoutEffect(() => { if (status1 === 'empty' || status2 === 'empty') { - load(); + _load(); } - return ()=>{_isMounted.current = true;} - }, []); - useLayoutEffect(()=>{ - load(); - return ()=>{_isMounted.current = true;} - + useLayoutEffect(() => { + _load(); }, [winWidth, winHeight]); + useEffect(() => { + setViewSlider(false); + }, [toggle]); + + useLayoutEffect(() => { + _drawImage1(); + _drawImage2(); + _resetOpacity(); + }, [viewSlider]); + /* Initialize panel resize */ function _resizeStart(e) { /* if slider is no longer engaged, exit this function: */ @@ -204,13 +273,21 @@ const Slider = ({ images = []}) => { // compute adjusted widths const _viewWidth = viewWidth - Math.round(slideInit - e.pageX); - const _imageWidth = Math.round(_viewWidth * ( imageLayer1.current.width / viewDims.w)); + const _imageFullWidth = toggle ? imageLayer1.current.width : imageLayer2.current.width; + const _imageWidth = Math.round(_viewWidth * (_imageFullWidth / viewDims.w)); + const _viewOffset = (canvasWidth - viewDims.w) / 2; // load data into canvas layer // - initially show half of top layer image + toggle ? renderLayer1.current.draw(imageLayer1.current, { - view: {x: 0, y: 0, w: _viewWidth, h: viewDims.h}, - source: {x: 0, y: 0, w: _imageWidth, h: imageLayer1.current.height} + view: { x: _viewOffset, y: 0, w: _viewWidth, h: viewDims.h }, + source: { x: 0, y: 0, w: _imageWidth, h: imageLayer1.current.height } + }) + : + renderLayer2.current.draw(imageLayer2.current, { + view: { x: _viewOffset, y: 0, w: _viewWidth, h: viewDims.h }, + source: { x: 0, y: 0, w: _imageWidth, h: imageLayer2.current.height } }); // set slide width @@ -220,15 +297,16 @@ const Slider = ({ images = []}) => { return <>
- { ( status1 !== 'loaded' || status2 !== 'loaded' ) && } - { message && } -
+ {(status1 !== 'loaded' || status2 !== 'loaded') && } + {message && } +
{ onTouchMove={_resize} /> { alt={label2} />
- +
+
    +
  • +
  • + +
  • +
+
+
; diff --git a/client/src/index.css b/client/src/index.css index 5b129a22..8c2d01fd 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -1079,6 +1079,10 @@ nav.breadcrumb ul li a:hover { background-color: #444444; color: #EEEEEE; } +.capture-button.active { + background-color: coral; + color: #EEEEEE; +} .comparator .prev, .comparator .next, .carousel .prev, diff --git a/src/controllers/model.controller.js b/src/controllers/model.controller.js index 2449dcb2..ebb38e62 100644 --- a/src/controllers/model.controller.js +++ b/src/controllers/model.controller.js @@ -217,6 +217,9 @@ export default function ModelController(nodeType) { // - collates metadata const importData = await importer.receive(req, owner_id, type); + + console.log(importData) + // check if files are present const hasFiles = Object.keys(importData.files).length > 0; diff --git a/src/services/files.services.js b/src/services/files.services.js index 51744358..a4487475 100644 --- a/src/services/files.services.js +++ b/src/services/files.services.js @@ -637,7 +637,6 @@ export const compress = async (files={}, version) => { export const getFilePath = (file, version='medium' ) => { const { fs_path = '', secure_token = '', file_type='' } = file || {}; - console.log(file, '****') const lowResPath = process.env.LOWRES_PATH; const defaultPath = process.env.UPLOAD_DIR; diff --git a/src/services/import.services.js b/src/services/import.services.js index 5cb7b030..955f7c66 100644 --- a/src/services/import.services.js +++ b/src/services/import.services.js @@ -50,37 +50,6 @@ export const receive = (req, owner_id=null, owner_type=null) => { // close request pipeline req.on('close', cleanup); - // initialize busboy - busboy - .on('field', onField.bind(null, metadata.data)) - .on('file', onFile.bind(null, filePromises, metadata, onError)) - .on('error', onError) - .on('end', onEnd) - .on('finish', onEnd); - - busboy.on('partsLimit', function() { - const err = new Error('Reach parts limit'); - err.code = 'Request_parts_limit'; - err.status = 413; - onError(err); - }); - - busboy.on('filesLimit', () => { - const err = new Error('Reach files limit'); - err.code = 'Request_files_limit'; - err.status = 413; - onError(err); - }); - - busboy.on('fieldsLimit', () => { - const err = new Error('Reach fields limit'); - err.code = 'Request_fields_limit'; - err.status = 413; - onError(err); - }); - - req.pipe(busboy); - function onError(err) { console.error(err); cleanup(); @@ -92,6 +61,7 @@ export const receive = (req, owner_id=null, owner_type=null) => { console.error(err); return reject(err); } + Promise.all(filePromises) .then(() => { cleanup(); @@ -105,14 +75,46 @@ export const receive = (req, owner_id=null, owner_type=null) => { function cleanup() { busboy.removeListener('field', onField); busboy.removeListener('file', onFile); - busboy.removeListener('close', cleanup); + busboy.removeListener('close', onEnd); busboy.removeListener('end', cleanup); busboy.removeListener('error', onEnd); busboy.removeListener('partsLimit', onEnd); busboy.removeListener('filesLimit', onEnd); busboy.removeListener('fieldsLimit', onEnd); - busboy.removeListener('finish', onEnd); } + + + // initialize busboy + busboy + .on('field', onField.bind(null, metadata.data)) + .on('file', onFile.bind(null, filePromises, metadata, onError)) + .on('error', onError) + .on('end', onEnd) + .on('close', onEnd); + + busboy.on('partsLimit', function() { + const err = new Error('Reach parts limit'); + err.code = 'Request_parts_limit'; + err.status = 413; + onError(err); + }); + + busboy.on('filesLimit', () => { + const err = new Error('Reach files limit'); + err.code = 'Request_files_limit'; + err.status = 413; + onError(err); + }); + + busboy.on('fieldsLimit', () => { + const err = new Error('Reach fields limit'); + err.code = 'Request_fields_limit'; + err.status = 413; + onError(err); + }); + + req.pipe(busboy); + }); } @@ -131,6 +133,7 @@ export const receive = (req, owner_id=null, owner_type=null) => { */ export const onFile = (filePromises, metadata, onError, fieldname, file, filename, encoding, mimetype) => { + console.log(filename) // reject file upload empty of files if (!filename) { @@ -183,8 +186,9 @@ export const onFile = (filePromises, metadata, onError, fieldname, file, filenam .on('open', () => file .pipe(writeStream) .on('error', reject) - .on('finish', () => { + .on('close', () => { // attach file data to global metadata + console.log(index, fileIndex) // - for multiple files on same type, use key index (e.g. 'file_type[2]') // - for multiple files on different type, use file index if (index) diff --git a/src/services/model.services.js b/src/services/model.services.js index 3cddc641..9a731e29 100644 --- a/src/services/model.services.js +++ b/src/services/model.services.js @@ -220,7 +220,6 @@ export default function ModelServices(model) { // [3] process model data query (if provided) if (stmts.model) { const { sql, data } = stmts.model(item); - console.log(sql) res = await client.query(sql, data); } From 81bd4518f75a612c1892560d42b0acc0b176bdf6 Mon Sep 17 00:00:00 2001 From: Spencer Rose Date: Sun, 18 Aug 2024 16:01:13 -0700 Subject: [PATCH 2/2] Update busboy --- .vscode/settings.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..6c2ff60b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "master" + ] +} \ No newline at end of file