diff --git a/package.json b/package.json index 074981a42a5..f8bdfbd280d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "start:prod": "NODE_ENV=production node build/server.js" }, "dependencies": { - "@ecency/render-helper": "^2.2.24", + "@ecency/render-helper": "^2.2.25", "@ecency/render-helper-amp": "^1.1.0", "@firebase/analytics": "^0.8.0", "@firebase/app": "^0.7.28", diff --git a/src/common/api/operations.ts b/src/common/api/operations.ts index 922e861eca3..8c839c01aa2 100644 --- a/src/common/api/operations.ts +++ b/src/common/api/operations.ts @@ -47,6 +47,7 @@ export interface MetaData { export interface BeneficiaryRoute { account: string; weight: number; + src?: string; } export interface CommentOptions { diff --git a/src/common/api/threespeak/mutations.ts b/src/common/api/threespeak/mutations.ts index 6fe0d4e0a99..6d5a4eadd59 100644 --- a/src/common/api/threespeak/mutations.ts +++ b/src/common/api/threespeak/mutations.ts @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { uploadFile, uploadVideoInfo } from "./api"; import { QueryIdentifiers } from "../../core"; -import { ThreeSpeakVideo } from "./types"; +import { useThreeSpeakVideo } from "./queries"; export function useThreeSpeakVideoUpload() { const [completedByType, setCompletedByType] = useState>({}); @@ -28,6 +28,7 @@ export function useThreeSpeakVideoUpload() { export function useUploadVideoInfo() { const queryClient = useQueryClient(); + const { data, refetch } = useThreeSpeakVideo("all"); return useMutation( ["threeSpeakVideoUploadInfo"], @@ -55,14 +56,15 @@ export function useUploadVideoInfo() { }, { - onSuccess: (response) => { + onSuccess: async (response) => { if (response) { - const next = [ - response, - ...(queryClient.getQueryData([ - QueryIdentifiers.THREE_SPEAK_VIDEO_LIST - ]) ?? []) - ]; + let current = data; + if (current.length === 0) { + const response = await refetch(); + current = response.data ?? []; + } + + const next = [response, ...current]; queryClient.setQueryData([QueryIdentifiers.THREE_SPEAK_VIDEO_LIST], next); queryClient.setQueryData([QueryIdentifiers.THREE_SPEAK_VIDEO_LIST_FILTERED, "all"], next); } diff --git a/src/common/api/threespeak/queries.ts b/src/common/api/threespeak/queries.ts index 5aa39f0f659..dbcb4fa3053 100644 --- a/src/common/api/threespeak/queries.ts +++ b/src/common/api/threespeak/queries.ts @@ -16,7 +16,7 @@ export function useThreeSpeakVideo( const queryClient = useQueryClient(); const apiQuery = useQuery( - [QueryIdentifiers.THREE_SPEAK_VIDEO_LIST], + [QueryIdentifiers.THREE_SPEAK_VIDEO_LIST, activeUser?.username ?? ""], async () => { try { return await getAllVideoStatuses(activeUser!.username); diff --git a/src/common/components/beneficiary-editor/index.spec.tsx b/src/common/components/beneficiary-editor/index.spec.tsx index ab1e609cfb6..e6615b98bd0 100644 --- a/src/common/components/beneficiary-editor/index.spec.tsx +++ b/src/common/components/beneficiary-editor/index.spec.tsx @@ -5,6 +5,7 @@ import BeneficiaryEditorDialog, { DialogBody } from "./index"; import TestRenderer from "react-test-renderer"; const defProps = { + body: "", list: [ { account: "foo", diff --git a/src/common/components/beneficiary-editor/index.tsx b/src/common/components/beneficiary-editor/index.tsx index 8c9e82c6038..d1f39fe6dc2 100644 --- a/src/common/components/beneficiary-editor/index.tsx +++ b/src/common/components/beneficiary-editor/index.tsx @@ -1,6 +1,6 @@ import React, { Component } from "react"; -import { Button, Modal, Form, InputGroup, FormControl } from "react-bootstrap"; +import { Button, Form, FormControl, InputGroup, Modal } from "react-bootstrap"; import BaseComponent from "../base"; import { error } from "../feedback"; @@ -11,11 +11,14 @@ import { getAccount } from "../../api/hive"; import { _t } from "../../i18n"; -import { plusSvg, deleteForeverSvg, accountMultipleSvg } from "../../img/svg"; +import { accountMultipleSvg, deleteForeverSvg, plusSvg } from "../../img/svg"; import { handleInvalid, handleOnInput } from "../../util/input-util"; import "./_index.scss"; +const THREE_SPEAK_VIDEO_PATTERN = /\[!\[]\(https:\/\/ipfs-3speak.*\)\]\(https:\/\/3speak\.tv.*\)/g; + interface Props { + body: string; author?: string; list: BeneficiaryRoute[]; onAdd: (item: BeneficiaryRoute) => void; @@ -162,16 +165,21 @@ export class DialogBody extends BaseComponent { {`@${x.account}`} {`${x.weight / 100}%`} - + {!!this.props.body.match(THREE_SPEAK_VIDEO_PATTERN) && + x.src === "ENCODER_PAY" ? ( + <> + ) : ( + + )} ); diff --git a/src/common/components/decks/deck-threads-form/_index.scss b/src/common/components/decks/deck-threads-form/_index.scss index 35986589092..8dabccb41d2 100644 --- a/src/common/components/decks/deck-threads-form/_index.scss +++ b/src/common/components/decks/deck-threads-form/_index.scss @@ -56,6 +56,13 @@ } } + &:not(.inline) { + .deck-toolbar-threads-form-content { + overflow-x: hidden; + overflow-y: auto; + } + } + &::-webkit-scrollbar { display: none; } @@ -192,6 +199,27 @@ background-color: rgba($primary, 0.15); } } + + &-video-picker { + position: relative; + + &::before { + content: ''; + position: absolute; + top: 0.5rem; + right: 1rem; + width: 0.25rem; + height: 0.25rem; + opacity: 0; + background-color: $danger; + border-radius: 50%; + transition: 0.3s; + } + + &:hover::before { + opacity: 1; + } + } } .deck-threads-form-selected-image { @@ -206,8 +234,8 @@ .remove { position: absolute; - top: 0; - right: 0; + top: 0.5rem; + right: 0.5rem; background-color: $white; display: flex; align-items: center; @@ -223,6 +251,20 @@ height: 1rem; } } + + .type { + position: absolute; + background-color: $dark-sky-blue; + color: $white; + rotate: -45deg; + padding: 0.125rem; + text-align: center; + width: 100px; + top: 0.75rem; + left: -1.75rem; + font-size: 0.675rem; + text-transform: uppercase; + } } .dropdown { diff --git a/src/common/components/decks/deck-threads-form/deck-threads-form-control.tsx b/src/common/components/decks/deck-threads-form/deck-threads-form-control.tsx index 4e744dd7954..8e8fc71491f 100644 --- a/src/common/components/decks/deck-threads-form/deck-threads-form-control.tsx +++ b/src/common/components/decks/deck-threads-form/deck-threads-form-control.tsx @@ -7,9 +7,11 @@ import { closeSvg } from "../../../img/svg"; interface Props { text: string; setText: (v: string) => void; + video: string | null; selectedImage: string | null; setSelectedImage: (url: string | null) => void; onAddImage: (url: string, name: string) => void; + onAddVideo: (value: string | null) => void; placeholder?: string; onTextareaFocus: () => void; } @@ -21,7 +23,9 @@ export const DeckThreadsFormControl = ({ selectedImage, setSelectedImage, placeholder, - onTextareaFocus + onTextareaFocus, + onAddVideo, + video }: Props) => { return ( <> @@ -37,16 +41,34 @@ export const DeckThreadsFormControl = ({
{text.length}/255
{selectedImage && ( -
+
+
image
setSelectedImage(null)}> {closeSvg}
)} + {video && ( +
+
video
+ \[!\[](.*)].*<\/center>/g) + .next() + .value[1].replace("(", "") + .replace(")", "")} + alt="" + /> +
onAddVideo(null)}> + {closeSvg} +
+
+ )} setText(`${text}${v}`)} + onAddVideo={onAddVideo} />
diff --git a/src/common/components/decks/deck-threads-form/deck-threads-form-toolbar-video-picker.tsx b/src/common/components/decks/deck-threads-form/deck-threads-form-toolbar-video-picker.tsx new file mode 100644 index 00000000000..a3304476b14 --- /dev/null +++ b/src/common/components/decks/deck-threads-form/deck-threads-form-toolbar-video-picker.tsx @@ -0,0 +1,49 @@ +import Tooltip from "../../tooltip"; +import { _t } from "../../../i18n"; +import { PopperDropdown } from "../../popper-dropdown"; +import { videoSvg } from "../../../img/svg"; +import React, { useState } from "react"; +import { useMappedStore } from "../../../store/use-mapped-store"; +import { VideoUpload } from "../../video-upload-threespeak"; +import VideoGallery from "../../video-gallery"; + +interface Props { + onSelect: (video: string) => void; +} + +export function DeckThreadsFormToolbarVideoPicker({ onSelect }: Props) { + const { activeUser, global } = useMappedStore(); + + const [showUpload, setShowUpload] = useState(false); + const [showGallery, setShowGallery] = useState(false); + + return ( +
+ {activeUser && ( + + +
+
setShowUpload(true)}> + {_t("video-upload.upload-video")} +
+ {global.usePrivate && ( +
setShowGallery(true)}> + {_t("video-upload.video-gallery")} +
+ )} +
+
+
+ )} + + { + onSelect(v); + }} + /> +
+ ); +} diff --git a/src/common/components/decks/deck-threads-form/deck-threads-form-toolbar.tsx b/src/common/components/decks/deck-threads-form/deck-threads-form-toolbar.tsx index 9a095e986c2..cfec7836372 100644 --- a/src/common/components/decks/deck-threads-form/deck-threads-form-toolbar.tsx +++ b/src/common/components/decks/deck-threads-form/deck-threads-form-toolbar.tsx @@ -5,13 +5,15 @@ import { DeckThreadsFormEmojiPicker } from "./deck-threads-form-emoji-picker"; interface Props { onAddImage: (url: string, name: string) => void; onEmojiPick: (value: string) => void; + onAddVideo: (value: string) => void; } -export const DeckThreadsFormToolbar = ({ onAddImage, onEmojiPick }: Props) => { +export const DeckThreadsFormToolbar = ({ onAddImage, onEmojiPick, onAddVideo }: Props) => { return (
+ {/**/}
); }; diff --git a/src/common/components/decks/deck-threads-form/index.tsx b/src/common/components/decks/deck-threads-form/index.tsx index dc32f9f8ea5..c1c9386a270 100644 --- a/src/common/components/decks/deck-threads-form/index.tsx +++ b/src/common/components/decks/deck-threads-form/index.tsx @@ -17,6 +17,7 @@ import { DeckThreadsCreatedRecently } from "./deck-threads-created-recently"; import { IdentifiableEntry, ThreadItemEntry } from "../columns/deck-threads-manager"; import useClickAway from "react-use/lib/useClickAway"; import { classNameObject } from "../../../helper/class-name-object"; +import usePrevious from "react-use/lib/usePrevious"; interface Props { className?: string; @@ -51,11 +52,13 @@ export const DeckThreadsForm = ({ {} ); const [persistedForm, setPersistedForm] = useLocalStorage>(PREFIX + "_dtf_f"); + const previousPersistedForm = usePrevious(persistedForm); const [threadHost, setThreadHost] = useState("ecency.waves"); const [text, setText] = useState(""); const [image, setImage] = useState(null); const [imageName, setImageName] = useState(null); + const [video, setVideo] = useState(null); const [disabled, setDisabled] = useState(true); const [loading, setLoading] = useState(false); @@ -90,16 +93,25 @@ export const DeckThreadsForm = ({ setText(text ? text : persistedForm?.text ?? ""); setImage(image ? image : persistedForm?.image); setImageName(imageName ? imageName : persistedForm?.imageName); + setVideo(video ? video : persistedForm?.video); } }, [persistedForm]); useEffect(() => { - if (persistable) { + if ( + persistable && + (persistedForm?.threadHost !== threadHost || + persistedForm?.text !== text || + persistedForm?.image !== image || + persistedForm?.imageName !== imageName || + persistedForm?.video !== video) + ) { setPersistedForm({ threadHost, text, image, - imageName + imageName, + video }); } }, [threadHost, text, image, imageName]); @@ -122,6 +134,10 @@ export const DeckThreadsForm = ({ content = `${content}
![${imageName ?? ""}](${image})`; } + if (video) { + content = `${content}
${video}`; + } + // Push to draft built content with attachments if (text!!.length > 255) { setLocalDraft({ @@ -213,6 +229,7 @@ export const DeckThreadsForm = ({ )} setFocused(true)} diff --git a/src/common/components/editor-toolbar/index.tsx b/src/common/components/editor-toolbar/index.tsx index 3f76944f03e..cdf91fa75cb 100644 --- a/src/common/components/editor-toolbar/index.tsx +++ b/src/common/components/editor-toolbar/index.tsx @@ -36,9 +36,11 @@ import { gridSvg, imageSvg, linkSvg, - textShortSvg + textShortSvg, + videoSvg } from "../../img/svg"; import { VideoUpload } from "../video-upload-threespeak"; +import VideoGallery from "../video-gallery"; interface Props { global: Global; @@ -59,6 +61,8 @@ interface State { link: boolean; mobileImage: boolean; shGif: boolean; + showVideoUpload: boolean; + showVideoGallery: boolean; } export const detectEvent = (eventType: string) => { @@ -78,7 +82,9 @@ export class EditorToolbar extends Component { image: false, link: false, mobileImage: false, - shGif: false + shGif: false, + showVideoUpload: false, + showVideoGallery: false }; holder = React.createRef(); @@ -189,6 +195,7 @@ export class EditorToolbar extends Component { if (el) { insertOrReplace(el, before, after); } + return this.getTargetEl(); }; replaceText = (find: string, rep: string) => { @@ -527,6 +534,37 @@ export class EditorToolbar extends Component {
this.setState({ showVideoUpload: v })} + setShowGallery={(v) => this.setState({ showVideoGallery: v })} + > + {videoSvg} + {activeUser && ( +
+
this.setState({ showVideoUpload: true })} + > + {_t("video-upload.upload-video")} +
+ {global.usePrivate && ( +
) => { + e.stopPropagation(); + this.setState({ showVideoGallery: true }); + }} + > + {_t("video-upload.video-gallery")} +
+ )} +
+ )} +
+ this.setState({ showVideoGallery: v })} insertText={this.insertText} setVideoEncoderBeneficiary={this.props.setVideoEncoderBeneficiary} toggleNsfwC={this.props.toggleNsfwC} diff --git a/src/common/components/popper-dropdown/index.tsx b/src/common/components/popper-dropdown/index.tsx index 2790d89b36e..d9aabe00f92 100644 --- a/src/common/components/popper-dropdown/index.tsx +++ b/src/common/components/popper-dropdown/index.tsx @@ -10,9 +10,10 @@ interface Props { toggle: JSX.Element; children: JSX.Element; options?: Parameters[2]; + hideOnClick?: boolean; } -export const PopperDropdown = ({ children, toggle, options }: Props) => { +export const PopperDropdown = ({ children, toggle, options, hideOnClick = false }: Props) => { const isMounted = useMounted(); const [isShow, setIsShow] = useState(false); @@ -47,7 +48,9 @@ export const PopperDropdown = ({ children, toggle, options }: Props) => { {...attributes.popper} ref={setPopperElement} > -
{children}
+
(hideOnClick ? hide() : null)}> + {children} +
), document.querySelector("#popper-container")!! diff --git a/src/common/components/video-gallery/index.tsx b/src/common/components/video-gallery/index.tsx index 9adbda71184..592e339feea 100644 --- a/src/common/components/video-gallery/index.tsx +++ b/src/common/components/video-gallery/index.tsx @@ -12,9 +12,10 @@ import { useMappedStore } from "../../store/use-mapped-store"; interface Props { showGallery: boolean; setShowGallery: React.Dispatch>; - insertText: (before: string, after?: string) => void; + insertText: (before: string, after?: string) => any; setVideoEncoderBeneficiary?: (video: any) => void; toggleNsfwC?: () => void; + preFilter?: string; } const VideoGallery = ({ @@ -22,16 +23,19 @@ const VideoGallery = ({ setShowGallery, insertText, setVideoEncoderBeneficiary, - toggleNsfwC + toggleNsfwC, + preFilter }: Props) => { const { activeUser } = useMappedStore(); const [label, setLabel] = useState("All"); - const [filterStatus, setFilterStatus] = useState("all"); + const [filterStatus, setFilterStatus] = useState( + preFilter ?? "all" + ); const { data: items, isFetching, refresh } = useThreeSpeakVideo(filterStatus, showGallery); useEffect(() => { - setFilterStatus("all"); + setFilterStatus(preFilter ?? "all"); }, [activeUser]); return ( @@ -48,61 +52,65 @@ const VideoGallery = ({
- {_t("video-gallery.all")}, - onClick: () => { - setLabel(_t("video-gallery.all")); - setFilterStatus("all"); - } - }, - { - label: {_t("video-gallery.published")}, - onClick: () => { - setLabel(_t("video-gallery.published")); - setFilterStatus("published"); - } - }, - { - label: {_t("video-gallery.encoding")}, - onClick: () => { - const encoding = "encoding_ipfs" || "encoding_preparing"; - setLabel(_t("video-gallery.encoding")); - setFilterStatus(encoding); - } - }, - { - label: {_t("video-gallery.encoded")}, - onClick: () => { - setLabel(_t("video-gallery.encoded")); - setFilterStatus("publish_manual"); - } - }, - { - label: {_t("video-gallery.failed")}, - onClick: () => { - setLabel(_t("video-gallery.failed")); - setFilterStatus("encoding_failed"); - } - }, - { - label: {_t("video-gallery.status-deleted")}, - onClick: () => { - setLabel(_t("video-gallery.status-deleted")); - setFilterStatus("deleted"); + {!preFilter ? ( + {_t("video-gallery.all")}, + onClick: () => { + setLabel(_t("video-gallery.all")); + setFilterStatus("all"); + } + }, + { + label: {_t("video-gallery.published")}, + onClick: () => { + setLabel(_t("video-gallery.published")); + setFilterStatus("published"); + } + }, + { + label: {_t("video-gallery.encoding")}, + onClick: () => { + const encoding = "encoding_ipfs" || "encoding_preparing"; + setLabel(_t("video-gallery.encoding")); + setFilterStatus(encoding); + } + }, + { + label: {_t("video-gallery.encoded")}, + onClick: () => { + setLabel(_t("video-gallery.encoded")); + setFilterStatus("publish_manual"); + } + }, + { + label: {_t("video-gallery.failed")}, + onClick: () => { + setLabel(_t("video-gallery.failed")); + setFilterStatus("encoding_failed"); + } + }, + { + label: {_t("video-gallery.status-deleted")}, + onClick: () => { + setLabel(_t("video-gallery.status-deleted")); + setFilterStatus("deleted"); + } } - } - ]} - /> + ]} + /> + ) : ( + <> + )}