mirror of
				https://github.com/mickael-kerjean/filestash.git
				synced 2025-10-31 18:16:00 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			632 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			632 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import React, { createRef } from "react";
 | |
| import path from "path";
 | |
| import { Link } from "react-router-dom";
 | |
| import { DragSource, DropTarget } from "react-dnd";
 | |
| 
 | |
| import "./thing.scss";
 | |
| import { Card, NgIf, Icon, EventEmitter, img_placeholder, Input } from "../../components/";
 | |
| import { pathBuilder, basename, filetype, prompt, alert, leftPad, getMimeType, debounce, memory } from "../../helpers/";
 | |
| import { Files } from "../../model/";
 | |
| import { ShareComponent } from "./share";
 | |
| import { TagComponent } from "./tag";
 | |
| import { t } from "../../locales/";
 | |
| 
 | |
| 
 | |
| const canDrop = (props, monitor) => {
 | |
|     const file = monitor.getItem();
 | |
|     if (props.file.type !== "directory") return false;
 | |
|     else if (file.name === props.file.name) return false;
 | |
|     else if (props.file.icon === "loading") return false;
 | |
|     else if (props.selected === true) return false;
 | |
|     return true;
 | |
| };
 | |
| 
 | |
| const HOCDropTargetForFsFile = (Cmp) => {
 | |
|     const nativeFileTarget = {
 | |
|         canDrop,
 | |
|         drop(props, monitor) {
 | |
|             const path = pathBuilder(props.path, props.file.name, "directory");
 | |
|             props.emit(
 | |
|                 "file.upload",
 | |
|                 path,
 | |
|                 monitor.getItem(),
 | |
|             );
 | |
|         },
 | |
|     };
 | |
| 
 | |
|     return DropTarget(
 | |
|         "__NATIVE_FILE__",
 | |
|         nativeFileTarget,
 | |
|         (connect, monitor) => ({
 | |
|             connectDropNativeFile: connect.dropTarget(),
 | |
|             nativeFileIsOver: monitor.isOver(),
 | |
|             canDropNativeFile: monitor.canDrop(),
 | |
|         }),
 | |
|     )(Cmp);
 | |
| };
 | |
| 
 | |
| const HOVDropTargetForVirtualFile = (Cmp) => {
 | |
|     const fileTarget = {
 | |
|         canDrop,
 | |
|         drop(props, monitor, component) {
 | |
|             const src = monitor.getItem();
 | |
|             const dest = props.file;
 | |
| 
 | |
|             if (props.currentSelection.length === 0) {
 | |
|                 const from = pathBuilder(props.path, src.name, src.type);
 | |
|                 const to = pathBuilder(props.path, "./"+dest.name+"/"+src.name, src.type);
 | |
|                 return {
 | |
|                     action: "rename",
 | |
|                     args: [from, to, src.type],
 | |
|                     ctx: "existingfile",
 | |
|                 };
 | |
|             } else {
 | |
|                 return {
 | |
|                     action: "rename.multiple",
 | |
|                     args: props.currentSelection.map((selectionPath) => {
 | |
|                         const from = selectionPath;
 | |
|                         const to = pathBuilder(
 | |
|                             props.path,
 | |
|                             "./"+dest.name+"/"+basename(selectionPath),
 | |
|                             filetype(selectionPath),
 | |
|                         );
 | |
|                         return [from, to];
 | |
|                     }),
 | |
|                 };
 | |
|             }
 | |
|         },
 | |
|     };
 | |
|     return DropTarget(
 | |
|         "file",
 | |
|         fileTarget,
 | |
|         (connect, monitor) => ({
 | |
|             connectDropFile: connect.dropTarget(),
 | |
|             fileIsOver: monitor.isOver(),
 | |
|             canDropFile: monitor.canDrop(),
 | |
|         }),
 | |
|     )(Cmp);
 | |
| };
 | |
| 
 | |
