mirror of
https://github.com/filecoin-project/slate.git
synced 2024-12-26 18:44:56 +03:00
image preview bubble for data list view
This commit is contained in:
parent
dd38cf8f9a
commit
7be67e7037
@ -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)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
64
components/core/FilePreviewBubble.js
Normal file
64
components/core/FilePreviewBubble.js
Normal 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;
|
Loading…
Reference in New Issue
Block a user