From 3907b5b0cd21d65dca9f525c70be12eaa3267ba1 Mon Sep 17 00:00:00 2001 From: Akuoko Daniel Jnr Date: Mon, 12 Apr 2021 18:05:01 +0000 Subject: [PATCH 01/12] feat: add files to data page while waiting on upload --- components/core/Application.js | 43 +++++++++++++++++++ components/core/SlateMediaObject.js | 2 +- components/core/SlateMediaObjectPreview.js | 8 ++-- .../system/components/GlobalCarousel.js | 2 + 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/components/core/Application.js b/components/core/Application.js index b3c60706d..8d08c9713 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -347,6 +347,9 @@ export default class ApplicationPage extends React.Component { return; } + await this._handleOptimisticUpload({ files }); + return; + // TODO(daniel): figure out how to handle successful and unsuccessful uploads const resolvedFiles = []; for (let i = 0; i < files.length; i++) { if (Store.checkCancelled(`${files[i].lastModified}-${files[i].name}`)) { @@ -424,6 +427,46 @@ export default class ApplicationPage extends React.Component { this._handleRegisterLoadingFinished({ keys }); }; + _handleOptimisticUpload = async ({ files }) => { + let optimisticFiles = []; + for (let file of files) { + if (!file.type.startsWith("image")) { + continue; + } + + let dataURL = await this._handleLoadDataURL(file); + let data = { + name: file.name, + type: file.type, + size: file.size, + decorator: "OPTIMISTIC-IMAGE-FILE", + dataURL, + }; + + optimisticFiles.push(data); + } + + let update = [...optimisticFiles, ...this.props.viewer?.library[0].children]; + let library = this.props.viewer.library; + library[0].children = update; + this._handleUpdateViewer({ library }); + }; + + _handleLoadDataURL = (file) => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result); + }; + + reader.onerror = () => { + reject({ error: true }); + reader.abort(); + }; + + reader.readAsDataURL(file); + }); + _handleRegisterFileLoading = ({ fileLoading }) => { if (this.state.fileLoading) { return this.setState({ diff --git a/components/core/SlateMediaObject.js b/components/core/SlateMediaObject.js index 74a7e9fdc..05ba7b87f 100644 --- a/components/core/SlateMediaObject.js +++ b/components/core/SlateMediaObject.js @@ -84,7 +84,7 @@ export default class SlateMediaObject extends React.Component { render() { const { file, isMobile } = this.props; - const url = Strings.getURLfromCID(file.cid); + const url = Strings.getURLfromCID(file.cid) || file.dataURL; const type = file.data.type || ""; const playType = typeMap[type] ? typeMap[type] : type; diff --git a/components/core/SlateMediaObjectPreview.js b/components/core/SlateMediaObjectPreview.js index 0d2a171d5..9b9f67d38 100644 --- a/components/core/SlateMediaObjectPreview.js +++ b/components/core/SlateMediaObjectPreview.js @@ -92,9 +92,9 @@ export default class SlateMediaObjectPreview extends React.Component { let coverImage = this.props.file.data.coverImage; let url; if (type && Validations.isPreviewableImage(type)) { - url = Strings.getURLfromCID(this.props.file.cid); + url = Strings.getURLfromCID(this.props.file.cid) || this.props.file?.dataURL; } else if (coverImage) { - url = Strings.getURLfromCID(coverImage.cid); + url = Strings.getURLfromCID(coverImage.cid) || this.props.file?.dataURL; } if (url) { const img = new Image(); @@ -109,9 +109,9 @@ export default class SlateMediaObjectPreview extends React.Component { const coverImage = this.props.file.data.coverImage; let url; if (type && Validations.isPreviewableImage(type)) { - url = Strings.getURLfromCID(this.props.file.cid); + url = Strings.getURLfromCID(this.props.file.cid) || this.props.file?.dataURL; } else if (coverImage) { - url = Strings.getURLfromCID(coverImage.cid); + url = Strings.getURLfromCID(coverImage.cid) || this.props.file?.dataURL; } if (url) { diff --git a/components/system/components/GlobalCarousel.js b/components/system/components/GlobalCarousel.js index 1661f3535..2e9d99590 100644 --- a/components/system/components/GlobalCarousel.js +++ b/components/system/components/GlobalCarousel.js @@ -288,6 +288,8 @@ export class GlobalCarousel extends React.Component { isRepost = this.props.current?.ownerId !== data.ownerId; } + /* data.url = data?.dataURL || Strings.getCIDGatewayURL(data.cid); */ + let slide = ; return ( From 5e005c9d2dfe626de40b462e6fe7d8483cbc7952 Mon Sep 17 00:00:00 2001 From: Akuoko Daniel Jnr Date: Fri, 16 Apr 2021 06:07:33 +0000 Subject: [PATCH 02/12] fix: double rendering issues --- common/file-utilities.js | 13 +++++- components/core/Application.js | 83 ++++++++++++++++++++++++++++++---- 2 files changed, 85 insertions(+), 11 deletions(-) diff --git a/common/file-utilities.js b/common/file-utilities.js index 50f7d55a8..17e60e9a7 100644 --- a/common/file-utilities.js +++ b/common/file-utilities.js @@ -44,7 +44,14 @@ const getCookie = (name) => { if (match) return match[2]; }; -export const upload = async ({ file, context, bucketName, routes, excludeFromLibrary }) => { +export const upload = async ({ + fileId = null, + file, + context, + bucketName, + routes, + excludeFromLibrary, +}) => { let formData = new FormData(); const HEIC2ANY = require("heic2any"); @@ -65,7 +72,9 @@ export const upload = async ({ file, context, bucketName, routes, excludeFromLib quality: 1, }); //TODO(martina): figure out how to cancel an await if upload has been cancelled - formData.append("data", converted); + formData.append(fileId, converted); + } else if (file.type.startsWith("image")) { + formData.append(fileId, file); } else { formData.append("data", file); } diff --git a/components/core/Application.js b/components/core/Application.js index 8d08c9713..44769d984 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -50,6 +50,7 @@ import ApplicationHeader from "~/components/core/ApplicationHeader"; import ApplicationLayout from "~/components/core/ApplicationLayout"; import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper"; +import { v4 as uuid } from "uuid"; import { GlobalModal } from "~/components/system/components/GlobalModal"; import { OnboardingModal } from "~/components/core/OnboardingModal"; import { SearchModal } from "~/components/core/SearchModal"; @@ -106,6 +107,7 @@ export default class ApplicationPage extends React.Component { isMobile: this.props.isMobile, loaded: false, activeUsers: null, + optimisticFiles: [], }; async componentDidMount() { @@ -224,6 +226,35 @@ export default class ApplicationPage extends React.Component { return; } } + + if (newViewerState.library?.length) { + let oldViewerState = this.state.viewer; + let oldLibrary = oldViewerState.library; + + let update = newViewerState.library[0].children.map((child) => { + let optimisticFileIndex = oldLibrary[0].children.findIndex( + (item) => item.id === child.id && item.decorator.startsWith("OPTMISTIC") + ); + if (optimisticFileIndex > -1) { + return { ...oldLibrary[0].children[optimisticFileIndex], ...child }; + } + + return child; + }); + + oldViewerState.library[0].children = update; + + this.setState( + { + viewer: { ...oldViewerState, ...newViewerState, type: "VIEWER" }, + }, + () => { + if (callback) { + callback(); + } + } + ); + } this.setState( { viewer: { ...this.state.viewer, ...newViewerState }, @@ -347,9 +378,8 @@ export default class ApplicationPage extends React.Component { return; } - await this._handleOptimisticUpload({ files }); - return; - // TODO(daniel): figure out how to handle successful and unsuccessful uploads + files = await this._handleOptimisticUpload({ files }); + const resolvedFiles = []; for (let i = 0; i < files.length; i++) { if (Store.checkCancelled(`${files[i].lastModified}-${files[i].name}`)) { @@ -363,6 +393,7 @@ export default class ApplicationPage extends React.Component { let response; try { response = await FileUtilities.upload({ + fileId: files[i]?.id, file: files[i], context: this, routes: this.props.resources, @@ -429,27 +460,35 @@ export default class ApplicationPage extends React.Component { _handleOptimisticUpload = async ({ files }) => { let optimisticFiles = []; - for (let file of files) { - if (!file.type.startsWith("image")) { + for (let i = 0; i < files.length; i++) { + if (!files[i].type.startsWith("image")) { continue; } - let dataURL = await this._handleLoadDataURL(file); + let id = `data-${uuid()}`; + let dataURL = await this._handleLoadDataURL(files[i]); let data = { - name: file.name, - type: file.type, - size: file.size, + id, + name: files[i].name, + type: files[i].type, + size: files[i].size, decorator: "OPTIMISTIC-IMAGE-FILE", dataURL, }; optimisticFiles.push(data); + + files[i].id = id; } + this.setState({ optimisticFiles }); + let update = [...optimisticFiles, ...this.props.viewer?.library[0].children]; let library = this.props.viewer.library; library[0].children = update; this._handleUpdateViewer({ library }); + + return files; }; _handleLoadDataURL = (file) => @@ -467,6 +506,32 @@ export default class ApplicationPage extends React.Component { reader.readAsDataURL(file); }); + _handleSuccessfulUpload = ({ succeeded }) => { + let optimisticFiles = this.state.optimisticFiles; + // let optimisticFilesNames = this.state.optimisticFiles.map(item => item.name); + let library = this.state.viewer.library; + let update = succeeded.map((item) => { + let data = item.json?.data; + + let itemToUpdateIndex = library[0].children.findIndex((item) => item.id === data.id); + if (itemToUpdateIndex > -1) { + let updatedItem = { ...library[0].children[itemToUpdateIndex], ...data }; + + let optimisticFileIndex = optimisticFiles.findIndex((item) => item.id === data.id); + optimisticFiles.splice(optimisticFileIndex, 1); + + return updatedItem; + } + }); + + this.setState({ optimisticFiles }); + + update = [...update, ...library[0].children]; + library[0].children = update; + + this._handleUpdateViewer({ library }); + }; + _handleRegisterFileLoading = ({ fileLoading }) => { if (this.state.fileLoading) { return this.setState({ From 7062d6081f12b9b807b26142eeac0e64a583d283 Mon Sep 17 00:00:00 2001 From: Akuoko Daniel Jnr Date: Mon, 19 Apr 2021 15:49:31 +0000 Subject: [PATCH 03/12] feat: disable sidebar action for optimistic files --- components/core/Application.js | 3 +- components/core/CarouselSidebarData.js | 758 +++++++++++++++++++++++++ components/core/DataView.js | 36 +- components/system/components/Tag.js | 2 + 4 files changed, 786 insertions(+), 13 deletions(-) create mode 100644 components/core/CarouselSidebarData.js diff --git a/components/core/Application.js b/components/core/Application.js index 44769d984..33be81333 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -255,6 +255,7 @@ export default class ApplicationPage extends React.Component { } ); } + this.setState( { viewer: { ...this.state.viewer, ...newViewerState }, @@ -379,7 +380,7 @@ export default class ApplicationPage extends React.Component { } files = await this._handleOptimisticUpload({ files }); - + return; const resolvedFiles = []; for (let i = 0; i < files.length; i++) { if (Store.checkCancelled(`${files[i].lastModified}-${files[i].name}`)) { diff --git a/components/core/CarouselSidebarData.js b/components/core/CarouselSidebarData.js new file mode 100644 index 000000000..215e2dc80 --- /dev/null +++ b/components/core/CarouselSidebarData.js @@ -0,0 +1,758 @@ +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 UserBehaviors from "~/common/user-behaviors"; +import * as SVG from "~/common/svg"; +import * as Events from "~/common/custom-events"; +import * as Window from "~/common/window"; +import * as FileUtilities from "~/common/file-utilities"; + +import { css, withTheme } from "@emotion/react"; +import { LoaderSpinner } from "~/components/system/components/Loaders"; +import { SlatePicker } from "~/components/core/SlatePicker"; +import { Input } from "~/components/system/components/Input"; +import { Boundary } from "~/components/system/components/fragments/Boundary"; +import { Toggle } from "~/components/system/components/Toggle"; +import { Tag } from "~/components/system/components/Tag"; + +import isEqual from "lodash/isEqual"; + +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; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + + ::-webkit-scrollbar { + width: 0px; + display: none; + } + ::-webkit-scrollbar-track { + background: ${Constants.system.foreground}; + } + ::-webkit-scrollbar-thumb { + background: ${Constants.system.darkGray}; + } +`; + +const STYLES_SIDEBAR = css` + width: 420px; + padding: 48px 24px 0px 24px; + flex-shrink: 0; + height: 100vh; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + background-color: rgba(20, 20, 20, 0.8); + ${STYLES_NO_VISIBLE_SCROLL} + + @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); + } + + @media (max-width: ${Constants.sizes.mobile}px) { + display: none; + } +`; + +const STYLES_DISMISS_BOX = css` + position: absolute; + top: 16px; + right: 16px; + color: ${Constants.system.darkGray}; + cursor: pointer; + + :hover { + color: ${Constants.system.white}; + } +`; + +const STYLES_META = css` + text-align: start; + padding: 14px 0px 8px 0px; + overflow-wrap: break-word; +`; + +const STYLES_META_TITLE = css` + font-family: ${Constants.font.semiBold}; + color: ${Constants.system.white}; + font-size: ${Constants.typescale.lvl2}; + text-decoration: none; + + :hover { + color: ${Constants.system.blue}; + } +`; + +const STYLES_TAG = css` + margin-right: 24px; + padding: 0px 2px; + border-radius: 2px; + border: 1px solid ${Constants.system.darkGray}; +`; + +const STYLES_OPTIONS_SECTION = css` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + margin: 16px 0 16px 0; +`; + +const STYLES_META_DETAILS = css` + color: ${Constants.system.darkGray}; + text-transform: uppercase; + margin: 24px 0px; + font-family: ${Constants.font.medium}; + font-size: 0.9rem; +`; + +const STYLES_ACTIONS = css` + color: ${Constants.system.white}; + border: 1px solid #3c3c3c; + border-radius: 4px; + background-color: transparent; + margin-bottom: 48px; +`; + +const STYLES_ACTION = css` + cursor: pointer; + padding: 12px 16px; + border-bottom: 1px solid #3c3c3c; + display: flex; + align-items: center; + + :hover { + color: ${Constants.system.brand}; + } + + :last-child { + border: none; + } +`; + +const STYLES_SECTION_HEADER = css` + font-family: ${Constants.font.semiBold}; + font-size: 1.1rem; + margin-bottom: 32px; +`; + +const STYLES_HIDDEN = css` + position: absolute; + opacity: 0; + 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_HIDDEN = css` + height: 1px; + width: 1px; + opacity: 0; + visibility: hidden; + position: fixed; + top: -1px; + left: -1px; +`; + +const STYLES_TEXT = css` + color: ${Constants.system.darkGray}; + line-height: 1.5; +`; + +const STYLES_INPUT = { + marginBottom: 16, + backgroundColor: "transparent", + boxShadow: "0 0 0 1px #3c3c3c inset", + color: Constants.system.white, + height: 48, +}; + +const STYLES_AUTOSAVE = css` + font-size: 12px; + line-height: 1.225; + display: flex; + justify-content: baseline; + color: ${Constants.system.yellow}; + opacity: 0; + margin: 26px 24px; + + @keyframes slate-animations-autosave { + 0% { + opacity: 0; + transform: translateX(0); + } + 10% { + opacity: 1; + transform: translateX(12px); + } + 90% { + opacity: 1; + transform: translateX(12px); + } + 100% { + opacity: 0; + } + } + animation: slate-animations-autosave 4000ms ease; +`; + +const STYLES_SPINNER = css` + width: 24px; + height: 24px; +`; + +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; +}; + +class CarouselSidebarData extends React.Component { + _ref = null; + + state = { + name: Strings.isEmpty(this.props.data.name) ? "" : this.props.data.name, + selected: {}, + isPublic: false, + inPublicSlates: false, + copyValue: "", + loading: false, + changingPreview: false, + unsavedChanges: false, + isEditing: false, + isDownloading: false, + subject: "", + tags: this.props.data?.tags || [], + suggestions: this.props.viewer?.tags || [], + }; + + componentDidMount = () => { + this.setState({ unsavedChanges: true }); + if (this.props.isOwner && !this.props.external) { + this.debounceInstance = Window.debounce(() => this._handleSave(), 3000); + let inPublicSlates = false; + let selected = {}; + const id = this.props.data.id; + for (let slate of this.props.slates) { + if (slate.data.objects.some((o) => o.id === id)) { + if (slate.data.public) { + inPublicSlates = true; + } + selected[slate.id] = true; + } + } + this.setState({ selected, inPublicSlates, isPublic: this.props.data.public }); + } + + this.updateSuggestions(); + }; + + componentDidUpdate = (prevProps, prevState) => { + if (!isEqual(prevState.tags, this.state.tags)) { + this.updateSuggestions(); + } + }; + + updateSuggestions = () => { + let newSuggestions = new Set([...this.state.suggestions, ...this.state.tags]); + this.setState({ suggestions: Array.from(newSuggestions) }); + }; + + _handleDarkMode = async (e) => { + Events.dispatchCustomEvent({ + name: "set-slate-theme", + detail: { darkmode: e.target.value }, + }); + }; + + _handleChange = (e) => { + if (this.props.isOwner && !this.props.external) { + this.debounceInstance(); + this.setState({ + [e.target.name]: e.target.value, + unsavedChanges: true, + subject: this._handleCapitalization(e.target.name), + }); + + if (e.target.name === "Tags") { + this.updateSuggestions(); + } + } + }; + + _handleCapitalization(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + } + + _handleSave = async () => { + let data = { name: this.state.name, tags: this.state.tags }; + this.props.onSave(data, this.props.index); + await setTimeout(() => { + this.setState({ unsavedChanges: false }); + }, 500); + await setTimeout(() => { + this.setState({ unsavedChanges: true }); + }, 4000); + + this.props.onUpdateViewer({ tags: this.state.suggestions }); + }; + + _handleToggleAutoPlay = async (e) => { + await this.props.onSave( + { settings: { ...this.props.data?.settings, autoPlay: e.target.value } }, + this.props.index + ); + }; + + _handleUpload = async (e) => { + e.persist(); + this.setState({ changingPreview: true }); + let previousCoverCid = this.props.data?.coverImage?.cid; + if (!e || !e.target) { + this.setState({ changingPreview: false }); + return; + } + let json = await UserBehaviors.uploadImage(e.target.files[0], this.props.resources, true); + if (!json) { + this.setState({ changingPreview: false }); + return; + } + + json.data.url = Strings.getCIDGatewayURL(json.data.cid); + + let updateReponse = await Actions.updateData({ + data: { + id: this.props.data.id, + coverImage: json.data, + }, + }); + + if (previousCoverCid) { + let libraryCids = this.props.viewer.library[0].children.map((obj) => obj.cid); + if (!libraryCids.includes(this.props.data.coverImage.cid)) { + await UserBehaviors.deleteFiles( + this.props.data.coverImage.cid, + this.props.data.coverImage.id, + true + ); + } + } + + Events.hasError(updateReponse); + this.setState({ changingPreview: false }); + }; + + _handleDownload = () => { + if (this.props.data.decorator.startsWith("OPTIMISTIC")) { + return; + } + + if (this.props.data.type === "application/unity") { + this.setState({ isDownloading: true }, async () => { + const response = await UserBehaviors.downloadZip(this.props.data); + this.setState({ isDownloading: false }); + + Events.hasError(response); + }); + } else { + UserBehaviors.download(this.props.data); + } + }; + + _handleCreateSlate = async () => { + if (this.props.data.decorator.startsWith("OPTIMISTIC")) { + return; + } + + this.props.onClose(); + this.props.onAction({ + type: "SIDEBAR", + value: "SIDEBAR_CREATE_SLATE", + data: { files: [this.props.data] }, + }); + }; + + _handleCopy = (copyValue, loading) => { + this.setState({ copyValue, loading }, () => { + this._ref.select(); + document.execCommand("copy"); + }); + setTimeout(() => { + this.setState({ loading: false }); + }, 1000); + }; + + _handleDelete = (cid) => { + if (!this.props.isOwner || this.props.data.decorator.startsWith("OPTIMISTIC")) return; + const message = `Are you sure you want to delete this? It will be deleted from your slates as well`; + if (!window.confirm(message)) { + return; + } + + let library = this.props.viewer.library; + library[0].children = library[0].children.filter((obj) => obj.cid !== cid); + this.props.onUpdateViewer({ library }); + + // NOTE(jim): Accepts ID as well if CID can't be found. + // Since our IDS are unique. + UserBehaviors.deleteFiles(cid, this.props.data.id); + }; + + _handleAdd = async (slate) => { + if (this.props.data.decorator.startsWith("OPTIMISTIC")) { + return; + } + + this.setState({ + selected: { ...this.state.selected, [slate.id]: !this.state.selected[slate.id] }, + }); + if (this.state.selected[slate.id]) { + await UserBehaviors.removeFromSlate({ slate, ids: [this.props.data.id] }); + } else { + await UserBehaviors.addToSlate({ + slate, + files: [this.props.data], + fromSlate: this.props.fromSlate, + }); + } + }; + + _handleEditFilename = async () => { + this.setState({ isEditing: !this.state.isEditing }, () => { + if (this.state.isEditing == false) { + this._handleSave(); + } + }); + }; + + _handleToggleVisibility = async (e) => { + if (this.props.data.decorator.startsWith("OPTIMISTIC")) { + return; + } + + const isVisible = this.state.inPublicSlates || this.state.isPublic; + let selected = this.state.selected; + if (this.state.inPublicSlates) { + const slateIds = Object.entries(this.state.selected) + .filter((entry) => entry[1]) + .map((entry) => entry[0]); + const publicSlateIds = []; + const publicSlateNames = []; + for (let slate of this.props.slates) { + if (slate.data.public && slateIds.includes(slate.id)) { + publicSlateNames.push(slate.data.name); + publicSlateIds.push(slate.id); + selected[slate.id] = false; + } + } + const message = `Making this file private will remove it from the following public slates: ${publicSlateNames.join( + ", " + )}. Do you wish to continue?`; + if (!window.confirm(message)) { + return; + } + } + let response = await Actions.toggleFilePrivacy({ + data: { + id: this.props.data.id, + public: !isVisible, + }, + }); + if (isVisible) { + this.setState({ inPublicSlates: false, isPublic: false, selected }); + } else { + this.setState({ isPublic: true }); + } + }; + + render() { + const isVisible = this.state.inPublicSlates || this.state.isPublic; + const { cid, file, name, coverImage, type, size, url, blurhash } = this.props.data; + const elements = []; + if (this.props.onClose) { + elements.push( +
+ +
+ ); + } + + elements.push( +
+
+ {this.state.isEditing && + this.props.isOwner && + !this.props.external && + !this.props.data.decorator.startsWith("OPTIMISTIC") ? ( + + + + ) : ( + {} + : this._handleEditFilename + } + > + {this.state.name} + + )} + +
+
+ {type} {Strings.bytesToSize(size)} +
+ {this.state.unsavedChanges == false ? ( +
+ + {this.state.subject} saved +
+ ) : null} +
+
+
+ {/* {this.props.isOwner ? ( +
this._handleCopy(cid, "cidCopying")}> + + + {this.state.loading === "cidCopying" ? "Copied!" : "Copy file CID"} + +
+ ) : null} +
this._handleCopy(url, "gatewayUrlCopying")}> + + + {this.state.loading === "gatewayUrlCopying" ? "Copied!" : "Copy file URL"} + +
*/} + {this.props.external ? null : ( +
+ {this.state.isDownloading ? ( + <> + + Downloading + + ) : ( + <> + + Download + + )} +
+ )} + {this.props.isOwner ? ( +
this._handleDelete(cid)}> + + Delete +
+ ) : null} +
+ {this.props.isOwner ? ( + +
+ Tags +
+
+ +
+
+ ) : null} + {this.props.external ? null : ( + +
Connected Slates
+ +
+ )} + {type && Validations.isPreviewableImage(type) ? null : ( +
+ {coverImage && ( + + + Preview image + + + + This is the preview image of your file. +
+ +
+
+
+ )} + {this.props.isOwner && ( + + + Preview image + + Add a cover image for your file. + +
+ + + Upload image + +
+
+ )} +
+ )} + {this.props.isOwner ? ( + +
+ Visibility +
+
+
{isVisible ? "Everyone" : "Link only"}
+ +
+
+ {isVisible + ? "This file is currently visible to everyone and searchable within Slate through public slates." + : "This file is currently not visible to others unless they have the link."} +
+
+ ) : null} + {this.props.data.name.endsWith(".md") ? ( + +
+ Settings +
+
+
Dark mode
+ +
+
+ ) : null} + {this.props.isOwner && type?.startsWith("video/") ? ( + +
+ Settings +
+
+
AutoPlay
+ +
+
+ {this.props?.data?.settings?.autoPlay + ? "This video will be autoplayed when opened by others." + : "This video will be paused when opened by others."} +
+
+ ) : null} + { + this._ref = c; + }} + readOnly + value={this.state.copyValue} + /> +
+ ); + + if (!elements.length) { + return null; + } + + return ( +
+ {elements} +
+ ); + } +} + +export default withTheme(CarouselSidebarData); diff --git a/components/core/DataView.js b/components/core/DataView.js index 117ba1e32..411855b43 100644 --- a/components/core/DataView.js +++ b/components/core/DataView.js @@ -805,24 +805,36 @@ export default class DataView extends React.Component { ) : null} */} -
this._handleCheckBox(e, i)}> - -
+ ) : ( +
this._handleCheckBox(e, i)}> + +
+ )} ) : null} diff --git a/components/system/components/Tag.js b/components/system/components/Tag.js index 6b14afad8..3569ab3f0 100644 --- a/components/system/components/Tag.js +++ b/components/system/components/Tag.js @@ -431,6 +431,7 @@ export const Tag = ({ dropdownStyles, onChange, handleClick, + isDisabled = false, }) => { const [value, setValue] = React.useState(""); const [open, setOpen] = React.useState(false); @@ -504,6 +505,7 @@ export const Tag = ({ onKeyPress={_handleKeyPress} onPaste={_handlePaste} onFocus={_handleFocus} + disabled={isDisabled} /> Date: Mon, 19 Apr 2021 16:15:47 +0000 Subject: [PATCH 04/12] chore: add comment --- components/core/Application.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/core/Application.js b/components/core/Application.js index 33be81333..093525a0b 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -228,6 +228,7 @@ export default class ApplicationPage extends React.Component { } if (newViewerState.library?.length) { + // NOTE(daniel): update optimistic files with upload data let oldViewerState = this.state.viewer; let oldLibrary = oldViewerState.library; @@ -380,7 +381,7 @@ export default class ApplicationPage extends React.Component { } files = await this._handleOptimisticUpload({ files }); - return; + const resolvedFiles = []; for (let i = 0; i < files.length; i++) { if (Store.checkCancelled(`${files[i].lastModified}-${files[i].name}`)) { From 5d2a8d0178053f1bec338a6d6e080fc17b230bde Mon Sep 17 00:00:00 2001 From: Akuoko Daniel Jnr Date: Wed, 21 Apr 2021 04:47:31 +0000 Subject: [PATCH 05/12] fix: handle failed state --- common/file-utilities.js | 3 ++- components/core/Application.js | 23 ++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/common/file-utilities.js b/common/file-utilities.js index 17e60e9a7..6642c1536 100644 --- a/common/file-utilities.js +++ b/common/file-utilities.js @@ -130,8 +130,9 @@ export const upload = async ({ try { return resolve(JSON.parse(event.target.response)); } catch (e) { - return resolve({ + return reject({ error: "SERVER_UPLOAD_ERROR", + failedFile: file, }); } }; diff --git a/components/core/Application.js b/components/core/Application.js index 093525a0b..bb54c74db 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -402,6 +402,27 @@ export default class ApplicationPage extends React.Component { }); } catch (e) { console.log(e); + let library = this.state.viewer.library; + library[0].children = library[0].children.filter((child) => { + if (child.id === e.failedFile.id) { + return false; + } + + return true; + }); + + this._handleUpdateViewer({ library }); + + let optimisticFiles = this.state.optimisticFiles; + let updatedOptimisticFiles = optimisticFiles.filter((item) => { + if (item.id === e.failedFile.id) { + return false; + } + + return true; + }); + + this.setState({ optimisticFiles: updatedOptimisticFiles }); } if (!response || response.error) { @@ -485,7 +506,7 @@ export default class ApplicationPage extends React.Component { this.setState({ optimisticFiles }); - let update = [...optimisticFiles, ...this.props.viewer?.library[0].children]; + let update = [...optimisticFiles, ...this.state.viewer?.library[0].children]; let library = this.props.viewer.library; library[0].children = update; this._handleUpdateViewer({ library }); From 593692ca6d9719390022c96370890919c9432249 Mon Sep 17 00:00:00 2001 From: Akuoko Daniel Jnr Date: Wed, 21 Apr 2021 05:22:19 +0000 Subject: [PATCH 06/12] fix: preview for pdf files --- common/file-utilities.js | 4 +--- components/core/Application.js | 13 +++++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/common/file-utilities.js b/common/file-utilities.js index 6642c1536..2abd5c137 100644 --- a/common/file-utilities.js +++ b/common/file-utilities.js @@ -73,10 +73,8 @@ export const upload = async ({ }); //TODO(martina): figure out how to cancel an await if upload has been cancelled formData.append(fileId, converted); - } else if (file.type.startsWith("image")) { - formData.append(fileId, file); } else { - formData.append("data", file); + formData.append(fileId, file); } if (Store.checkCancelled(`${file.lastModified}-${file.name}`)) { diff --git a/components/core/Application.js b/components/core/Application.js index bb54c74db..dccf0d222 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -484,9 +484,9 @@ export default class ApplicationPage extends React.Component { _handleOptimisticUpload = async ({ files }) => { let optimisticFiles = []; for (let i = 0; i < files.length; i++) { - if (!files[i].type.startsWith("image")) { - continue; - } + // if (!files[i].type.startsWith("image") || !files[i].type.startsWith("video")) { + // continue; + // } let id = `data-${uuid()}`; let dataURL = await this._handleLoadDataURL(files[i]); @@ -495,7 +495,7 @@ export default class ApplicationPage extends React.Component { name: files[i].name, type: files[i].type, size: files[i].size, - decorator: "OPTIMISTIC-IMAGE-FILE", + decorator: "OPTIMISTIC-FILE", dataURL, }; @@ -516,6 +516,11 @@ export default class ApplicationPage extends React.Component { _handleLoadDataURL = (file) => new Promise((resolve, reject) => { + if (file.type.startsWith("application/pdf")) { + resolve(URL.createObjectURL(file)); + return; + } + const reader = new FileReader(); reader.onload = () => { resolve(reader.result); From 9e4de33805e831afddd8b7d6df940a6a7740aac6 Mon Sep 17 00:00:00 2001 From: Akuoko Daniel Jnr Date: Wed, 21 Apr 2021 05:46:06 +0000 Subject: [PATCH 07/12] fix: show spinner in table view --- components/core/DataView.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/components/core/DataView.js b/components/core/DataView.js index 411855b43..e38458b5d 100644 --- a/components/core/DataView.js +++ b/components/core/DataView.js @@ -910,17 +910,26 @@ export default class DataView extends React.Component { ...each, checkbox: (
this._handleCheckBox(e, index)}> - 0 || this.state.hover === index ? "100%" : "0%", - }} - /> + {each.decorator.startsWith("OPTIMISTIC") ? ( + + ) : ( + 0 || this.state.hover === index ? "100%" : "0%", + }} + /> + )}
), name: ( From b43799b80cbc411ade181f2586ae83b1fee28ed9 Mon Sep 17 00:00:00 2001 From: Akuoko Daniel Jnr Date: Wed, 21 Apr 2021 19:15:39 +0000 Subject: [PATCH 08/12] fix: update viewer func --- components/core/Application.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/core/Application.js b/components/core/Application.js index dccf0d222..7610e2dd5 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -380,7 +380,7 @@ export default class ApplicationPage extends React.Component { return; } - files = await this._handleOptimisticUpload({ files }); + files = await this._handleOptimisticUpload({ files, slate }); const resolvedFiles = []; for (let i = 0; i < files.length; i++) { @@ -507,7 +507,7 @@ export default class ApplicationPage extends React.Component { this.setState({ optimisticFiles }); let update = [...optimisticFiles, ...this.state.viewer?.library[0].children]; - let library = this.props.viewer.library; + let library = this.props.viewer?.library; library[0].children = update; this._handleUpdateViewer({ library }); From eeec7b5fb52a377697d7532ded6b904f77e982a3 Mon Sep 17 00:00:00 2001 From: Akuoko Daniel Jnr Date: Mon, 26 Apr 2021 11:48:27 +0000 Subject: [PATCH 09/12] fix: rebase conflicts --- components/core/Application.js | 53 +++++----------------- components/core/DataView.js | 4 +- components/core/SlateMediaObject.js | 2 +- components/core/SlateMediaObjectPreview.js | 8 ++-- 4 files changed, 18 insertions(+), 49 deletions(-) diff --git a/components/core/Application.js b/components/core/Application.js index 7610e2dd5..3ace810fb 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -227,36 +227,6 @@ export default class ApplicationPage extends React.Component { } } - if (newViewerState.library?.length) { - // NOTE(daniel): update optimistic files with upload data - let oldViewerState = this.state.viewer; - let oldLibrary = oldViewerState.library; - - let update = newViewerState.library[0].children.map((child) => { - let optimisticFileIndex = oldLibrary[0].children.findIndex( - (item) => item.id === child.id && item.decorator.startsWith("OPTMISTIC") - ); - if (optimisticFileIndex > -1) { - return { ...oldLibrary[0].children[optimisticFileIndex], ...child }; - } - - return child; - }); - - oldViewerState.library[0].children = update; - - this.setState( - { - viewer: { ...oldViewerState, ...newViewerState, type: "VIEWER" }, - }, - () => { - if (callback) { - callback(); - } - } - ); - } - this.setState( { viewer: { ...this.state.viewer, ...newViewerState }, @@ -380,7 +350,7 @@ export default class ApplicationPage extends React.Component { return; } - files = await this._handleOptimisticUpload({ files, slate }); + files = await this._handleOptimisticUpload({ files }); const resolvedFiles = []; for (let i = 0; i < files.length; i++) { @@ -403,7 +373,7 @@ export default class ApplicationPage extends React.Component { } catch (e) { console.log(e); let library = this.state.viewer.library; - library[0].children = library[0].children.filter((child) => { + library = library.filter((child) => { if (child.id === e.failedFile.id) { return false; } @@ -484,17 +454,16 @@ export default class ApplicationPage extends React.Component { _handleOptimisticUpload = async ({ files }) => { let optimisticFiles = []; for (let i = 0; i < files.length; i++) { - // if (!files[i].type.startsWith("image") || !files[i].type.startsWith("video")) { - // continue; - // } - - let id = `data-${uuid()}`; + let id = uuid(); let dataURL = await this._handleLoadDataURL(files[i]); let data = { id, - name: files[i].name, - type: files[i].type, - size: files[i].size, + filename: files[i].name, + data: { + name: files[i].name, + type: files[i].type, + size: files[i].size, + }, decorator: "OPTIMISTIC-FILE", dataURL, }; @@ -506,9 +475,9 @@ export default class ApplicationPage extends React.Component { this.setState({ optimisticFiles }); - let update = [...optimisticFiles, ...this.state.viewer?.library[0].children]; + let update = [...optimisticFiles, ...this.state.viewer?.library]; let library = this.props.viewer?.library; - library[0].children = update; + library = update; this._handleUpdateViewer({ library }); return files; diff --git a/components/core/DataView.js b/components/core/DataView.js index e38458b5d..51bd5b783 100644 --- a/components/core/DataView.js +++ b/components/core/DataView.js @@ -805,7 +805,7 @@ export default class DataView extends React.Component { ) : null} */} - {each.decorator.startsWith("OPTIMISTIC") ? ( + {each.decorator?.startsWith("OPTIMISTIC") ? ( this._handleCheckBox(e, index)}> - {each.decorator.startsWith("OPTIMISTIC") ? ( + {each.decorator?.startsWith("OPTIMISTIC") ? ( Date: Mon, 26 Apr 2021 11:55:27 +0000 Subject: [PATCH 10/12] fix: disable carouselsidebar action for optimistic files --- components/core/CarouselSidebarData.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/core/CarouselSidebarData.js b/components/core/CarouselSidebarData.js index 215e2dc80..e45671e05 100644 --- a/components/core/CarouselSidebarData.js +++ b/components/core/CarouselSidebarData.js @@ -385,7 +385,7 @@ class CarouselSidebarData extends React.Component { }; _handleDownload = () => { - if (this.props.data.decorator.startsWith("OPTIMISTIC")) { + if (this.props.data.decorator?.startsWith("OPTIMISTIC")) { return; } @@ -402,7 +402,7 @@ class CarouselSidebarData extends React.Component { }; _handleCreateSlate = async () => { - if (this.props.data.decorator.startsWith("OPTIMISTIC")) { + if (this.props.data.decorator?.startsWith("OPTIMISTIC")) { return; } @@ -441,7 +441,7 @@ class CarouselSidebarData extends React.Component { }; _handleAdd = async (slate) => { - if (this.props.data.decorator.startsWith("OPTIMISTIC")) { + if (this.props.data.decorator?.startsWith("OPTIMISTIC")) { return; } @@ -468,7 +468,7 @@ class CarouselSidebarData extends React.Component { }; _handleToggleVisibility = async (e) => { - if (this.props.data.decorator.startsWith("OPTIMISTIC")) { + if (this.props.data.decorator?.startsWith("OPTIMISTIC")) { return; } @@ -525,7 +525,7 @@ class CarouselSidebarData extends React.Component { {this.state.isEditing && this.props.isOwner && !this.props.external && - !this.props.data.decorator.startsWith("OPTIMISTIC") ? ( + !this.props.data.decorator?.startsWith("OPTIMISTIC") ? ( {} : this._handleEditFilename } @@ -619,7 +619,7 @@ class CarouselSidebarData extends React.Component { inputStyles={{ padding: "16px" }} dropdownStyles={{ top: "50px" }} onChange={this._handleChange} - isDisabled={this.props.data.decorator.startsWith("OPTIMISTIC") && true} + isDisabled={this.props.data.decorator?.startsWith("OPTIMISTIC") && true} /> From bc0514d0f79fd6c572879b8a61d4a91a2ae98224 Mon Sep 17 00:00:00 2001 From: Akuoko Daniel Jnr Date: Mon, 26 Apr 2021 12:58:07 +0000 Subject: [PATCH 11/12] feat: add optimistic upload to collection page --- components/core/Application.js | 55 ++++++++++++++++++++++++------ components/core/CarouselSidebar.js | 38 ++++++++++++++++++--- scenes/SceneSlate.js | 2 +- 3 files changed, 80 insertions(+), 15 deletions(-) diff --git a/components/core/Application.js b/components/core/Application.js index 3ace810fb..28ce76e2f 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -350,7 +350,7 @@ export default class ApplicationPage extends React.Component { return; } - files = await this._handleOptimisticUpload({ files }); + files = await this._handleOptimisticUpload({ files, slate }); const resolvedFiles = []; for (let i = 0; i < files.length; i++) { @@ -372,27 +372,47 @@ export default class ApplicationPage extends React.Component { }); } catch (e) { console.log(e); - let library = this.state.viewer.library; - library = library.filter((child) => { - if (child.id === e.failedFile.id) { + + let optimisticFiles = this.state.optimisticFiles; + let updatedOptimisticFiles = optimisticFiles.filter((item) => { + if (item.id === e.failedFile.id) { return false; } return true; }); - this._handleUpdateViewer({ library }); + this.setState({ optimisticFiles: updatedOptimisticFiles }); - let optimisticFiles = this.state.optimisticFiles; - let updatedOptimisticFiles = optimisticFiles.filter((item) => { - if (item.id === e.failedFile.id) { + if (slate && slate.id) { + let slates = this.state.viewer.slates; + for (let item of slates) { + if (item.id === slate.id) { + item.objects = item.objects.filter((child) => { + if (child.id === e.failedFile.id) { + return false; + } + + return true; + }); + } + } + + this._handleUpdateViewer({ slates }); + + return; + } + + let library = this.state.viewer.library; + library = library.filter((child) => { + if (child.id === e.failedFile.id) { return false; } return true; }); - this.setState({ optimisticFiles: updatedOptimisticFiles }); + this._handleUpdateViewer({ library }); } if (!response || response.error) { @@ -451,8 +471,9 @@ export default class ApplicationPage extends React.Component { this._handleRegisterLoadingFinished({ keys }); }; - _handleOptimisticUpload = async ({ files }) => { + _handleOptimisticUpload = async ({ files, slate }) => { let optimisticFiles = []; + for (let i = 0; i < files.length; i++) { let id = uuid(); let dataURL = await this._handleLoadDataURL(files[i]); @@ -475,6 +496,20 @@ export default class ApplicationPage extends React.Component { this.setState({ optimisticFiles }); + if (slate && slate.id) { + const slates = this.state.viewer.slates; + + for (let item of slates) { + if (item.id === slate.id) { + item.objects = [...optimisticFiles, ...item.objects]; + break; + } + } + + this._handleUpdateViewer({ slates }); + return files; + } + let update = [...optimisticFiles, ...this.state.viewer?.library]; let library = this.props.viewer?.library; library = update; diff --git a/components/core/CarouselSidebar.js b/components/core/CarouselSidebar.js index 01d4f24e0..10db1ac34 100644 --- a/components/core/CarouselSidebar.js +++ b/components/core/CarouselSidebar.js @@ -428,6 +428,10 @@ class CarouselSidebar extends React.Component { }; _handleDownload = () => { + if (this.props.data.decorator?.startsWith("OPTIMISTIC")) { + return; + } + if (this.props.data.data.type === "application/unity") { this.setState({ isDownloading: true }, async () => { const response = await UserBehaviors.downloadZip(this.props.data); @@ -440,6 +444,10 @@ class CarouselSidebar extends React.Component { }; _handleCreateSlate = async () => { + if (this.props.data.decorator?.startsWith("OPTIMISTIC")) { + return; + } + if (this.props.external) return; this.props.onClose(); this.props.onAction({ @@ -450,7 +458,12 @@ class CarouselSidebar extends React.Component { }; _handleDelete = () => { - if (this.props.external || !this.props.isOwner) return; + if ( + this.props.external || + !this.props.isOwner || + this.props.data.decorator.startsWith("OPTIMISTIC") + ) + return; const message = "Are you sure you want to delete this? It will be removed from your collections as well"; if (!window.confirm(message)) { @@ -477,6 +490,10 @@ class CarouselSidebar extends React.Component { }; _handleAdd = async (slate) => { + if (this.props.data.decorator?.startsWith("OPTIMISTIC")) { + return; + } + let inPublicSlates = this.state.inPublicSlates; if (this.state.selected[slate.id]) { if (slate.isPublic) { @@ -521,6 +538,10 @@ class CarouselSidebar extends React.Component { }; _handleToggleVisibility = async (e) => { + if (this.props.data.decorator?.startsWith("OPTIMISTIC")) { + return; + } + if (this.props.external || !this.props.isOwner) return; const isVisible = this.state.isPublic || this.state.inPublicSlates > 0; let selected = cloneDeep(this.state.selected); @@ -574,7 +595,7 @@ class CarouselSidebar extends React.Component { const isUnityGame = type === "application/unity"; const elements = []; - if (editingAllowed && !isUnityGame) { + if (editingAllowed && !isUnityGame && !file.decorator?.startsWith("OPTIMISTIC")) { elements.push(
); - if (!this.props.external && (!this.props.isOwner || this.props.isRepost)) { + if ( + !this.props.external && + (!this.props.isOwner || this.props.isRepost) && + !file.decorator?.startsWith("OPTIMISTIC") + ) { actions.push(
this._handleSaveCopy(file)}> @@ -734,7 +759,12 @@ class CarouselSidebar extends React.Component { ); } - if (this.props.carouselType === "SLATE" && !this.props.external && this.props.isOwner) { + if ( + this.props.carouselType === "SLATE" && + !this.props.external && + this.props.isOwner && + !file.decorator?.startsWith("OPTIMISTIC") + ) { actions.push(
diff --git a/scenes/SceneSlate.js b/scenes/SceneSlate.js index 240942aea..18ad411aa 100644 --- a/scenes/SceneSlate.js +++ b/scenes/SceneSlate.js @@ -347,7 +347,7 @@ class SlatePage extends React.Component { const isPublic = this.props.current.isPublic; const isOwner = this.props.current.ownerId === this.props.viewer.id; const tags = data.tags; - + console.log(objects); let actions = isOwner ? ( From 23cb5a4d3b28d257255a35131477503a15b540a0 Mon Sep 17 00:00:00 2001 From: Akuoko Daniel Jnr Date: Mon, 26 Apr 2021 13:56:40 +0000 Subject: [PATCH 12/12] fix: optimistic upload on collection page --- components/core/Application.js | 2 +- scenes/SceneSlate.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/core/Application.js b/components/core/Application.js index 28ce76e2f..8d64567c6 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -501,7 +501,7 @@ export default class ApplicationPage extends React.Component { for (let item of slates) { if (item.id === slate.id) { - item.objects = [...optimisticFiles, ...item.objects]; + item.objects = [...item.objects, ...optimisticFiles]; break; } } diff --git a/scenes/SceneSlate.js b/scenes/SceneSlate.js index 18ad411aa..240942aea 100644 --- a/scenes/SceneSlate.js +++ b/scenes/SceneSlate.js @@ -347,7 +347,7 @@ class SlatePage extends React.Component { const isPublic = this.props.current.isPublic; const isOwner = this.props.current.ownerId === this.props.viewer.id; const tags = data.tags; - console.log(objects); + let actions = isOwner ? (