| const HOVDropSourceForVirtualFile = (Cmp) => {
 | |
|     const fileSource = {
 | |
|         beginDrag(props, monitor, component) {
 | |
|             return {
 | |
|                 path: props.path,
 | |
|                 name: props.file.name,
 | |
|                 type: props.file.type,
 | |
|             };
 | |
|         },
 | |
|         canDrag(props, monitor) {
 | |
|             if (props.metadata.can_move === false) {
 | |
|                 return false;
 | |
|             }
 | |
|             if (props.file.icon === "loading") return false;
 | |
|             else if (props.selected === false && props.currentSelection.length > 0) return false;
 | |
|             return true;
 | |
|         },
 | |
|         endDrag(props, monitor, component) {
 | |
|             if (monitor.didDrop() && component.state.icon !== "loading") {
 | |
|                 const result = monitor.getDropResult();
 | |
|                 if (result.action === "rename") {
 | |
|                     props.emit.apply(component, ["file.rename"].concat(result.args));
 | |
|                 } else if (result.action === "rename.multiple") {
 | |
|                     props.emit.call(component, "file.rename.multiple", result.args);
 | |
|                 } else {
 | |
|                     throw new Error("unknown action");
 | |
|                 }
 | |
|             }
 | |
|         },
 | |
|     };
 | |
| 
 | |
|     return DragSource(
 | |
|         "file",
 | |
|         fileSource,
 | |
|         (connect, monitor) => ({
 | |
|             connectDragSource: connect.dragSource(),
 | |
|             isDragging: monitor.isDragging(),
 | |
|         }),
 | |
|     )(Cmp);
 | |
| };
 | |
| 
 | |
