mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-11-01 10:56:31 +08:00
351 lines
12 KiB
JavaScript
351 lines
12 KiB
JavaScript
import React, { useEffect, useReducer, useRef } from "react";
|
|
import filepath from "path";
|
|
import ReactCSSTransitionGroup from "react-addons-css-transition-group";
|
|
|
|
import { MenuBar } from "./menubar";
|
|
import { Bundle, Icon, NgIf, Loader, EventEmitter, EventReceiver } from "../../components/";
|
|
import { alert, randomString, notify, getMimeType, currentShare } from "../../helpers/";
|
|
import { Chromecast } from "../../model/";
|
|
import { Pager } from "./pager";
|
|
import { t } from "../../locales/";
|
|
import "./imageviewer.scss";
|
|
import "./pager.scss";
|
|
|
|
const SmallExif = (props) => (
|
|
<Bundle loader={import(/* webpackChunkName: "exif" */"./image_exif")} symbol="SmallExif">
|
|
{(Comp) => <Comp {...props}/>}
|
|
</Bundle>
|
|
);
|
|
const LargeExif = (props) => (
|
|
<Bundle loader={import(/* webpackChunkName: "exif" */"./image_exif")} symbol="LargeExif">
|
|
{(Comp) => <Comp {...props}/>}
|
|
</Bundle>
|
|
);
|
|
|
|
export function ImageViewerComponent({ filename, data, path, subscribe, unsubscribe }) {
|
|
const [state, setState] = useReducer((s, a) => {
|
|
return { ...s, ...a };
|
|
}, {
|
|
preload: null,
|
|
refresh: 0,
|
|
show_exif: false,
|
|
is_loaded: false,
|
|
draggable: false,
|
|
});
|
|
const $container = useRef();
|
|
const refresh = () => setState({ refresh: state.refresh + 1 });
|
|
const shortcut = (e) => {
|
|
if (e.keyCode === 27) setState({ show_exif: false });
|
|
else if (e.keyCode === 73) setState({ show_exif: !state.show_exif });
|
|
};
|
|
useEffect(() => {
|
|
setState({ is_loaded: false });
|
|
subscribe("media::preload", (preload) => {
|
|
setState({ preload: preload });
|
|
});
|
|
document.addEventListener("webkitfullscreenchange", refresh);
|
|
document.addEventListener("mozfullscreenchange", refresh);
|
|
document.addEventListener("fullscreenchange", refresh);
|
|
document.addEventListener("keydown", shortcut);
|
|
|
|
return () => {
|
|
unsubscribe("media::preload");
|
|
document.removeEventListener("webkitfullscreenchange", refresh);
|
|
document.removeEventListener("mozfullscreenchange", refresh);
|
|
document.removeEventListener("fullscreenchange", refresh);
|
|
document.removeEventListener("keydown", shortcut);
|
|
};
|
|
|
|
}, [data]);
|
|
|
|
const chromecastSetup = (event) => {
|
|
switch (event.sessionState) {
|
|
case cast.framework.SessionState.SESSION_STARTED:
|
|
chromecastLoader();
|
|
break;
|
|
}
|
|
};
|
|
|
|
const chromecastLoader = () => {
|
|
const session = Chromecast.session();
|
|
if (!session) return;
|
|
|
|
const link = Chromecast.createLink(data);
|
|
const media = new chrome.cast.media.MediaInfo(
|
|
link,
|
|
getMimeType(filename),
|
|
);
|
|
media.metadata = new chrome.cast.media.PhotoMediaMetadata();
|
|
media.metadata.title = filename;
|
|
media.metadata.images = [
|
|
new chrome.cast.Image(Chromecast.origin() + "/assets/icons/photo.png"),
|
|
];
|
|
Chromecast.createRequest(media)
|
|
.then((req) => session.loadMedia(req))
|
|
.catch((err) => {
|
|
console.error(err)
|
|
notify.send(t("Cannot establish a connection"), "error");
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
const context = Chromecast.context();
|
|
if (!context) return;
|
|
document.getElementById("chromecast-target").append(document.createElement("google-cast-launcher"));
|
|
context.addEventListener(
|
|
cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
|
chromecastSetup,
|
|
);
|
|
const media = Chromecast.media();
|
|
if (media && media.media && media.media.mediaCategory === "IMAGE") chromecastLoader();
|
|
return () => {
|
|
context.removeEventListener(
|
|
cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
|
chromecastSetup,
|
|
);
|
|
};
|
|
}, []);
|
|
|
|
const hasExif = (fname) => {
|
|
const ext = filepath.extname(fname).toLowerCase().substring(1);
|
|
return ["jpg", "jpeg", "tiff", "tif"].indexOf(ext) !== -1;
|
|
};
|
|
const toggleExif = () => {
|
|
if (window.innerWidth < 580) {
|
|
alert.now(<SmallExif />);
|
|
} else {
|
|
setState({
|
|
show_exif: !state.show_exif,
|
|
});
|
|
}
|
|
};
|
|
const requestFullScreen = () => {
|
|
chromecastLoader();
|
|
if ("webkitRequestFullscreen" in document.body) {
|
|
$container.current.webkitRequestFullscreen();
|
|
} else if ("mozRequestFullScreen" in document.body) {
|
|
$container.current.mozRequestFullScreen();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="component_imageviewer">
|
|
<MenuBar title={filename} download={data}>
|
|
<NgIf type="inline" cond={hasExif(filename)}>
|
|
<Icon name="info" onClick={toggleExif} />
|
|
</NgIf>
|
|
<NgIf
|
|
type="inline"
|
|
cond={("webkitRequestFullscreen" in document.body) ||
|
|
("mozRequestFullScreen" in document.body)}>
|
|
<Icon name="fullscreen" onClick={requestFullScreen} />
|
|
</NgIf>
|
|
</MenuBar>
|
|
<div
|
|
ref={$container}
|
|
className={
|
|
"component_image_container" +
|
|
(document.webkitIsFullScreen || document.mozFullScreen ? " fullscreen" : "")
|
|
}
|
|
>
|
|
<div className="images_wrapper">
|
|
<ImageFancy
|
|
draggable={state.draggable}
|
|
onLoad={() => setState({ is_loaded: true })}
|
|
url={data} />
|
|
</div>
|
|
<div className={"images_aside scroll-y"+(state.show_exif ? " open": "")}>
|
|
<div className="header">
|
|
<div>{ t("Info") }</div>
|
|
<div style={{ flex: 1 }}>
|
|
<Icon name="close" onClick={toggleExif} />
|
|
</div>
|
|
</div>
|
|
<div className="content">
|
|
<LargeExif
|
|
data={data}
|
|
show={state.show_exif}
|
|
ready={state.is_loaded} />
|
|
</div>
|
|
</div>
|
|
<Pager
|
|
path={path}
|
|
pageChange={(files) => setState({ draggable: files.length > 1 ? true : false })}
|
|
/>
|
|
</div>
|
|
|
|
<NgIf cond={state.is_loaded}>
|
|
<Img style={{ display: "none" }} src={state.preload}/>
|
|
</NgIf>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export const ImageViewer = EventReceiver(EventEmitter(ImageViewerComponent));
|
|
|
|
class ImageFancyComponent extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
isLoading: true,
|
|
isError: false,
|
|
drag_init: { x: null, t: null },
|
|
drag_current: { x: null, t: null },
|
|
hasAction: false,
|
|
};
|
|
this.img = new Image();
|
|
this.img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
|
|
}
|
|
|
|
UNSAFE_componentWillReceiveProps(nextProp) {
|
|
if (nextProp.url !== this.props.url) {
|
|
this.setState({
|
|
isLoading: true,
|
|
isError: false,
|
|
drag_current: { x: 0 },
|
|
hasAction: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
onLoad() {
|
|
this.setState({ isLoading: false });
|
|
this.props.onLoad();
|
|
}
|
|
onError(w) {
|
|
this.setState({ isError: true });
|
|
}
|
|
|
|
imageDragStart(e) {
|
|
if(!this.props.draggable) return;
|
|
const t = new Date();
|
|
if (e.touches) {
|
|
this.setState({
|
|
drag_init: { x: e.touches[0].clientX, t: t },
|
|
hasAction: true,
|
|
});
|
|
} else {
|
|
this.setState({
|
|
drag_init: { x: e.pageX, t: t },
|
|
hasAction: true,
|
|
});
|
|
}
|
|
if (e.dataTransfer) e.dataTransfer.setDragImage(this.img, 0, 0);
|
|
}
|
|
imageDragEnd(e) {
|
|
const drag_end = {
|
|
x: function(dragX, touch) {
|
|
if (dragX !== null) return dragX;
|
|
if (touch && touch[0]) {
|
|
return touch[0].clientX;
|
|
}
|
|
return 0;
|
|
}(e.pageX || null, e.changedTouches || null),
|
|
t: new Date(),
|
|
};
|
|
|
|
const direction = function(x_current, x_init) {
|
|
if (x_current.t - x_init.t > 200 &&
|
|
Math.abs(x_current.x - x_init.x) <
|
|
(window.innerWidth < 500 ? window.innerWidth / 3 : 250)) {
|
|
return "neutral";
|
|
}
|
|
return x_current.x > x_init.x ? "right" : "left";
|
|
}(drag_end, this.state.drag_init);
|
|
|
|
if (direction === "left") {
|
|
return this.setState({
|
|
drag_current: { x: - window.innerWidth },
|
|
hasAction: false,
|
|
}, () => {
|
|
this.props.emit("media::next");
|
|
});
|
|
} else if (direction === "right") {
|
|
return this.setState({
|
|
drag_current: { x: + window.innerWidth },
|
|
hasAction: false,
|
|
}, () => {
|
|
this.props.emit("media::previous");
|
|
});
|
|
}
|
|
return this.setState({
|
|
drag_current: { x: 0 },
|
|
hasAction: false,
|
|
});
|
|
}
|
|
imageDrag(e) {
|
|
if (e.pageX > 0) {
|
|
this.setState({
|
|
drag_current: { x: e.pageX - this.state.drag_init.x },
|
|
});
|
|
} else if (e.touches && e.touches[0].clientX > 0) {
|
|
this.setState({
|
|
drag_current: { x: e.touches[0].clientX - this.state.drag_init.x },
|
|
});
|
|
}
|
|
}
|
|
|
|
render() {
|
|
if (this.state.isError) {
|
|
return (
|
|
<span className="error">
|
|
<div>
|
|
<div className="label">
|
|
{ t("Can't load this picture") }
|
|
</div>
|
|
</div>
|
|
</span>
|
|
);
|
|
}
|
|
if (this.state.isLoading) {
|
|
return (
|
|
<div className="loader">
|
|
<Loader style={{ margin: "auto" }}/>
|
|
<Img
|
|
className="photo"
|
|
onError={this.onError.bind(this)}
|
|
onLoad={this.onLoad.bind(this)}
|
|
style={{ display: "none" }}
|
|
src={this.props.url} />
|
|
</div>
|
|
);
|
|
}
|
|
return (
|
|
<ReactCSSTransitionGroup
|
|
transitionName="image" transitionLeave={true} transitionEnter={true}
|
|
transitionAppear={true} transitionEnterTimeout={5000} transitionAppearTimeout={5000}
|
|
transitionLeaveTimeout={5000}>
|
|
<div key={this.props.url}>
|
|
<Img
|
|
src={this.props.url}
|
|
style={{ transform: `translateX(${this.state.drag_current.x}px)` }}
|
|
className={this.state.hasAction ? "photo": "photo idle"}
|
|
onTouchStart={this.imageDragStart.bind(this)}
|
|
onDragStart={this.imageDragStart.bind(this)}
|
|
onDragEnd={this.imageDragEnd.bind(this)}
|
|
onTouchEnd={this.imageDragEnd.bind(this)}
|
|
onDrag={this.imageDrag.bind(this)}
|
|
onTouchMove={this.imageDrag.bind(this)}
|
|
draggable={this.props.draggable} />
|
|
</div>
|
|
</ReactCSSTransitionGroup>
|
|
);
|
|
}
|
|
}
|
|
|
|
const ImageFancy = EventEmitter(ImageFancyComponent);
|
|
|
|
function Img({ src, ...props }) {
|
|
const image_url = (url, size) => {
|
|
return url+"&size="+parseInt(Math.max(window.innerWidth*size, window.innerHeight*size));
|
|
};
|
|
if (!src) return null;
|
|
return (
|
|
<img
|
|
{...props}
|
|
src={image_url(src, 1)}
|
|
srcSet={`${image_url(src, 1)}, ${image_url(src, 3/2)} 1.5x, ${image_url(src, 2)} 2x`}
|
|
/>
|
|
);
|
|
}
|