diff --git a/common/file-utilities.js b/common/file-utilities.js index 50f7d55a8..2abd5c137 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,9 +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 { - formData.append("data", file); + formData.append(fileId, file); } if (Store.checkCancelled(`${file.lastModified}-${file.name}`)) { @@ -121,8 +128,9 @@ export const upload = async ({ file, context, bucketName, routes, excludeFromLib 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 b3c60706d..8d64567c6 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,7 @@ export default class ApplicationPage extends React.Component { return; } } + this.setState( { viewer: { ...this.state.viewer, ...newViewerState }, @@ -347,6 +350,8 @@ export default class ApplicationPage extends React.Component { return; } + files = await this._handleOptimisticUpload({ files, slate }); + const resolvedFiles = []; for (let i = 0; i < files.length; i++) { if (Store.checkCancelled(`${files[i].lastModified}-${files[i].name}`)) { @@ -360,12 +365,54 @@ 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, }); } catch (e) { console.log(e); + + 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 (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._handleUpdateViewer({ library }); } if (!response || response.error) { @@ -424,6 +471,99 @@ export default class ApplicationPage extends React.Component { this._handleRegisterLoadingFinished({ keys }); }; + _handleOptimisticUpload = async ({ files, slate }) => { + let optimisticFiles = []; + + for (let i = 0; i < files.length; i++) { + let id = uuid(); + let dataURL = await this._handleLoadDataURL(files[i]); + let data = { + id, + filename: files[i].name, + data: { + name: files[i].name, + type: files[i].type, + size: files[i].size, + }, + decorator: "OPTIMISTIC-FILE", + dataURL, + }; + + optimisticFiles.push(data); + + files[i].id = id; + } + + 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 = [...item.objects, ...optimisticFiles]; + break; + } + } + + this._handleUpdateViewer({ slates }); + return files; + } + + let update = [...optimisticFiles, ...this.state.viewer?.library]; + let library = this.props.viewer?.library; + library = update; + this._handleUpdateViewer({ library }); + + return files; + }; + + _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); + }; + + reader.onerror = () => { + reject({ error: true }); + reader.abort(); + }; + + 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({ 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/components/core/CarouselSidebarData.js b/components/core/CarouselSidebarData.js new file mode 100644 index 000000000..e45671e05 --- /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..51bd5b783 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} @@ -898,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: ( diff --git a/components/core/SlateMediaObject.js b/components/core/SlateMediaObject.js index 74a7e9fdc..591b6e9d4 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 = file?.dataURL || Strings.getURLfromCID(file.cid); 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..4e71fbfef 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 = this.props.file?.dataURL || Strings.getURLfromCID(this.props.file.cid); } else if (coverImage) { - url = Strings.getURLfromCID(coverImage.cid); + url = this.props.file?.dataURL || Strings.getURLfromCID(coverImage.cid); } 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 = this.props.file?.dataURL || Strings.getURLfromCID(this.props.file.cid); } else if (coverImage) { - url = Strings.getURLfromCID(coverImage.cid); + url = this.props.file?.dataURL || Strings.getURLfromCID(coverImage.cid); } 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 ( 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} />