| class ExistingThingComponent extends React.Component {
 | |
|     constructor(props) {
 | |
|         super(props);
 | |
|         this.state = {
 | |
|             hover: null,
 | |
|             filename: props.file.name,
 | |
|             is_renaming: false,
 | |
|             preview: null,
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     shouldComponentUpdate(nextProps, nextState) {
 | |
|         if (nextState.hover !== this.state.hover ||
 | |
|             nextState.is_renaming !== this.state.is_renaming ||
 | |
|             nextProps.view !== this.props.view ||
 | |
|             this.state.preview !== nextState.preview ||
 | |
|             this.props.fileIsOver !== nextProps.fileIsOver ||
 | |
|             this.props.canDropFile !== nextProps.canDropFile ||
 | |
|             this.props.nativeFileIsOver !== nextProps.nativeFileIsOver ||
 | |
|             this.props.canDropNativeFile !== nextProps.canDropNativeFile ||
 | |
|             this.props.selected !== nextProps.selected) return true;
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     componentDidMount() {
 | |
|         this.updateThumbnail(this.props);
 | |
|     }
 | |
| 
 | |
|     UNSAFE_componentWillReceiveProps(props) {
 | |
|         if (props.view !== this.props.view) {
 | |
|             this.updateThumbnail(props);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     updateThumbnail(props) {
 | |
|         if (props.view === "grid" && props.icon !== "loading") {
 | |
|             const mimetype = getMimeType(props.file.path);
 | |
|             if (window.CONFIG.thumbnailer.indexOf(mimetype) !== -1) {
 | |
|                 Files.url(props.file.path).then((url) => {
 | |
|                     this.setState({ preview: url+"&thumbnail=true" });
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     onRename(newFilename) {
 | |
|         if (typeof newFilename === "string") {
 | |
|             this.props.emit(
 | |
|                 "file.rename",
 | |
|                 pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
 | |
|                 pathBuilder(this.props.path, newFilename, this.props.file.type),
 | |
|                 this.props.file.type,
 | |
|             );
 | |
|         }
 | |
|         this.setState({ is_renaming: false });
 | |
|     }
 | |
| 
 | |
|     onRenameRequest(force) {
 | |
|         let new_state = !this.state.is_renaming;
 | |
|         if (typeof force === "boolean") {
 | |
|             new_state = force;
 | |
|         }
 | |
|         this.setState({ is_renaming: new_state });
 | |
|     }
 | |
| 
 | |
|     onDeleteRequest(filename) {
 | |
|         prompt.now(
 | |
|             t("Confirm by typing") +" \""+this._confirm_delete_text()+"\"",
 | |
|             (answer) => { // click on ok
 | |
|                 if (answer === this._confirm_delete_text()) {
 | |
|                     this.setState({ icon: "loading" });
 | |
|                     this.props.emit(
 | |
|                         "file.delete",
 | |
|                         pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
 | |
|                         this.props.file.type,
 | |
|                     );
 | |
|                     return Promise.resolve();
 | |
|                 } else {
 | |
|                     return Promise.reject(t("Doesn't match"));
 | |
|                 }
 | |
|             },
 | |
|             () => {/* click on cancel */},
 | |
|         );
 | |
|     }
 | |
|     onDeleteConfirm(answer) {
 | |
|         if (answer === this._confirm_delete_text()) {
 | |
|             this.setState({ icon: "loading", delete_request: false });
 | |
|             this.props.emit(
 | |
|                 "file.delete",
 | |
|                 pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
 | |
|                 this.props.file.type,
 | |
|             );
 | |
|         } else {
 | |
|             this.setState({ delete_error: t("Doesn't match") });
 | |
|         }
 | |
|     }
 | |
|     onDeleteCancel() {
 | |
|         this.setState({ delete_request: false });
 | |
|     }
 | |
| 
 | |
|     onTagRequest() {
 | |
|         alert.now(
 | |
|             <TagComponent path={this.props.file.path} type={this.props.file.type} />,
 | |
|             () => {},
 | |
|         )
 | |
|     }
 | |
| 
 | |
|     onShareRequest(filename) {
 | |
|         alert.now(
 | |
|             <ShareComponent path={this.props.file.path} type={this.props.file.type} />,
 | |
|             (ok) => {},
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     onThingClick(e) {
 | |
|         if (e.ctrlKey === true || e.target.classList.contains("component_checkbox")) {
 | |
|             e.preventDefault();
 | |
|             this.props.emit(
 | |
|                 "file.select",
 | |
|                 pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
 | |
|             );
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     onThingClickCheckbox(e) {
 | |
|         e.preventDefault();
 | |
|         this.props.emit(
 | |
|             "file.select",
 | |
|             pathBuilder(this.props.path, this.props.file.name, this.props.file.type),
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     _confirm_delete_text() {
 | |
|         return this.props.file.name.length > 16?
 | |
|             this.props.file.name.substring(0, 10).toLowerCase() :
 | |
|             this.props.file.name;
 | |
|     }
 | |
| 
 | |
|     render(highlight) {
 | |
|         const { connectDragSource, connectDropFile, connectDropNativeFile } = this.props;
 | |
|         let className = "";
 | |
|         if (this.props.isDragging) {
 | |
|             className += "is-dragging ";
 | |
|         }
 | |
|         if ((this.props.fileIsOver && this.props.canDropFile) || (this.props.nativeFileIsOver && this.props.canDropNativeFile)) {
 | |
|             className += "file-is-hover ";
 | |
|         }
 | |
|         if (this.state.is_renaming) {
 | |
|             className += "highlight ";
 | |
|         }
 | |
|         if (this.props.file.icon === "loading") {
 | |
|             className += "loading ";
 | |
|         }
 | |
|         if (this.state.preview) {
 | |
|             className += "preview ";
 | |
|         }
 | |
|         className = className.trim();
 | |
| 
 | |
|         const fileLink = this.props.file.link
 | |
|             .replace(/%2F/g, "/")
 | |
|             .replace(/\%/g, "%2525") // Hack to get the Link Component to work
 | |
|             .replace(/\?/g, "%3F")
 | |
|             .replace(/\#/g, "%23");
 | |
| 
 | |
|         const onClickCheckbox = (e, scaleNumber) => {
 | |
|             const $box = e.target.parentElement.parentElement.parentElement;
 | |
|             if ($box.classList.contains("view-grid") === false) return;
 | |
|             $box.style.transform = `scale(${scaleNumber})`;
 | |
|         };
 | |
| 
 | |
|         return connectDragSource(connectDropNativeFile(connectDropFile(
 | |
|             <div className={"component_thing" + ` view-${this.props.view}`+
 | |
|                             (this.props.selected === true ? " selected" : " not-selected")}>
 | |
|                 <ToggleableLink
 | |
|                     onClick={this.onThingClick.bind(this)}
 | |
|                     to={fileLink + window.location.search}
 | |
|                     disabled={this.props.file.icon === "loading" || this.state.is_renaming}>
 | |
|                     <Card
 | |
|                         className={className + " " + this.state.hover}>
 | |
|                         <Input type="checkbox" checked={this.props.selected} onMouseUp={(e) => onClickCheckbox(e, 1)} onMouseDown={(e) => onClickCheckbox(e, 0.95)}/>
 | |
|                         <Image
 | |
|                             preview={this.state.preview}
 | |
|                             icon={this.props.file.icon || this.props.file.type}
 | |
|                             view={this.props.view}
 | |
|                             path={path.join(this.props.path, this.props.file.name)}
 | |
|                             hide_extension={this.props.metadata.hide_extension} />
 | |
|                         <Filename
 | |
|                             filename={this.props.file.name}
 | |
|                             filesize={this.props.file.size}
 | |
|                             filetype={this.props.file.type}
 | |
|                             hide_extension={this.props.metadata.hide_extension}
 | |
|                             onRename={this.onRename.bind(this)}
 | |
|                             is_renaming={this.state.is_renaming}
 | |
|                             onRenameCancel={this.onRenameRequest.bind(this, false)} />
 | |
|                         <DateTime
 | |
|                             show={this.state.icon !== "loading" || this.props.file.time !== 0}
 | |
|                             timestamp={this.props.file.time} />
 | |
|                         <ActionButton
 | |
|                             onClickRename={this.onRenameRequest.bind(this)}
 | |
|                             onClickDelete={this.onDeleteRequest.bind(this)}
 | |
|                             onClickShare={this.onShareRequest.bind(this)}
 | |
|                             onClickTag={this.onTagRequest.bind(this)}
 | |
|                             is_renaming={this.state.is_renaming}
 | |
|                             can_rename={this.props.metadata.can_rename !== false}
 | |
|                             can_delete={this.props.metadata.can_delete !== false}
 | |
|                             can_share={this.props.metadata.can_share !== false && window.CONFIG.enable_share === true} />
 | |
|                         <div className="selectionOverlay"></div>
 | |
|                     </Card>
 | |
|                 </ToggleableLink>
 | |
|             </div>,
 | |
|         )));
 | |
|     }
 | |
| }
 | |
| 
 | |
| export const ExistingThing = EventEmitter(
 | |
|     HOCDropTargetForFsFile(
 | |
|         HOVDropTargetForVirtualFile(
 | |
|             HOVDropSourceForVirtualFile(
 | |
|                 ExistingThingComponent,
 | |
|             ),
 | |
|         ),
 | |
|     ),
 | |
| );
 | |
| 
 | |
| export default function ToggleableLink(props) {
 | |
|     const { disabled, ...rest } = props;
 | |
|     return disabled ?
 | |
|         props.children :
 | |
|         <Link {...rest}>{props.children}</Link>;
 | |
| }
 | |
| 
 | |
| class Filename extends React.Component {
 | |
|     constructor(props) {
 | |
|         super(props);
 | |
|         this.state = {
 | |
|             filename: props.filename,
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     onInputFocus(e) {
 | |
|         let value = e.target.value.split(".");
 | |
|         if (value.length > 1) {
 | |
|             value.pop();
 | |
|         }
 | |
|         value = value.join(".");
 | |
|         e.target.setSelectionRange(0, value.length);
 | |
|     }
 | |
| 
 | |
|     onRename(e) {
 | |
|         e.preventDefault();
 | |
|         e.stopPropagation();
 | |
|         this.props.onRename(this.state.filename);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     onCancel() {
 | |
|         this.setState({ filename: this.props.filename });
 | |
|         this.props.onRenameCancel();
 | |
|     }
 | |
| 
 | |
|     preventSelect(e) {
 | |
|         e.preventDefault();
 | |
|         e.stopPropagation();
 | |
|     }
 | |
| 
 | |
|     render() {
 | |
|         const [fileWithoutExtension, fileExtension] = function(filename) {
 | |
|             const fname = filename.split(".");
 | |
|             if (fname.length < 2) {
 | |
|                 return [filename, ""];
 | |
|             }
 | |
|             const ext = fname.pop();
 | |
|             if (window.CONFIG.mime[ext] === undefined) {
 | |
|                 return [filename, ""];
 | |
|             }
 | |
|             return [fname.join("."), "." + ext];
 | |
|         }(this.state.filename);
 | |
| 
 | |
|         return (
 | |
|             <span className="component_filename">
 | |
|                 <span className="file-details">
 | |
|                     <NgIf cond={this.props.is_renaming === false} type="inline">
 | |
|                         {
 | |
|                             fileWithoutExtension
 | |
|                         }{
 | |
|                             this.props.hide_extension ? null :
 | |
|                                 <span className="extension">{fileExtension}</span>
 | |
|                         }
 | |
|                         <FileSize
 | |
|                             type={this.props.filetype}
 | |
|                             size={this.props.filesize} />
 | |
|                     </NgIf>
 | |
|                     <NgIf cond={this.props.is_renaming === true} type="inline">
 | |
|                         <form
 | |
|                             onClick={this.preventSelect}
 | |
|                             onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); this.onRename(e) }}>
 | |
|                             <input
 | |
|                                 value={this.state.filename}
 | |
|                                 onChange={(e) => this.setState({ filename: e.target.value })}
 | |
|                                 onBlur={this.onCancel.bind(this)}
 | |
|                                 onFocus={this.onInputFocus.bind(this)}
 | |
|                                 autoFocus />
 | |
|                         </form>
 | |
|                     </NgIf>
 | |
|                 </span>
 | |
|             </span>
 | |
|         );
 | |
|     }
 | |
| }
 | |
| 
 | |
| const ActionButton = (props) => {
 | |
|     const onRename = (e) => {
 | |
|         e.preventDefault();
 | |
|         props.onClickRename();
 | |
|     };
 | |
| 
 | |
|     const onDelete = (e) => {
 | |
|         e.preventDefault();
 | |
|         props.onClickDelete();
 | |
|     };
 | |
| 
 | |
|     const onShare = (e) => {
 | |
|         e.preventDefault();
 | |
|         props.onClickShare();
 | |
|     };
 | |
| 
 | |
|     const onTag = (e) => {
 | |
|         e.preventDefault();
 | |
|         props.onClickTag();
 | |
|     }
 | |
| 
 | |
|     return (
 | |
|         <div className="component_action">
 | |
|             <NgIf
 | |
|                 type="inline"
 | |
|                 cond={props.can_rename !== false && props.is_renaming === false}>
 | |
|                 <Icon
 | |
|                     name="edit"
 | |
|                     onClick={onRename}
 | |
|                     className="component_updater--icon" />
 | |
|             </NgIf>
 | |
|             {
 | |
|                 /canary/.test(location.search) ? (
 | |
|                     <span type="inline">
 | |
|                         <Icon
 | |
|                             name="tag"
 | |
|                             onClick={onTag}
 | |
|                             className="component_updater--icon" />
 | |
|                     </span>
 | |
|                 ) : (
 | |
|                     <NgIf
 | |
|                         type="inline"
 | |
|                         cond={props.can_delete !== false}>
 | |
|                         <Icon
 | |
|                             name="delete"
 | |
|                             onClick={onDelete}
 | |
|                             className="component_updater--icon" />
 | |
|                     </NgIf>
 | |
|                 )
 | |
|             }
 | |
|             <NgIf
 | |
|                 type="inline"
 | |
|                 cond={props.can_share !== false}>
 | |
|                 <Icon
 | |
|                     name="share"
 | |
|                     onClick={onShare}
 | |
|                     className="component_updater--icon" />
 | |
|             </NgIf>
 | |
|         </div>
 | |
|     );
 | |
| };
 | |
| 
 | |
| const DateTime = (props) => {
 | |
|     function displayTime(timestamp) {
 | |
|         if (!timestamp || timestamp < 0) {
 | |
|             return "";
 | |
|         }
 | |
|         const t = new Date(timestamp);
 | |
|         if("DateTimeFormat" in Intl) {
 | |
|             const str = new Intl.DateTimeFormat({ dateStyle: "short" }).format(t);
 | |
|             if (str.length <= 10) return str;
 | |
|         }
 | |
|         return t.getFullYear() + "-" + leftPad((t.getMonth() + 1).toString(), 2) + "-" + leftPad(t.getDate().toString(), 2);
 | |
|     }
 | |
| 
 | |
|     if (props.show === false) {
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     return (
 | |
|         <span className="component_datetime">
 | |
|             <span>{displayTime(props.timestamp)}</span>
 | |
|         </span>
 | |
|     );
 | |
| };
 | |
| 
 | |
| const FileSize = (props) => {
 | |
|     function displaySize(bytes) {
 | |
|         if (Number.isNaN(bytes) || bytes < 0 || bytes === undefined) {
 | |
|             return "";
 | |
|         } else if (bytes < 1024) {
 | |
|             return "("+bytes+"B)";
 | |
|         } else if (bytes < 1048576) {
 | |
|             return "("+Math.round(bytes/1024*10)/10+"KB)";
 | |
|         } else if (bytes < 1073741824) {
 | |
|             return "("+Math.round(bytes/(1024*1024)*10)/10+"MB)";
 | |
|         } else if (bytes < 1099511627776) {
 | |
|             return "("+Math.round(bytes/(1024*1024*1024)*10)/10+"GB)";
 | |
|         } else {
 | |
|             return "("+Math.round(bytes/(1024*1024*1024*1024))+"TB)";
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return (
 | |
|         <NgIf type="inline" className="component_filesize" cond={props.type === "file"}>
 | |
|             <span> {displaySize(props.size)}</span>
 | |
|         </NgIf>
 | |
|     );
 | |
| };
 | |
| 
 | |
| class Image extends React.Component {
 | |
|     constructor(props) {
 | |
|         super(props);
 | |
|     }
 | |
| 
 | |
|     render() {
 | |
|         if (this.props.preview && this.props.view === "grid") {
 | |
|             return (
 | |
|                 <span>
 | |
|                     <div className="image_layer"></div>
 | |
|                     <LazyLoadImage scroller=".scroll-y" className="thumbnail" src={this.props.preview} />
 | |
|                 </span>
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         const ext = path.extname(this.props.path).replace(/^\./, "");
 | |
|         return (
 | |
|             <span>
 | |
|                 <Icon name={this.props.icon} />
 | |
|                 <NgIf
 | |
|                     className="info_extension"
 | |
|                     cond={!!ext && this.props.view === "grid" && this.props.icon === "file" && this.props.hide_extension !== true}>
 | |
|                     <span>{ext}</span>
 | |
|                 </NgIf>
 | |
|             </span>
 | |
|         );
 | |
|     }
 | |
| };
 | |
| 
 | |
| class LazyLoadImage extends React.Component {
 | |
|     constructor(props) {
 | |
|         super(props);
 | |
|         this.state = {
 | |
|             appear: false,
 | |
|             error: false,
 | |
|         };
 | |
|         this.$scroll = document.querySelector(props.scroller);
 | |
|         this.onScroll = debounce(this.onScroll.bind(this), 250);
 | |
|         this.$el = createRef();
 | |
|     }
 | |
| 
 | |
|     componentDidMount() {
 | |
|         if (!this.$scroll) throw new Error("No scroll detected on LazyLoadImage");
 | |
|         this.$scroll.addEventListener("scroll", this.onScroll, { passive: true });
 | |
|         this.onScroll();
 | |
|     }
 | |
|     componentWillUnmount() {
 | |
|         this.$scroll.removeEventListener("scroll", this.onScroll);
 | |
|     }
 | |
| 
 | |
|     onScroll() {
 | |
|         if (!this.$el.current) return this.componentWillUnmount();
 | |
|         const dim_el = this.$el.current.getBoundingClientRect();
 | |
|         if (dim_el.top + dim_el.height > 0 && dim_el.top < window.innerHeight) {
 | |
|             this.componentWillUnmount();
 | |
|             memory.set(this.props.src, true);
 | |
|             this.setState({ appear: true });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     onError() {
 | |
|         this.setState({ error: true });
 | |
|     }
 | |
| 
 | |
|     render() {
 | |
|         if ((this.props.preview || memory.get(this.props.src) === null) || this.state.error === true) {
 | |
|             return (
 | |
|                 <img
 | |
|                     ref={this.$el}
 | |
|                     className={this.props.className}
 | |
|                     src={img_placeholder} />
 | |
|             );
 | |
|         }
 | |
|         return (
 | |
|             <img
 | |
|                 onError={this.onError.bind(this)}
 | |
|                 className={this.props.className}
 | |
|                 src={this.props.src} />
 | |
|         );
 | |
|     }
 | |
| }
 | 
