image preview bubble for data list view

This commit is contained in:
tarafanlin 2020-10-20 02:25:46 -07:00
parent dd38cf8f9a
commit 7be67e7037
2 changed files with 104 additions and 115 deletions

View File

@ -14,14 +14,12 @@ import { generateLayout } from "~/components/core/Slate";
import { CheckBox } from "~/components/system/components/CheckBox"; import { CheckBox } from "~/components/system/components/CheckBox";
import { Table } from "~/components/core/Table"; import { Table } from "~/components/core/Table";
import { FileTypeIcon } from "~/components/core/FileTypeIcon"; import { FileTypeIcon } from "~/components/core/FileTypeIcon";
import { import { ButtonPrimary, ButtonWarning } from "~/components/system/components/Buttons";
ButtonPrimary,
ButtonWarning,
} from "~/components/system/components/Buttons";
import { TabGroup } from "~/components/core/TabGroup"; import { TabGroup } from "~/components/core/TabGroup";
import SlateMediaObject from "~/components/core/SlateMediaObject"; import SlateMediaObject from "~/components/core/SlateMediaObject";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
import FilePreviewBubble from "~/components/core/FilePreviewBubble";
const VIEW_LIMIT = 20; const VIEW_LIMIT = 20;
@ -236,14 +234,8 @@ export default class DataView extends React.Component {
if (!mounted) { if (!mounted) {
mounted = true; mounted = true;
window.addEventListener("remote-data-deletion", this._handleDataDeletion); window.addEventListener("remote-data-deletion", this._handleDataDeletion);
window.addEventListener( window.addEventListener("remote-slate-object-remove", this._handleRemoteSlateObjectRemove);
"remote-slate-object-remove", window.addEventListener("remote-slate-object-add", this._handleRemoteSlateObjectAdd);
this._handleRemoteSlateObjectRemove
);
window.addEventListener(
"remote-slate-object-add",
this._handleRemoteSlateObjectAdd
);
window.addEventListener("remote-update-carousel", this._handleUpdate); window.addEventListener("remote-update-carousel", this._handleUpdate);
} }
@ -253,26 +245,16 @@ export default class DataView extends React.Component {
componentWillUnmount() { componentWillUnmount() {
mounted = false; mounted = false;
window.removeEventListener( window.removeEventListener("remote-data-deletion", this._handleDataDeletion);
"remote-data-deletion", window.removeEventListener("remote-slate-object-remove", this._handleRemoteSlateObjectRemove);
this._handleDataDeletion window.removeEventListener("remote-slate-object-add", this._handleRemoteSlateObjectAdd);
);
window.removeEventListener(
"remote-slate-object-remove",
this._handleRemoteSlateObjectRemove
);
window.removeEventListener(
"remote-slate-object-add",
this._handleRemoteSlateObjectAdd
);
window.removeEventListener("remote-update-carousel", this._handleUpdate); window.removeEventListener("remote-update-carousel", this._handleUpdate);
} }
_increment = (direction) => { _increment = (direction) => {
if ( if (
direction > 0 && direction > 0 &&
this.state.startIndex + VIEW_LIMIT < this.state.startIndex + VIEW_LIMIT < this.props.viewer.library[0].children.length
this.props.viewer.library[0].children.length
) { ) {
this.setState({ startIndex: this.state.startIndex + VIEW_LIMIT }); this.setState({ startIndex: this.state.startIndex + VIEW_LIMIT });
} else if (direction < 0 && this.state.startIndex - VIEW_LIMIT >= 0) { } else if (direction < 0 && this.state.startIndex - VIEW_LIMIT >= 0) {
@ -299,10 +281,7 @@ export default class DataView extends React.Component {
} }
let cids = Object.keys(this.state.checked).map((id) => { let cids = Object.keys(this.state.checked).map((id) => {
let index = parseInt(id); let index = parseInt(id);
return this.props.viewer.library[0].children[index].ipfs.replace( return this.props.viewer.library[0].children[index].ipfs.replace("/ipfs/", "");
"/ipfs/",
""
);
}); });
this._handleLoading({ cids }); this._handleLoading({ cids });
@ -312,8 +291,7 @@ export default class DataView extends React.Component {
name: "create-alert", name: "create-alert",
detail: { detail: {
alert: { alert: {
message: message: "We're having trouble connecting right now. Please try again later",
"We're having trouble connecting right now. Please try again later",
}, },
}, },
}); });
@ -360,8 +338,7 @@ export default class DataView extends React.Component {
name: "create-alert", name: "create-alert",
detail: { detail: {
alert: { alert: {
message: message: "We're having trouble connecting right now. Please try again later",
"We're having trouble connecting right now. Please try again later",
}, },
}, },
}); });
@ -460,8 +437,7 @@ export default class DataView extends React.Component {
name: "create-alert", name: "create-alert",
detail: { detail: {
alert: { alert: {
message: message: "We're having trouble connecting right now. Please try again later",
"We're having trouble connecting right now. Please try again later",
}, },
}, },
}); });
@ -599,9 +575,7 @@ export default class DataView extends React.Component {
_handleAddToSlate = (e) => { _handleAddToSlate = (e) => {
let userFiles = this.props.viewer.library[0].children; let userFiles = this.props.viewer.library[0].children;
let files = Object.keys(this.state.checked).map( let files = Object.keys(this.state.checked).map((index) => userFiles[index]);
(index) => userFiles[index]
);
this.props.onAction({ this.props.onAction({
type: "SIDEBAR", type: "SIDEBAR",
value: "SIDEBAR_ADD_FILE_TO_SLATE", value: "SIDEBAR_ADD_FILE_TO_SLATE",
@ -628,10 +602,7 @@ export default class DataView extends React.Component {
> >
<SVG.GridView <SVG.GridView
style={{ style={{
color: color: this.state.view === "grid" ? Constants.system.black : "rgba(0,0,0,0.25)",
this.state.view === "grid"
? Constants.system.black
: "rgba(0,0,0,0.25)",
}} }}
height="24px" height="24px"
/> />
@ -646,10 +617,7 @@ export default class DataView extends React.Component {
> >
<SVG.ListView <SVG.ListView
style={{ style={{
color: color: this.state.view === "list" ? Constants.system.black : "rgba(0,0,0,0.25)",
this.state.view === "list"
? Constants.system.black
: "rgba(0,0,0,0.25)",
}} }}
height="24px" height="24px"
/> />
@ -672,16 +640,12 @@ export default class DataView extends React.Component {
} }
onClick={() => this._increment(-1)} onClick={() => this._increment(-1)}
> >
<SVG.NavigationArrow <SVG.NavigationArrow height="24px" style={{ transform: `rotate(180deg)` }} />
height="24px"
style={{ transform: `rotate(180deg)` }}
/>
</span> </span>
<span <span
css={STYLES_ICON_ELEMENT} css={STYLES_ICON_ELEMENT}
style={ style={
this.state.startIndex + VIEW_LIMIT < this.state.startIndex + VIEW_LIMIT < this.props.viewer.library[0].children.length
this.props.viewer.library[0].children.length
? null ? null
: { : {
cursor: "not-allowed", cursor: "not-allowed",
@ -717,14 +681,8 @@ export default class DataView extends React.Component {
> >
Delete files Delete files
</ButtonWarning> </ButtonWarning>
<div <div css={STYLES_ICON_BOX} onClick={() => this.setState({ checked: {} })}>
css={STYLES_ICON_BOX} <SVG.Dismiss height="20px" style={{ color: Constants.system.darkGray }} />
onClick={() => this.setState({ checked: {} })}
>
<SVG.Dismiss
height="20px"
style={{ color: Constants.system.darkGray }}
/>
</div> </div>
</div> </div>
</div> </div>
@ -744,24 +702,17 @@ export default class DataView extends React.Component {
<div <div
key={each.id} key={each.id}
css={STYLES_IMAGE_BOX} css={STYLES_IMAGE_BOX}
onClick={() => onClick={() => this._handleSelect(i + this.state.startIndex)}
this._handleSelect(i + this.state.startIndex)
}
onMouseEnter={() => this.setState({ hover: i })} onMouseEnter={() => this.setState({ hover: i })}
onMouseLeave={() => this.setState({ hover: null })} onMouseLeave={() => this.setState({ hover: null })}
> >
<SlateMediaObjectPreview <SlateMediaObjectPreview
url={`${Constants.gateways.ipfs}/${each.ipfs.replace( url={`${Constants.gateways.ipfs}/${each.ipfs.replace("/ipfs/", "")}`}
"/ipfs/",
""
)}`}
title={each.file || each.name} title={each.file || each.name}
type={each.type || each.icon} type={each.type || each.icon}
/> />
<span css={STYLES_MOBILE_HIDDEN}> <span css={STYLES_MOBILE_HIDDEN}>
{numChecked || {numChecked || this.state.hover === i || this.state.menu === each.id ? (
this.state.hover === i ||
this.state.menu === each.id ? (
<React.Fragment> <React.Fragment>
<div <div
css={STYLES_ICON_BOX_BACKGROUND} css={STYLES_ICON_BOX_BACKGROUND}
@ -771,18 +722,13 @@ export default class DataView extends React.Component {
: (e) => { : (e) => {
e.stopPropagation(); e.stopPropagation();
this.setState({ this.setState({
menu: menu: this.state.menu === each.id ? null : each.id,
this.state.menu === each.id
? null
: each.id,
}); });
} }
} }
> >
{this.state.loading[cid] ? ( {this.state.loading[cid] ? (
<LoaderSpinner <LoaderSpinner style={{ height: 24, width: 24 }} />
style={{ height: 24, width: 24 }}
/>
) : ( ) : (
<SVG.MoreHorizontal height="24px" /> <SVG.MoreHorizontal height="24px" />
)} )}
@ -807,10 +753,7 @@ export default class DataView extends React.Component {
{ {
text: "Copy link", text: "Copy link",
onClick: (e) => onClick: (e) =>
this._handleCopy( this._handleCopy(e, `${Constants.gateways.ipfs}/${cid}`),
e,
`${Constants.gateways.ipfs}/${cid}`
),
}, },
{ {
text: "Delete", text: "Delete",
@ -841,16 +784,12 @@ export default class DataView extends React.Component {
> >
<CheckBox <CheckBox
name={this.state.startIndex + i} name={this.state.startIndex + i}
value={ value={!!this.state.checked[this.state.startIndex + i]}
!!this.state.checked[this.state.startIndex + i]
}
onChange={this._handleCheckBox} onChange={this._handleCheckBox}
boxStyle={{ boxStyle={{
height: 24, height: 24,
width: 24, width: 24,
backgroundColor: this.state.checked[ backgroundColor: this.state.checked[this.state.startIndex + i]
this.state.startIndex + i
]
? Constants.system.brand ? Constants.system.brand
: "rgba(255, 255, 255, 0.75)", : "rgba(255, 255, 255, 0.75)",
}} }}
@ -885,14 +824,8 @@ export default class DataView extends React.Component {
{ {
key: "checkbox", key: "checkbox",
name: numChecked ? ( name: numChecked ? (
<div <div css={STYLES_CANCEL_BOX} onClick={() => this.setState({ checked: {} })}>
css={STYLES_CANCEL_BOX} <SVG.Minus height="16px" style={{ color: Constants.system.white }} />
onClick={() => this.setState({ checked: {} })}
>
<SVG.Minus
height="16px"
style={{ color: Constants.system.white }}
/>
</div> </div>
) : ( ) : (
<span /> <span />
@ -933,24 +866,22 @@ export default class DataView extends React.Component {
position: "relative", position: "relative",
right: 3, right: 3,
margin: "12px 0", margin: "12px 0",
opacity: opacity: numChecked > 0 || this.state.hover === index ? "100%" : "0%",
numChecked > 0 || this.state.hover === index ? "100%" : "0%",
}} }}
/> />
), ),
name: ( name: (
<div <FilePreviewBubble url={cid} type={each.type}>
css={STYLES_CONTAINER_HOVER}
onClick={() => this._handleSelect(this.state.startIndex + index)}
>
<div <div
css={STYLES_ICON_BOX_HOVER} css={STYLES_CONTAINER_HOVER}
style={{ paddingLeft: 0, paddingRight: 18 }} onClick={() => this._handleSelect(this.state.startIndex + index)}
> >
<FileTypeIcon type={each.type} height="24px" /> <div css={STYLES_ICON_BOX_HOVER} style={{ paddingLeft: 0, paddingRight: 18 }}>
<FileTypeIcon type={each.type} height="24px" />
</div>
<div css={STYLES_LINK}>{each.file || each.name}</div>
</div> </div>
<div css={STYLES_LINK}>{each.file || each.name}</div> </FilePreviewBubble>
</div>
), ),
size: <div css={STYLES_VALUE}>{Strings.bytesToSize(each.size)}</div>, size: <div css={STYLES_VALUE}>{Strings.bytesToSize(each.size)}</div>,
more: ( more: (
@ -990,19 +921,13 @@ export default class DataView extends React.Component {
}, },
{ {
text: "Copy link", text: "Copy link",
onClick: (e) => onClick: (e) => this._handleCopy(e, `${Constants.gateways.ipfs}/${cid}`),
this._handleCopy(
e,
`${Constants.gateways.ipfs}/${cid}`
),
}, },
{ {
text: "Delete", text: "Delete",
onClick: (e) => { onClick: (e) => {
e.stopPropagation(); e.stopPropagation();
this.setState({ menu: null }, () => this.setState({ menu: null }, () => this._handleDelete(cid));
this._handleDelete(cid)
);
}, },
}, },
]} ]}

View File

@ -0,0 +1,64 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import { css, keyframes } from "@emotion/react";
import { useState } from "react";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
import SlateMediaObject from "~/components/core/SlateMediaObject";
const fadein = keyframes`
0%{
opacity: 0;
}
100%{
opacity: 1;
}
`;
const STYLES_FILE_PREVIEW_BUBBLE = css`
z-index: ${Constants.zindex.tooltip};
padding: 8px;
border-radius: 4px;
background-color: ${Constants.system.black};
animation: ${fadein} 200ms ease-out 1;
width: 200px;
position: absolute;
`;
const STYLES_FILE_PREVIEW = css`
width: 100%;
height: 100%;
`;
const STYLES_BUBBLE_ANCHOR = css`
box-sizing: border-box;
display: inline-flex;
align-item: center;
justify-content: center;
cursor: pointer;
`;
export const FilePreviewBubble = (props) => {
const [onHover, setHover] = useState(false);
const type = props.type && props.type.startsWith("image/") && onHover;
console.log(type);
return (
<React.Fragment>
<div
css={STYLES_BUBBLE_ANCHOR}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{props.children}
</div>
{type && (
<div css={STYLES_FILE_PREVIEW_BUBBLE}>
<img css={STYLES_FILE_PREVIEW} src={`${Constants.gateways.ipfs}/${props.url}`} />
</div>
)}
</React.Fragment>
);
};
export default FilePreviewBubble;