import * as React from "react"; import * as Constants from "~/common/constants"; import * as Strings from "~/common/strings"; import * as SVG from "~/common/svg"; import * as Window from "~/common/window"; import * as UserBehaviors from "~/common/user-behaviors"; import * as Events from "~/common/custom-events"; import * as Styles from "~/common/styles"; import { Link } from "~/components/core/Link"; import { css } from "@emotion/react"; import { Boundary } from "~/components/system/components/fragments/Boundary"; import { PopoverNavigation } from "~/components/system/components/PopoverNavigation"; import { CheckBox } from "~/components/system/components/CheckBox"; import { Table } from "~/components/core/Table"; import { FileTypeIcon } from "~/components/core/FileTypeIcon"; import { ButtonPrimary, ButtonWarning } from "~/components/system/components/Buttons"; import { GroupSelectable, Selectable } from "~/components/core/Selectable/"; import { ConfirmationModal } from "~/components/core/ConfirmationModal"; import FilePreviewBubble from "~/components/core/FilePreviewBubble"; import ObjectPreview from "~/components/core/ObjectPreview"; import isEqual from "lodash/isEqual"; const STYLES_CONTAINER_HOVER = css` display: flex; :hover { color: ${Constants.system.blue}; } `; const STYLES_ICON_BOX = css` height: 32px; width: 32px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; margin-left: 16px; `; const STYLES_CANCEL_BOX = css` height: 16px; width: 16px; background-color: ${Constants.system.blue}; border-radius: 3px; position: relative; right: 3px; cursor: pointer; box-shadow: 0 0 0 1px ${Constants.system.blue}; `; const STYLES_HEADER_LINE = css` display: flex; align-items: center; margin-top: 80px; margin-bottom: 30px; `; const STYLES_LINK = css` display: inline; cursor: pointer; transition: 200ms ease all; font-size: 0.9rem; padding: 12px 0px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 320px; @media (max-width: ${Constants.sizes.tablet}px) { max-width: 120px; } `; const STYLES_VALUE = css` font-size: 0.9rem; padding: 12px 0px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; `; const STYLES_ICON_BOX_HOVER = css` display: inline-flex; align-items: center; padding: 8px; cursor: pointer; :hover { color: ${Constants.system.blue}; } `; const STYLES_ICON_BOX_BACKGROUND = css` display: inline-flex; align-items: center; justify-content: center; height: 25px; width: 25px; cursor: pointer; background-color: rgba(255, 255, 255, 0.75); border-radius: 3px; position: absolute; bottom: 8px; right: 8px; `; const STYLES_ACTION_BAR = css` display: flex; align-items: center; justify-content: space-between; border-radius: 4px; padding: 0px 32px; box-sizing: border-box; background-color: ${Constants.semantic.textGrayDark}; width: 90vw; max-width: 878px; height: 48px; @media (max-width: ${Constants.sizes.mobile}px) { display: none; } `; const STYLES_ACTION_BAR_CONTAINER = css` position: fixed; bottom: 12px; left: 0px; width: 100vw; display: flex; justify-content: center; z-index: ${Constants.zindex.header}; @media (max-width: ${Constants.sizes.mobile}px) { display: none; } `; const STYLES_RIGHT = css` flex-shrink: 0; display: flex; align-items: center; `; const STYLES_LEFT = css` width: 100%; min-width: 10%; display: flex; align-items: center; `; const STYLES_FILES_SELECTED = css` font-family: ${Constants.font.semiBold}; color: ${Constants.system.white}; @media (max-width: ${Constants.sizes.mobile}px) { display: none; } `; const STYLES_COPY_INPUT = css` pointer-events: none; position: absolute; opacity: 0; `; const STYLES_IMAGE_GRID = css` display: grid; grid-template-columns: repeat(auto-fill, minmax(248px, 1fr)); grid-gap: 20px 12px; @media (max-width: ${Constants.sizes.mobile}px) { grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); } `; const STYLES_IMAGE_BOX = css` width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; cursor: pointer; position: relative; @media (max-width: ${Constants.sizes.mobile}px) { margin: 12px auto; } :hover { box-shadow: 0px 0px 0px 1px ${Constants.semantic.borderLight} inset, ${Constants.shadow.lightSmall}; } `; const STYLES_MOBILE_HIDDEN = css` @media (max-width: ${Constants.sizes.mobile}px) { display: none; } `; const STYLES_TAGS_WRAPPER = css` box-sizing: border-box; display: block; width: 100%; max-width: 800px; `; const STYLES_LIST = css` display: inline-flex; margin: 0; padding: 0; `; const STYLES_TAG = css` list-style-type: none; border-radius: 4px; background: ${Constants.semantic.bgLight}; color: ${Constants.system.black}; font-family: ${Constants.font.text}; padding: 2px 8px; margin: 8px 8px 0 0; span { line-height: 1.5; font-size: 14px; } &:hover { background: ${Constants.system.grayLight4}; } `; class Tags extends React.Component { state = { isTruncated: false, truncateIndex: 0, }; listWrapper = React.createRef(); listEl = React.createRef(); componentDidMount() { this._handleTruncate(); } componentDidUpdate(prevProps, prevState) { if (!isEqual(prevProps.tags, this.props.tags)) { this._handleTruncate(); } } _handleTruncate = () => { const listWrapper = this.listWrapper.current?.getBoundingClientRect(); const tagNodes = this.listEl.current?.querySelectorAll("li"); const tagElems = Array.from(tagNodes); let total = 0; const truncateIndex = tagElems.findIndex((tagElem) => { const { width } = tagElem?.getBoundingClientRect(); total += width; if (total >= listWrapper.width - 50) { return true; } }); if (truncateIndex > 0) { this.setState({ isTruncated: true, truncateIndex }); return; } this.setState({ isTruncated: false, truncateIndex: tagElems.length }); }; render() { const { tags } = this.props; return (
{this.state.isTruncated && ...}
); } } export default class DataView extends React.Component { _mounted = false; state = { menu: null, checked: {}, viewLimit: 40, scrollDebounce: false, imageSize: 100, modalShow: false, }; isShiftDown = false; lastSelectedItemIndex = null; gridWrapperEl = React.createRef(); async componentDidMount() { this.calculateWidth(); this.debounceInstance = Window.debounce(this.calculateWidth, 200); if (!this._mounted) { this._mounted = true; window.addEventListener("scroll", this._handleCheckScroll); window.addEventListener("resize", this.debounceInstance); window.addEventListener("keydown", this._handleKeyDown); window.addEventListener("keyup", this._handleKeyUp); if (this.gridWrapperEl.current) { this.gridWrapperEl.current.addEventListener("selectstart", this._handleSelectStart); } } } componentWillUnmount() { this._mounted = false; window.removeEventListener("scroll", this._handleCheckScroll); window.removeEventListener("resize", this.debounceInstance); window.removeEventListener("keydown", this._handleKeyDown); window.removeEventListener("keyup", this._handleKeyUp); if (this.gridWrapperEl.current) { this.gridWrapperEl.current.removeEventListener("selectstart", this._handleSelectStart); } } calculateWidth = () => { let windowWidth = window.innerWidth; let imageSize; if (windowWidth < Constants.sizes.mobile) { imageSize = (windowWidth - 2 * 24 - 20) / 2; } else { imageSize = (windowWidth - 2 * 56 - 4 * 20) / 5; } this.setState({ imageSize }); }; _handleScroll = (e) => { const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight; const body = document.body; const html = document.documentElement; const docHeight = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); const windowBottom = windowHeight + window.pageYOffset; if (windowBottom >= docHeight - 600) { this.setState({ viewLimit: this.state.viewLimit + 30 }); } }; _handleCheckScroll = Window.debounce(this._handleScroll, 200); /* NOTE(daniel): This disable text selection while pressing shift key */ _handleSelectStart = (e) => { if (this.isShiftDown) { e.preventDefault(); } }; _addSelectedItemsOnDrag = (e) => { let selectedItems = {}; for (const i of e) { selectedItems[i] = true; } this.setState({ checked: { ...this.state.checked, ...selectedItems } }); }; _removeSelectedItemsOnDrag = (e) => { const selectedItems = { ...this.state.checked }; for (const i in selectedItems) { selectedItems[i] = selectedItems[i] && !e.includes(+i); if (!selectedItems[i]) delete selectedItems[i]; } this.setState({ checked: selectedItems, ...selectedItems }); }; _handleDragAndSelect = (e, { isAltDown }) => { if (isAltDown) { this._removeSelectedItemsOnDrag(e); return; } this._addSelectedItemsOnDrag(e); }; _handleKeyUp = (e) => { if (e.keyCode === 16 && this.isShiftDown) { this.isShiftDown = false; } }; _handleKeyDown = (e) => { if (e.keyCode === 16 && !this.isShiftDown) { this.isShiftDown = true; } let numChecked = Object.keys(this.state.checked).length || 0; if (e.keyCode === 27 && numChecked) { this._handleUncheckAll(); } }; _handleCheckBox = (e, i) => { e.stopPropagation(); e.preventDefault(); let checked = this.state.checked; if (this.isShiftDown && this.lastSelectedItemIndex !== i) { return this._handleShiftClick({ currentSelectedItemIndex: i, lastSelectedItemIndex: this.lastSelectedItemIndex, checked, }); } if (checked[i]) { delete checked[i]; } else { checked[i] = true; } this.setState({ checked }); this.lastSelectedItemIndex = i; }; _handleShiftClick = ({ currentSelectedItemIndex, lastSelectedItemIndex, checked }) => { const start = Math.min(currentSelectedItemIndex, lastSelectedItemIndex); const stop = Math.max(currentSelectedItemIndex, lastSelectedItemIndex) + 1; let rangeSelected = {}; for (let i = start; i < stop; i++) { if (checked[currentSelectedItemIndex]) { delete checked[i]; } else { rangeSelected[i] = true; } } let newSelection = Object.assign({}, checked, rangeSelected); this.setState({ checked: newSelection }); this.lastSelectedItemIndex = currentSelectedItemIndex; return; }; _handleDownloadFiles = async () => { if (!this.props.viewer) { Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} }); return; } const selectedFiles = this.props.items.filter((_, i) => this.state.checked[i]); UserBehaviors.compressAndDownloadFiles({ files: selectedFiles, resourceURI: this.props.resources.download, }); this.setState({ checked: {} }); }; _handleDelete = (res, id) => { if (!res) { this.setState({ modalShow: false }); return; } let ids; if (id) { ids = [id]; } else { ids = Object.keys(this.state.checked).map((id) => { let index = parseInt(id); let item = this.props.viewer.library[index]; return item.id; }); } let library = this.props.viewer.library.filter((obj) => !ids.includes(obj.id)); this.props.onAction({ type: "UPDATE_VIEWER", viewer: { library } }); UserBehaviors.deleteFiles(ids); this.setState({ checked: {}, modalShow: false }); }; _handleCheckBoxMouseEnter = (i) => { if (this.props.isOwner) { this.setState({ hover: i }); } }; _handleCheckBoxMouseLeave = (i) => { if (this.props.isOwner) { this.setState({ hover: null }); } }; _handleCopy = (e, value) => { e.stopPropagation(); this._handleHide(); this.setState({ copyValue: value }, () => { this._ref.select(); document.execCommand("copy"); }); }; _handleHide = (e) => { this.setState({ menu: null }); }; _handleClick = (e) => { this.setState({ [e.target.name]: e.target.value }); }; _handleAddToSlate = (e) => { if (!this.props.viewer) { Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} }); return; } let userFiles = this.props.viewer.library; let files = Object.keys(this.state.checked).map((index) => userFiles[index]); this.props.onAction({ type: "SIDEBAR", value: "SIDEBAR_ADD_FILE_TO_SLATE", data: { files }, }); this._handleUncheckAll(); }; _handleUncheckAll = () => { this.setState({ checked: {} }); this.lastSelectedItemIndex = null; }; /** Note(Amine): These methods will stop collision between Drag to desktop event and drop to upload */ _stopPropagation = (e) => e.stopPropagation(); _disableDragAndDropUploadEvent = () => { document.addEventListener("dragenter", this._stopPropagation); document.addEventListener("drop", this._stopPropagation); }; _enableDragAndDropUploadEvent = () => { document.removeEventListener("dragenter", this._stopPropagation); document.removeEventListener("drop", this._stopPropagation); }; _handleDragToDesktop = (e, object) => { const url = Strings.getURLfromCID(object.cid); const title = object.filename || object.data.name; const type = object.data.type; console.log(e.dataTransfer, e.dataTransfer.setData); e.dataTransfer.setData("DownloadURL", `${type}:${title}:${url}`); }; getCommonTagFromSelectedItems = () => { const { items } = this.props; const { checked } = this.state; if (!Object.keys(checked).length) { return; } let allTagsFromSelectedItems = Object.keys(checked).map((index) => items[index].data.tags ? items[index].data.tags : [] ); let sortedItems = allTagsFromSelectedItems.sort((a, b) => a.length - b.length); if (sortedItems.length === 0) { return []; } let commonTags = sortedItems.shift().reduce((acc, cur) => { if (acc.indexOf(cur) === -1 && sortedItems.every((item) => item.indexOf(cur) !== -1)) { acc.push(cur); } return acc; }, []); return commonTags; }; render() { let numChecked = Object.keys(this.state.checked).length || 0; // const header = ( //
// //
{ // this.setState({ view: "grid", menu: null }); // }} // > // //
//
// //
{ // this.setState({ view: "list", menu: null }); // }} // > // //
//
//
// ); const footer = ( {numChecked ? (
{numChecked} file{numChecked > 1 ? "s" : ""} selected
Add to collection {this.props.isOwner && ( { this.props.onAction({ type: "SIDEBAR", value: "SIDEBAR_EDIT_TAGS", data: { numChecked, commonTags: this.getCommonTagFromSelectedItems(), objects: this.props.items, checked: this.state.checked, }, }); }} > Edit tags )} this._handleDownloadFiles()} > Download {this.props.isOwner && ( this.setState({ modalShow: true })} > Delete )} {this.state.modalShow && ( )}
{ this.setState({ checked: {} }); this.lastSelectedItemIndex = null; }} >
) : null}
); if (this.props.view === "grid") { return (
{this.props.items.slice(0, this.state.viewLimit).map((each, i) => { return ( { this._disableDragAndDropUploadEvent(); this._handleDragToDesktop(e, each); }} onDragEnd={this._enableDragAndDropUploadEvent} selectableKey={i} onMouseEnter={() => this._handleCheckBoxMouseEnter(i)} onMouseLeave={() => this._handleCheckBoxMouseLeave(i)} >
{numChecked || this.state.hover === i || this.state.menu === each.id ? (
this._handleCheckBox(e, i)} >
) : null}
); })} {[0, 1, 2, 3].map((i) => (
))}
{footer} { this._ref = c; }} readOnly value={this.state.copyValue} css={STYLES_COPY_INPUT} /> ); } const columns = [ { key: "checkbox", name: numChecked ? (
{ this.setState({ checked: {} }); this.lastSelectedItemIndex = null; }} >
) : ( ), width: "24px", }, { key: "name", name:
Name
, width: "100%", }, { key: "tags", name:
Tags
, width: "360px", }, { key: "size", name:
Size
, width: "104px", }, { key: "more", name: , width: "48px", }, ]; const rows = this.props.items.slice(0, this.state.viewLimit).map((each, index) => { const cid = each.cid; return { ...each, checkbox: (
this._handleCheckBox(e, index)}> 0 || this.state.hover === index ? "100%" : "0%", }} />
), name: ( { this._disableDragAndDropUploadEvent(); this._handleDragToDesktop(e, each); }} onDragEnd={this._enableDragAndDropUploadEvent} >
{each.data.name || each.filename}
), tags: <>{each.data.tags?.length ? : null}, size:
{Strings.bytesToSize(each.data.size)}
, more: this.props.isOwner ? (
this.setState({ menu: this.state.menu === each.id ? null : each.id, }) } > {this.state.menu === each.id ? ( this._handleCopy(e, cid), }, // { // text: "Copy link", // onClick: (e) => this._handleCopy(e, Strings.getURLfromCID(cid)), // }, { text: "Delete", onClick: (e) => { e.stopPropagation(); this.setState({ menu: null, modalShow: true }); }, }, ], ]} /> ) : null}
) : null, }; }); const data = { columns, rows, }; return ( {({ isSelecting }) => ( { if (isSelecting) return; this._handleCheckBoxMouseEnter(i); }} onMouseLeave={() => { if (isSelecting) return; this._handleCheckBoxMouseEnter(); }} isShiftDown={this.isShiftDown} /> )} {footer} { this._ref = c; }} readOnly value={this.state.copyValue} css={STYLES_COPY_INPUT} /> ); } }