mirror of
https://github.com/filecoin-project/slate.git
synced 2024-12-02 08:56:02 +03:00
Merge pull request #425 from filecoin-project/@tarafanlin/filepreview
File image preview uploader
This commit is contained in:
commit
b9238f7e97
@ -259,6 +259,13 @@ export const addCIDToData = async (data) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const updateData = async (data) => {
|
||||
return await returnJSON(`/api/data/update`, {
|
||||
...DEFAULT_OPTIONS,
|
||||
body: JSON.stringify({ data }),
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteBucketItems = async (data) => {
|
||||
return await returnJSON(`/api/data/remove`, {
|
||||
...DEFAULT_OPTIONS,
|
||||
|
@ -977,6 +977,7 @@ export default class ApplicationPage extends React.Component {
|
||||
{scene}
|
||||
</ApplicationLayout>
|
||||
<System.GlobalCarousel
|
||||
resources={this.props.resources}
|
||||
viewer={this.state.viewer}
|
||||
current={
|
||||
current.target &&
|
||||
|
@ -1,15 +1,33 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Validations from "~/common/validations";
|
||||
import * as Actions from "~/common/actions";
|
||||
import * as System from "~/components/system";
|
||||
import * as SVG from "~/common/svg";
|
||||
import * as Window from "~/common/window";
|
||||
import * as FileUtilities from "~/common/file-utilities";
|
||||
|
||||
import { css } from "@emotion/core";
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
import { SlatePicker } from "~/components/core/SlatePicker";
|
||||
import { dispatchCustomEvent } from "~/common/custom-events";
|
||||
|
||||
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
|
||||
import { json } from "body-parser";
|
||||
import { isResSent } from "next/dist/next-server/lib/utils";
|
||||
|
||||
const DEFAULT_BOOK =
|
||||
"https://slate.textile.io/ipfs/bafkreibk32sw7arspy5kw3p5gkuidfcwjbwqyjdktd5wkqqxahvkm2qlyi";
|
||||
const DEFAULT_DATA =
|
||||
"https://slate.textile.io/ipfs/bafkreid6bnjxz6fq2deuhehtxkcesjnjsa2itcdgyn754fddc7u72oks2m";
|
||||
const DEFAULT_DOCUMENT =
|
||||
"https://slate.textile.io/ipfs/bafkreiecdiepww52i5q3luvp4ki2n34o6z3qkjmbk7pfhx4q654a4wxeam";
|
||||
const DEFAULT_VIDEO =
|
||||
"https://slate.textile.io/ipfs/bafkreibesdtut4j5arclrxd2hmkfrv4js4cile7ajnndn3dcn5va6wzoaa";
|
||||
const DEFAULT_AUDIO =
|
||||
"https://slate.textile.io/ipfs/bafkreig2hijckpamesp4nawrhd6vlfvrtzt7yau5wad4mzpm3kie5omv4e";
|
||||
|
||||
const STYLES_NO_VISIBLE_SCROLL = css`
|
||||
overflow-y: scroll;
|
||||
scrollbar-width: none;
|
||||
@ -37,13 +55,9 @@ const STYLES_SIDEBAR = css`
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
background-color: rgba(20, 20, 20, 0.8);
|
||||
${STYLES_NO_VISIBLE_SCROLL}
|
||||
|
||||
@supports (
|
||||
(-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))
|
||||
) {
|
||||
@supports ((-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))) {
|
||||
-webkit-backdrop-filter: blur(75px);
|
||||
backdrop-filter: blur(75px);
|
||||
background-color: rgba(150, 150, 150, 0.2);
|
||||
@ -146,6 +160,44 @@ const STYLES_HIDDEN = css`
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
const STYLES_IMAGE_BOX = css`
|
||||
max-width: 100%;
|
||||
max-height: 368px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: ${Constants.system.black};
|
||||
overflow: hidden;
|
||||
${"" /* box-shadow: 0 0 0 1px ${Constants.system.border} inset; */}
|
||||
border-radius: 4px;
|
||||
`;
|
||||
|
||||
const STYLES_FILE_HIDDREN = css`
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
position: fixed;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
`;
|
||||
|
||||
export const FileTypeDefaultPreview = () => {
|
||||
if (props.type && props.type.startsWith("video/")) {
|
||||
return DEFAULT_VIDEO;
|
||||
}
|
||||
if (props.type && props.type.startsWith("audio/")) {
|
||||
return DEFAULT_AUDIO;
|
||||
}
|
||||
if (props.type && props.type.startsWith("application/epub")) {
|
||||
return DEFAULT_BOOK;
|
||||
}
|
||||
if (props.type && props.type.startsWith("application/pdf")) {
|
||||
return DEFAULT_DOCUMENT;
|
||||
}
|
||||
return DEFAULT_DATA;
|
||||
};
|
||||
|
||||
export default class CarouselSidebarData extends React.Component {
|
||||
_ref = null;
|
||||
|
||||
@ -154,6 +206,7 @@ export default class CarouselSidebarData extends React.Component {
|
||||
isPublic: false,
|
||||
copyValue: "",
|
||||
loading: false,
|
||||
changingPreview: false,
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
@ -182,6 +235,87 @@ export default class CarouselSidebarData extends React.Component {
|
||||
this.setState({ loading: e.detail.loading });
|
||||
};
|
||||
|
||||
_handleUpload = async (e) => {
|
||||
this.setState({ changingPreview: true });
|
||||
e.persist();
|
||||
let file = e.target.files[0];
|
||||
console.log(file);
|
||||
|
||||
if (!file) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: {
|
||||
message: "Something went wrong with the upload. Please try again",
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Validations.isPreviewableImage(file.type)) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: { message: "Upload failed. Only images and gifs are allowed" },
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await FileUtilities.upload({ file, routes: this.props.resources });
|
||||
const { json } = response;
|
||||
|
||||
if (!response) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: { message: "We're having trouble connecting right now" },
|
||||
},
|
||||
});
|
||||
this.setState({ changingPreview: false });
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: { alert: { decorator: response.decorator } },
|
||||
});
|
||||
this.setState({ changingPreview: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const cid = json.data.ipfs.replace("/ipfs/", "");
|
||||
let updateReponse = await Actions.updateData({
|
||||
data: {
|
||||
id: this.props.data.id,
|
||||
previewImage: Strings.getCIDGatewayURL(cid),
|
||||
},
|
||||
});
|
||||
|
||||
if (!updateReponse) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: {
|
||||
message: "We're having trouble connecting right now.",
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (updateReponse.error) {
|
||||
dispatchCustomEvent({
|
||||
name: "create-alert",
|
||||
detail: {
|
||||
alert: {
|
||||
decorator: response.decorator,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
this.setState({ changingPreview: false });
|
||||
};
|
||||
|
||||
_handleDownload = () => {
|
||||
const filename = this.props.data.file;
|
||||
let cid = this.props.data.cid || this.props.data.ipfs.replace("/ipfs/", "");
|
||||
@ -296,6 +430,44 @@ export default class CarouselSidebarData extends React.Component {
|
||||
loading={this.props.loading}
|
||||
selectedColor={Constants.system.white}
|
||||
/>
|
||||
{type && type.startsWith("image/") ? null : (
|
||||
<div>
|
||||
<System.P css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}>
|
||||
Preview image
|
||||
</System.P>
|
||||
<System.P style={{ color: Constants.system.darkGray, lineHeight: "1.5" }}>
|
||||
This is the preview image of your file.
|
||||
</System.P>
|
||||
<div css={STYLES_IMAGE_BOX} style={{ marginTop: 24 }}>
|
||||
<div>
|
||||
<SlateMediaObjectPreview
|
||||
style={{ color: `${Constants.system.black}`, height: "240px" }}
|
||||
blurhash={this.props.previewImage ? false : true}
|
||||
url={url}
|
||||
title={file}
|
||||
type={type}
|
||||
previewImage={this.props.data.previewImage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<input
|
||||
css={STYLES_FILE_HIDDREN}
|
||||
type="file"
|
||||
id="file"
|
||||
onChange={this._handleUpload}
|
||||
/>
|
||||
<System.ButtonPrimary
|
||||
full
|
||||
type="label"
|
||||
htmlFor="file"
|
||||
loading={this.state.changingPreview}
|
||||
>
|
||||
Upload image
|
||||
</System.ButtonPrimary>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}>
|
||||
Privacy
|
||||
</div>
|
||||
|
@ -549,6 +549,8 @@ export default class DataView extends React.Component {
|
||||
url={`${Constants.gateways.ipfs}/${each.ipfs.replace("/ipfs/", "")}`}
|
||||
title={each.file || each.name}
|
||||
type={each.type || each.icon}
|
||||
previewImage={each.previewImage}
|
||||
dataView={true}
|
||||
/>
|
||||
<span css={STYLES_MOBILE_HIDDEN}>
|
||||
{numChecked || this.state.hover === i || this.state.menu === each.id ? (
|
||||
|
@ -229,6 +229,7 @@ const SlatePreview = ({ item, viewer }) => {
|
||||
url={preview.url.replace("https://undefined", "https://")}
|
||||
title={preview.title || preview.name}
|
||||
type={preview.type}
|
||||
previewImage={preview.previewImage}
|
||||
/>
|
||||
) : (
|
||||
<div css={STYLES_EMPTY_SLATE_PREVIEW}>
|
||||
@ -295,6 +296,7 @@ const FilePreview = ({ item, viewer }) => {
|
||||
}
|
||||
title={file.title || file.name || file.file}
|
||||
type={file.type}
|
||||
previewImage={file.previewImage}
|
||||
/>
|
||||
</div>
|
||||
{viewer || (slate && slate.owner) ? (
|
||||
|
@ -1316,6 +1316,7 @@ export class SlateLayout extends React.Component {
|
||||
type={this.state.items[i].type}
|
||||
url={this.state.items[i].url}
|
||||
title={this.state.items[i].title || this.state.items[i].name}
|
||||
previewImage={this.state.previewImage}
|
||||
height={pos.h * unit}
|
||||
width={pos.w * unit}
|
||||
style={{
|
||||
|
@ -39,6 +39,7 @@ export class SlateLayoutMobile extends React.Component {
|
||||
type={item.type}
|
||||
url={item.url}
|
||||
title={item.title || item.name}
|
||||
previewImage={item.previewImage}
|
||||
style={{
|
||||
height: `calc(100vw - 48px)`,
|
||||
width: `calc(100vw - 48px)`,
|
||||
|
@ -188,18 +188,32 @@ export default class SlateMediaObjectPreview extends React.Component {
|
||||
);
|
||||
|
||||
return (
|
||||
<article
|
||||
css={STYLES_ENTITY}
|
||||
style={{
|
||||
...this.props.style,
|
||||
border: this.props.previewPanel ? `1px solid ${Constants.system.bgGray}` : "auto",
|
||||
}}
|
||||
>
|
||||
<div>{element}</div>
|
||||
{this.props.title && !this.props.iconOnly && !this.props.previewPanel ? (
|
||||
<div css={STYLES_TITLE}>{title}</div>
|
||||
) : null}
|
||||
</article>
|
||||
<React.Fragment>
|
||||
{this.props.previewImage ? (
|
||||
<img
|
||||
src={this.props.previewImage}
|
||||
alt=""
|
||||
css={STYLES_IMAGE}
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<article
|
||||
css={STYLES_ENTITY}
|
||||
style={{
|
||||
...this.props.style,
|
||||
border: this.props.previewPanel ? `1px solid ${Constants.system.bgGray}` : "auto",
|
||||
}}
|
||||
>
|
||||
<div>{element}</div>
|
||||
{this.props.title && !this.props.iconOnly && !this.props.previewPanel ? (
|
||||
<div css={STYLES_TITLE}>{title}</div>
|
||||
) : null}
|
||||
</article>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ export class SlatePreviewRow extends React.Component {
|
||||
style={this.props.previewStyle}
|
||||
title={each.title || each.name}
|
||||
iconOnly={this.props.small}
|
||||
previewImage={each.previewImage}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
@ -444,6 +445,7 @@ export class SlatePreviewBlock extends React.Component {
|
||||
style={{ borderRadius: 8 }}
|
||||
imageStyle={{ borderRadius: 8 }}
|
||||
title={first.title || first.name}
|
||||
previewImage={first.previewImage}
|
||||
/>
|
||||
) : (
|
||||
<div css={STYLES_CREATE_NEW} key="add-files">
|
||||
|
@ -85,6 +85,7 @@ export class SlatePreviewRow extends React.Component {
|
||||
style={this.props.previewStyle}
|
||||
title={each.title || each.name}
|
||||
iconOnly={this.props.small}
|
||||
previewImage={each.previewImage}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
@ -222,6 +223,7 @@ export class SlatePreviewBlock extends React.Component {
|
||||
type={first.type}
|
||||
url={first.url}
|
||||
title={first.title || first.name}
|
||||
previewImage={first.previewImage}
|
||||
/>
|
||||
</div>
|
||||
) : first ? (
|
||||
@ -239,6 +241,7 @@ export class SlatePreviewBlock extends React.Component {
|
||||
type={first.type}
|
||||
url={first.url}
|
||||
title={first.title || first.name}
|
||||
previewImage={first.previewImage}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@ -296,6 +299,7 @@ export class SlatePreviewBlock extends React.Component {
|
||||
type={first.type}
|
||||
url={first.url}
|
||||
title={first.title || first.name}
|
||||
previewImage={first.previewImage}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
|
@ -414,6 +414,7 @@ export class GlobalCarousel extends React.Component {
|
||||
loading={this.state.loading}
|
||||
slates={this.props.slates}
|
||||
onAction={this.props.onAction}
|
||||
resources={this.props.resources}
|
||||
data={data}
|
||||
cid={data.cid}
|
||||
/>
|
||||
|
@ -114,3 +114,15 @@ export const addData = ({ user, files }) => {
|
||||
skipped: files.length - noRepeats.length,
|
||||
};
|
||||
};
|
||||
|
||||
export const editItem = ({ user, update }) => {
|
||||
const { library } = user.data;
|
||||
const previewImage = update.data.previewImage;
|
||||
for (let i = 0; i < library[0].children.length; i++) {
|
||||
if (library[0].children[i].id === update.data.id) {
|
||||
library[0].children[i] = { ...library[0].children[i], ...{ previewImage } };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return user.data;
|
||||
};
|
||||
|
60
pages/api/data/update.js
Normal file
60
pages/api/data/update.js
Normal file
@ -0,0 +1,60 @@
|
||||
import * as Data from "~/node_common/data";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
import * as LibraryManager from "~/node_common/managers/library";
|
||||
import * as ViewerManager from "~/node_common/managers/viewer";
|
||||
|
||||
export default async (req, res) => {
|
||||
const id = Utilities.getIdFromCookie(req);
|
||||
if (!id) {
|
||||
return res.status(500).send({ decorator: "SERVER_EDIT_DATA", error: true });
|
||||
}
|
||||
|
||||
const user = await Data.getUserById({ id });
|
||||
|
||||
if (!user || user.error) {
|
||||
return res.status(403).send({ decorator: "SERVER_EDIT_DATA_USER_NOT_FOUND", error: true });
|
||||
}
|
||||
let newUserData = LibraryManager.editItem({ user, update: req.body.data });
|
||||
let response = await Data.updateUserById({
|
||||
id: user.id,
|
||||
data: newUserData,
|
||||
});
|
||||
if (!response || response.error) {
|
||||
return res.status(500).send({ decorator: "SERVER_EDIT_DATA_NOT_UPDATED", error: true });
|
||||
}
|
||||
|
||||
let slates = await Data.getSlatesByUserId({ userId: id });
|
||||
for (let slate of slates) {
|
||||
let edited = false;
|
||||
let objects = slate.data.objects.map((obj) => {
|
||||
if (obj.id === req.body.data.id) {
|
||||
edited = true;
|
||||
obj = { ...obj, ...req.body.data };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
|
||||
if (edited) {
|
||||
await Data.updateSlateById({
|
||||
id: slate.id,
|
||||
updated_at: new Date(),
|
||||
data: {
|
||||
...slate.data,
|
||||
objects,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
slates = await Data.getSlatesByUserId({ userId: id });
|
||||
ViewerManager.hydratePartialSlates(slates, id);
|
||||
|
||||
if (newUserData && newUserData.library) {
|
||||
ViewerManager.hydratePartialLibrary(newUserData.library, id);
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
decorator: "SERVER_EDIT_DATA",
|
||||
data: {},
|
||||
});
|
||||
};
|
@ -105,6 +105,7 @@ export default async (req, res) => {
|
||||
|
||||
return {
|
||||
blurhash: each.blurhash,
|
||||
previewImage: each.previewImage,
|
||||
cid: cid,
|
||||
size: each.size,
|
||||
id: each.id,
|
||||
|
Loading…
Reference in New Issue
Block a user