diff --git a/apps/extension/src/contentscript/multitable-panel/components/modals-confirm-document.tsx b/apps/extension/src/contentscript/multitable-panel/components/modals-confirm-document.tsx new file mode 100644 index 00000000..e9f9cc11 --- /dev/null +++ b/apps/extension/src/contentscript/multitable-panel/components/modals-confirm-document.tsx @@ -0,0 +1,204 @@ +import { AppId, BaseDto, DocumentMetadata, MutationCreateDto, MutationDto } from '@mweb/backend' +import { + useAppDocuments, + useCreateMutation, + useDeleteLocalMutation, + useDocument, + useEditMutation, +} from '@mweb/engine' +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react' +import { cloneDeep } from '../../helpers' +import { useEscape } from '../../hooks/use-escape' +import { AlertProps } from './alert' +import { ModalsConfirm, ModalMode } from './modals-confirm' + +export interface Props { + editingDocument: BaseDto & { + metadata: DocumentMetadata + openWith: AppId[] + content: any + } + loggedInAccountId: string + onCloseCurrent: () => void + onCloseAll: () => void +} + +interface IAlert extends AlertProps { + id: string +} + +// ToDo: duplication -- move somewhere +const alerts: { [name: string]: IAlert } = { + noWallet: { + id: 'noWallet', + text: 'Connect the NEAR wallet to create the document.', + severity: 'warning', + }, + emptyDocument: { + id: 'emptyDocument', + text: 'A document cannot be empty.', + severity: 'warning', + }, + notEditedDocument: { + id: 'notEditedDocument', + text: 'No changes found!', + severity: 'warning', + }, + idIsNotUnique: { + id: 'idIsNotUnique', + text: 'This document ID already exists.', + severity: 'warning', + }, + noName: { + id: 'noName', + text: 'Name must be specified.', + severity: 'error', + }, + noImage: { + id: 'noImage', + text: 'Image must be specified.', + severity: 'error', + }, +} + +export const ModalConfirmDocument: FC = ({ + editingDocument, + loggedInAccountId, + onCloseCurrent, + onCloseAll, +}) => { + const { name, image, description, fork_of } = editingDocument.metadata + + // Close modal with escape key + useEscape(onCloseCurrent) // ToDo -- does not work + + const [newName, setName] = useState(name ?? '') + const [newImage, setImage] = useState<{ ipfs_cid?: string } | undefined>(image) + const [newDescription, setDescription] = useState(description ?? '') + const [isApplyToOriginChecked, setIsApplyToOriginChecked] = useState(false) // ToDo: separate checkboxes + const [alert, setAlert] = useState(null) + const appDocuments = editingDocument.openWith && useAppDocuments(editingDocument.openWith[0]) // ToDo: need to change when different apps could use same documents + + const [mode, setMode] = useState( + !editingDocument.authorId // Newly created local document doesn't have author + ? ModalMode.Creating + : editingDocument.authorId === loggedInAccountId + ? ModalMode.Editing + : ModalMode.Forking + ) + + const forkedDocument = useMemo(() => { + if (mode !== ModalMode.Editing || !fork_of || !appDocuments) return + return appDocuments.documents?.find((doc) => doc.id === fork_of) + }, [fork_of, appDocuments, mode]) + + const { createMutation, isLoading: isCreating } = useCreateMutation() + const { editMutation, isLoading: isEditing } = useEditMutation() + const { deleteLocalMutation } = useDeleteLocalMutation() + const { documentTask, resolveDocumentTask } = useDocument() + + const isFormDisabled = isCreating || isEditing + + useEffect(() => setAlert(null), [newName, newImage, newDescription, isApplyToOriginChecked]) + + // const checkIfModified = useCallback( + // (mutationToPublish: MutationDto) => + // baseMutation ? !compareMutations(baseMutation, mutationToPublish) : true, + // [baseMutation] + // ) + + const doChecksForAlerts = useCallback( + (mutationToPublish: MutationCreateDto | MutationDto, isEditing: boolean): IAlert | null => { + if (!mutationToPublish.metadata.name) return alerts.noName + if (!mutationToPublish.metadata.image) return alerts.noImage + // if ( + // isEditing && + // !isApplyToOriginChecked && + // !checkIfModified(mutationToPublish as MutationDto) + // ) + // return alerts.notEditedMutation + return null + }, + [newName, newImage, isApplyToOriginChecked] // checkIfModified + ) + + const handleSaveClick = async () => { + if (!documentTask) return + console.log('handleSaveClick') + const documentToPublish = cloneDeep(editingDocument) + documentToPublish.metadata.name = newName.trim() + documentToPublish.metadata.image = newImage + documentToPublish.metadata.description = newDescription.trim() + resolveDocumentTask(documentToPublish) + // mutationToPublish.source = EntitySourceType.Origin // save to the contract + // if (mode === ModalMode.Forking) { + // mutationToPublish.metadata.fork_of = mutationToPublish.id + // } + // const newAlert = doChecksForAlerts(mutationToPublish, mode === ModalMode.Editing) + // if (newAlert) { + // setAlert(newAlert) + // return + // } + // if (mode === ModalMode.Creating || mode === ModalMode.Forking) { + // try { + // const id = await createMutation( + // mutationToPublish, + // mode === ModalMode.Forking + // ? { askOriginToApplyChanges: isApplyToOriginChecked } + // : undefined + // ) + // switchMutation(id) + // switchPreferredSource(id, EntitySourceType.Origin) + // await deleteLocalMutation(mutationToPublish.id) + // onCloseAll() + // } catch (error: any) { + // if (error?.message === 'Mutation with that ID already exists') { + // setAlert(alerts.idIsNotUnique) + // } + // } + // } else if (mode === ModalMode.Editing) { + // try { + // await editMutation( + // mutationToPublish as MutationDto, + // forkedMutation && isApplyToOriginChecked + // ? forkedMutation.authorId === loggedInAccountId + // ? { applyChangesToOrigin: true } + // : { askOriginToApplyChanges: true } + // : undefined + // ) + // switchPreferredSource(mutationToPublish.id, EntitySourceType.Origin) + // await deleteLocalMutation(mutationToPublish.id) + // onCloseAll() + // } catch (error: any) { + // console.error(error) + // } + // } + } + + const handleChangeModalMode = (itemId: string) => { + setMode(itemId as ModalMode) + } + + return ( + + ) +} diff --git a/apps/extension/src/contentscript/multitable-panel/components/modals-confirm-mutation.tsx b/apps/extension/src/contentscript/multitable-panel/components/modals-confirm-mutation.tsx new file mode 100644 index 00000000..0fab9664 --- /dev/null +++ b/apps/extension/src/contentscript/multitable-panel/components/modals-confirm-mutation.tsx @@ -0,0 +1,198 @@ +import { EntitySourceType, MutationCreateDto, MutationDto } from '@mweb/backend' +import { + useCreateMutation, + useDeleteLocalMutation, + useEditMutation, + useMutableWeb, +} from '@mweb/engine' +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react' +import { cloneDeep } from '../../helpers' +import { useEscape } from '../../hooks/use-escape' +import { AlertProps } from './alert' +import { ModalsConfirm, ModalMode } from './modals-confirm' + +export interface Props { + onCloseCurrent: () => void + onCloseAll: () => void + editingMutation: MutationDto + loggedInAccountId: string +} + +interface IAlert extends AlertProps { + id: string +} + +// ToDo: duplication -- move somewhere +const alerts: { [name: string]: IAlert } = { + noWallet: { + id: 'noWallet', + text: 'Connect the NEAR wallet to create the mutation.', + severity: 'warning', + }, + emptyMutation: { + id: 'emptyMutation', + text: 'A mutation cannot be empty.', + severity: 'warning', + }, + notEditedMutation: { + id: 'notEditedMutation', + text: 'No changes found!', + severity: 'warning', + }, + idIsNotUnique: { + id: 'idIsNotUnique', + text: 'This mutation ID already exists.', + severity: 'warning', + }, + noName: { + id: 'noName', + text: 'Name must be specified.', + severity: 'error', + }, + noImage: { + id: 'noImage', + text: 'Image must be specified.', + severity: 'error', + }, +} + +export const ModalConfirmMutation: FC = ({ + onCloseCurrent, + onCloseAll, + editingMutation, + loggedInAccountId, +}) => { + const { name, image, description, fork_of } = editingMutation.metadata + + // Close modal with escape key + useEscape(onCloseCurrent) // ToDo -- does not work + + const [newName, setName] = useState(name ?? '') + const [newImage, setImage] = useState<{ ipfs_cid?: string } | undefined>(image) + const [newDescription, setDescription] = useState(description ?? '') + const [isApplyToOriginChecked, setIsApplyToOriginChecked] = useState(false) // ToDo: separate checkboxes + const [alert, setAlert] = useState(null) + const { mutations, switchMutation, switchPreferredSource } = useMutableWeb() + + const [mode, setMode] = useState( + !editingMutation.authorId // Newly created local mutation doesn't have author + ? ModalMode.Creating + : editingMutation.authorId === loggedInAccountId + ? ModalMode.Editing + : ModalMode.Forking + ) + + const forkedMutation = useMemo(() => { + if (mode !== ModalMode.Editing || !fork_of) return + return mutations.find((mutation) => mutation.id === fork_of) + }, [fork_of, mutations, mode]) + + const { createMutation, isLoading: isCreating } = useCreateMutation() + const { editMutation, isLoading: isEditing } = useEditMutation() + const { deleteLocalMutation } = useDeleteLocalMutation() + + const isFormDisabled = isCreating || isEditing + + useEffect(() => setAlert(null), [newName, newImage, newDescription, isApplyToOriginChecked]) + + // const checkIfModified = useCallback( + // (mutationToPublish: MutationDto) => + // baseMutation ? !compareMutations(baseMutation, mutationToPublish) : true, + // [baseMutation] + // ) + + const doChecksForAlerts = useCallback( + (mutationToPublish: MutationCreateDto | MutationDto, isEditing: boolean): IAlert | null => { + if (!mutationToPublish.metadata.name) return alerts.noName + if (!mutationToPublish.metadata.image) return alerts.noImage + // if ( + // isEditing && + // !isApplyToOriginChecked && + // !checkIfModified(mutationToPublish as MutationDto) + // ) + // return alerts.notEditedMutation + return null + }, + [newName, newImage, isApplyToOriginChecked] // checkIfModified + ) + + const handleSaveClick = async () => { + const mutationToPublish = cloneDeep(editingMutation) + mutationToPublish.metadata.name = newName.trim() + mutationToPublish.metadata.image = newImage + mutationToPublish.metadata.description = newDescription.trim() + mutationToPublish.source = EntitySourceType.Origin // save to the contract + + if (mode === ModalMode.Forking) { + mutationToPublish.metadata.fork_of = mutationToPublish.id + } + + const newAlert = doChecksForAlerts(mutationToPublish, mode === ModalMode.Editing) + if (newAlert) { + setAlert(newAlert) + return + } + + if (mode === ModalMode.Creating || mode === ModalMode.Forking) { + try { + const id = await createMutation( + mutationToPublish, + mode === ModalMode.Forking + ? { askOriginToApplyChanges: isApplyToOriginChecked } + : undefined + ) + switchMutation(id) + switchPreferredSource(id, EntitySourceType.Origin) + await deleteLocalMutation(mutationToPublish.id) + onCloseAll() + } catch (error: any) { + if (error?.message === 'Mutation with that ID already exists') { + setAlert(alerts.idIsNotUnique) + } + } + } else if (mode === ModalMode.Editing) { + try { + await editMutation( + mutationToPublish as MutationDto, + forkedMutation && isApplyToOriginChecked + ? forkedMutation.authorId === loggedInAccountId + ? { applyChangesToOrigin: true } + : { askOriginToApplyChanges: true } + : undefined + ) + switchPreferredSource(mutationToPublish.id, EntitySourceType.Origin) + await deleteLocalMutation(mutationToPublish.id) + onCloseAll() + } catch (error: any) { + console.error(error) + } + } + } + + const handleChangeModalMode = (itemId: string) => { + setMode(itemId as ModalMode) + } + + return ( + + ) +} diff --git a/apps/extension/src/contentscript/multitable-panel/components/modals-confirm.tsx b/apps/extension/src/contentscript/multitable-panel/components/modals-confirm.tsx index 50db7202..307b2bb9 100644 --- a/apps/extension/src/contentscript/multitable-panel/components/modals-confirm.tsx +++ b/apps/extension/src/contentscript/multitable-panel/components/modals-confirm.tsx @@ -1,26 +1,13 @@ -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react' +import { BaseDto } from '@mweb/backend' +import { EntityMetadata } from '@mweb/backend/lib/common/entity-metadata' +import React, { FC } from 'react' import styled from 'styled-components' -import { - useCreateMutation, - useEditMutation, - useMutableWeb, - useDeleteLocalMutation, -} from '@mweb/engine' -import { EntitySourceType, MutationCreateDto, MutationDto } from '@mweb/backend' -import { Image } from './image' -import { useEscape } from '../../hooks/use-escape' import { Alert, AlertProps } from './alert' import { Button } from './button' -import { InputImage } from './upload-image' -import { cloneDeep } from '../../helpers' -import { DropdownButton } from './dropdown-button' import { ButtonsGroup } from './buttons-group' - -enum MutationModalMode { - Editing = 'editing', - Creating = 'creating', - Forking = 'forking', -} +import { DropdownButton } from './dropdown-button' +import { Image } from './image' +import { InputImage } from './upload-image' const ModalConfirmWrapper = styled.div` display: flex; @@ -177,185 +164,68 @@ const CheckboxInput = styled.input` border: 1px solid #384bff; ` -export interface Props { - itemType: 'mutation' | 'document' - onCloseCurrent: () => void - onCloseAll: () => void - editingMutation: MutationDto - loggedInAccountId: string -} - -interface IAlert extends AlertProps { - id: string +export enum ModalMode { + Editing = 'editing', + Creating = 'creating', + Forking = 'forking', } -// ToDo: duplication -- move somewhere -const alerts: { [name: string]: IAlert } = { - noWallet: { - id: 'noWallet', - text: 'Connect the NEAR wallet to create the mutation.', - severity: 'warning', - }, - emptyMutation: { - id: 'emptyMutation', - text: 'A mutation cannot be empty.', - severity: 'warning', - }, - notEditedMutation: { - id: 'notEditedMutation', - text: 'No changes found!', - severity: 'warning', - }, - idIsNotUnique: { - id: 'idIsNotUnique', - text: 'This mutation ID already exists.', - severity: 'warning', - }, - noName: { - id: 'noName', - text: 'Name must be specified.', - severity: 'error', - }, - noImage: { - id: 'noImage', - text: 'Image must be specified.', - severity: 'error', - }, +export type ModalsConfirmProps = { + entityType: string + editingEntity: BaseDto & { metadata: EntityMetadata } + loggedInAccountId: string + mode: ModalMode + alert: AlertProps | null + isFormDisabled: boolean + isApplyToOriginChecked: boolean + newName: string + newImage?: { ipfs_cid?: string } + newDescription: string + forkedEntity?: BaseDto & { metadata: EntityMetadata } + onChangeModalMode: (itemId: string) => void + setIsApplyToOriginChecked: React.Dispatch> + setName: (name: string) => void + setImage: (image: { ipfs_cid?: string }) => void + setDescription: (description: string) => void + onSaveClick: () => void + onCloseCurrent: () => void } -export const ModalConfirm: FC = ({ - itemType, - onCloseCurrent, - onCloseAll, - editingMutation, +export const ModalsConfirm: FC = ({ + entityType, + editingEntity, loggedInAccountId, + mode, + alert, + isFormDisabled, + isApplyToOriginChecked, + newName, + newImage, + newDescription, + forkedEntity, + onChangeModalMode, + setIsApplyToOriginChecked, + setName, + setImage, + setDescription, + onSaveClick, + onCloseCurrent, }) => { - const { name, image, description, fork_of } = editingMutation.metadata - - // Close modal with escape key - useEscape(onCloseCurrent) // ToDo -- does not work - - const [newName, setName] = useState(name ?? '') - const [newImage, setImage] = useState<{ ipfs_cid?: string } | undefined>(image) - const [newDescription, setDescription] = useState(description ?? '') - const [isApplyToOriginChecked, setIsApplyToOriginChecked] = useState(false) // ToDo: separate checkboxes - const [alert, setAlert] = useState(null) - const { mutations, switchMutation, switchPreferredSource } = useMutableWeb() - - const [mode, setMode] = useState( - !editingMutation.authorId // Newly created local mutation doesn't have author - ? MutationModalMode.Creating - : editingMutation.authorId === loggedInAccountId - ? MutationModalMode.Editing - : MutationModalMode.Forking - ) - - const forkedMutation = useMemo(() => { - if (mode !== MutationModalMode.Editing || !fork_of) return null - return mutations.find((mutation) => mutation.id === fork_of) - }, [fork_of, mutations, mode]) - - const { createMutation, isLoading: isCreating } = useCreateMutation() - const { editMutation, isLoading: isEditing } = useEditMutation() - const { deleteLocalMutation } = useDeleteLocalMutation() - - const isFormDisabled = isCreating || isEditing - - useEffect(() => setAlert(null), [newName, newImage, newDescription, isApplyToOriginChecked]) - - // const checkIfModified = useCallback( - // (mutationToPublish: MutationDto) => - // baseMutation ? !compareMutations(baseMutation, mutationToPublish) : true, - // [baseMutation] - // ) - - const doChecksForAlerts = useCallback( - (mutationToPublish: MutationCreateDto | MutationDto, isEditing: boolean): IAlert | null => { - if (!mutationToPublish.metadata.name) return alerts.noName - if (!mutationToPublish.metadata.image) return alerts.noImage - // if ( - // isEditing && - // !isApplyToOriginChecked && - // !checkIfModified(mutationToPublish as MutationDto) - // ) - // return alerts.notEditedMutation - return null - }, - [newName, newImage, isApplyToOriginChecked] // checkIfModified - ) - - const handleSaveClick = async () => { - const mutationToPublish = cloneDeep(editingMutation) - mutationToPublish.metadata.name = newName.trim() - mutationToPublish.metadata.image = newImage - mutationToPublish.metadata.description = newDescription.trim() - mutationToPublish.source = EntitySourceType.Origin // save to the contract - - if (mode === MutationModalMode.Forking) { - mutationToPublish.metadata.fork_of = mutationToPublish.id - } - - const newAlert = doChecksForAlerts(mutationToPublish, mode === MutationModalMode.Editing) - if (newAlert) { - setAlert(newAlert) - return - } - - if (mode === MutationModalMode.Creating || mode === MutationModalMode.Forking) { - try { - const id = await createMutation( - mutationToPublish, - mode === MutationModalMode.Forking - ? { askOriginToApplyChanges: isApplyToOriginChecked } - : undefined - ) - switchMutation(id) - switchPreferredSource(id, EntitySourceType.Origin) - await deleteLocalMutation(mutationToPublish.id) - onCloseAll() - } catch (error: any) { - if (error?.message === 'Mutation with that ID already exists') { - setAlert(alerts.idIsNotUnique) - } - } - } else if (mode === MutationModalMode.Editing) { - try { - await editMutation( - mutationToPublish as MutationDto, - forkedMutation && isApplyToOriginChecked - ? forkedMutation.authorId === loggedInAccountId - ? { applyChangesToOrigin: true } - : { askOriginToApplyChanges: true } - : undefined - ) - switchPreferredSource(mutationToPublish.id, EntitySourceType.Origin) - await deleteLocalMutation(mutationToPublish.id) - onCloseAll() - } catch (error: any) { - console.error(error) - } - } - } - - const handleSaveDropdownChange = (itemId: string) => { - setMode(itemId as MutationModalMode) - } - return ( - {mode === MutationModalMode.Creating - ? `Create your ${itemType}` - : mode === MutationModalMode.Editing - ? `Publish your ${itemType}` - : mode === MutationModalMode.Forking + {mode === ModalMode.Creating + ? `Create your ${entityType}` + : mode === ModalMode.Editing + ? `Publish your ${entityType}` + : mode === ModalMode.Forking ? 'Publish as a fork' : null} {alert ? : null} - {mode === MutationModalMode.Creating ? ( + {mode === ModalMode.Creating ? ( <> @@ -369,7 +239,7 @@ export const ModalConfirm: FC = ({ id={'name'} type={'text'} value={newName} - placeholder={`Enter your ${itemType} name`} + placeholder={`Enter your ${entityType} name`} onChange={(e) => setName(e.target.value)} disabled={isFormDisabled} /> @@ -383,36 +253,36 @@ export const ModalConfirm: FC = ({ setDescription(e.target.value)} disabled={isFormDisabled} /> Description - ) : mode === MutationModalMode.Forking ? ( + ) : mode === ModalMode.Forking ? ( <> {editingMutation.metadata.name} -

{editingMutation.metadata.name}

+

{editingEntity.metadata.name}

by{' '} - {editingMutation.authorId === loggedInAccountId + {editingEntity.authorId === loggedInAccountId ? `me (${loggedInAccountId})` - : editingMutation.authorId} + : editingEntity.authorId}
- {editingMutation.authorId === loggedInAccountId ? null : ( + {editingEntity.authorId === loggedInAccountId ? null : ( Ask Origin to apply changes = ({ id={'name'} type={'text'} value={newName} - placeholder={`Enter your ${itemType} name`} + placeholder={`Enter your ${entityType} name`} onChange={(e) => setName(e.target.value)} disabled={isFormDisabled} /> @@ -449,14 +319,14 @@ export const ModalConfirm: FC = ({ setDescription(e.target.value)} disabled={isFormDisabled} /> Description - ) : mode === MutationModalMode.Editing ? ( + ) : mode === ModalMode.Editing ? ( <> @@ -473,31 +343,31 @@ export const ModalConfirm: FC = ({ - {forkedMutation ? ( + {forkedEntity ? ( <> {forkedMutation.metadata.name} -

{forkedMutation.metadata.name}

+

{forkedEntity.metadata.name}

by{' '} - {forkedMutation.authorId === loggedInAccountId + {forkedEntity.authorId === loggedInAccountId ? `me (${loggedInAccountId})` - : forkedMutation.authorId} + : forkedEntity.authorId}
- {forkedMutation.authorId === loggedInAccountId + {forkedEntity.authorId === loggedInAccountId ? 'Apply changes to Origin' : 'Ask Origin to apply changes'} @@ -515,7 +385,7 @@ export const ModalConfirm: FC = ({ setDescription(e.target.value)} disabled={isFormDisabled} /> @@ -530,23 +400,23 @@ export const ModalConfirm: FC = ({ value={mode} items={[ { - value: MutationModalMode.Forking, + value: ModalMode.Forking, title: 'Fork', - visible: !!editingMutation.authorId, + visible: !!editingEntity.authorId, }, { - value: MutationModalMode.Editing, + value: ModalMode.Editing, title: 'Save', - visible: !!editingMutation.authorId && editingMutation.authorId === loggedInAccountId, + visible: !!editingEntity.authorId && editingEntity.authorId === loggedInAccountId, }, { - value: MutationModalMode.Creating, + value: ModalMode.Creating, title: 'Create', - visible: !editingMutation.authorId, + visible: !editingEntity.authorId, }, ]} - onClick={handleSaveClick} - onChange={handleSaveDropdownChange} + onClick={onSaveClick} + onChange={onChangeModalMode} disabled={isFormDisabled} disabledAll={isFormDisabled} /> diff --git a/apps/extension/src/contentscript/multitable-panel/components/mutation-editor-modal.tsx b/apps/extension/src/contentscript/multitable-panel/components/mutation-editor-modal.tsx index c7e16480..c5998868 100644 --- a/apps/extension/src/contentscript/multitable-panel/components/mutation-editor-modal.tsx +++ b/apps/extension/src/contentscript/multitable-panel/components/mutation-editor-modal.tsx @@ -8,7 +8,7 @@ import { Alert, AlertProps } from './alert' import { ApplicationCardWithDocs, SimpleApplicationCard } from './application-card' import { Button } from './button' import { DocumentsModal } from './documents-modal' -import { ModalConfirm } from './modals-confirm' +import { ModalConfirmMutation } from './modals-confirm-mutation' import { AppInMutation } from '@mweb/backend' import { Image } from './image' import { useSaveMutation, useMutableWeb } from '@mweb/engine' @@ -452,8 +452,7 @@ export const MutationEditorModal: FC = ({ apps, baseMutation, localMutati {isConfirmModalOpen && loggedInAccountId && ( - setIsConfirmModalOpen(false)} onCloseAll={onClose} editingMutation={editingMutation} diff --git a/apps/extension/src/contentscript/multitable-panel/multitable-panel.tsx b/apps/extension/src/contentscript/multitable-panel/multitable-panel.tsx index ce57cea8..320c75b0 100644 --- a/apps/extension/src/contentscript/multitable-panel/multitable-panel.tsx +++ b/apps/extension/src/contentscript/multitable-panel/multitable-panel.tsx @@ -1,15 +1,17 @@ +import { EntitySourceType } from '@mweb/backend' +import { useMutableWeb, useDocument } from '@mweb/engine' import { EventEmitter as NEventEmitter } from 'events' -import { useMutableWeb } from '@mweb/engine' +import { useAccountId } from 'near-social-vm' import React, { FC, useEffect, useRef, useState } from 'react' import Draggable from 'react-draggable' import styled from 'styled-components' +import { NearNetworkId } from '../../common/networks' import { getIsPanelUnpinned, removePanelUnpinnedFlag, setPanelUnpinnedFlag } from '../storage' import { PinOutlineIcon, PinSolidIcon } from './assets/vectors' import { Dropdown } from './components/dropdown' import { MutationEditorModal } from './components/mutation-editor-modal' import MutableOverlayContainer from './mutable-overlay-container' -import { NearNetworkId } from '../../common/networks' -import { EntitySourceType } from '@mweb/backend' +import { ModalConfirmDocument } from './components/modals-confirm-document' const WrapperPanel = styled.div<{ $isAnimated?: boolean }>` // Global Styles @@ -102,6 +104,19 @@ const IconWrapper = styled.div` justify-content: space-between; ` +const WhiteBackground = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgb(255 255 255 / 75%); + backdrop-filter: blur(5px); + display: flex; + justify-content: center; + align-items: center; +` + const DragIcon = () => ( @@ -122,6 +137,8 @@ export const MultitablePanel: FC = ({ eventEmitter }) => { const [isNotchDisplayed, setIsNotchDisplayed] = useState(true) const [isModalOpen, setIsModalOpen] = useState(false) const notchRef = useRef(null) + const loggedInAccountId = useAccountId() + const { documentTask, rejectDocumentTask } = useDocument() useEffect(() => { const timer = setTimeout(() => { @@ -223,6 +240,17 @@ export const MultitablePanel: FC = ({ eventEmitter }) => { )} + + {documentTask && !!selectedMutation && !!loggedInAccountId ? ( + + + + ) : null} ) diff --git a/libs/backend/src/services/document/dtos/document-commit.dto.ts b/libs/backend/src/services/document/dtos/document-commit.dto.ts index fecb98b9..56a4249b 100644 --- a/libs/backend/src/services/document/dtos/document-commit.dto.ts +++ b/libs/backend/src/services/document/dtos/document-commit.dto.ts @@ -1,6 +1,6 @@ import { AppId } from '../../application/application.entity' -import { DocumentMetadata } from '../document.entity' import { BaseDto } from '../../base/base.dto' +import { DocumentMetadata } from '../document.entity' export type DocumentCommitDto = BaseDto & { metadata: DocumentMetadata diff --git a/libs/backend/src/services/document/dtos/document-create.dto.ts b/libs/backend/src/services/document/dtos/document-create.dto.ts index cf168ada..e1cef66b 100644 --- a/libs/backend/src/services/document/dtos/document-create.dto.ts +++ b/libs/backend/src/services/document/dtos/document-create.dto.ts @@ -1,5 +1,5 @@ -import { BaseCreateDto } from '../../base/base-create.dto' import { AppId } from '../../application/application.entity' +import { BaseCreateDto } from '../../base/base-create.dto' import { DocumentMetadata } from '../document.entity' export type DocumentCreateDto = BaseCreateDto & { diff --git a/libs/backend/src/services/document/dtos/document.dto.ts b/libs/backend/src/services/document/dtos/document.dto.ts index aebcb8c9..f53ca5ea 100644 --- a/libs/backend/src/services/document/dtos/document.dto.ts +++ b/libs/backend/src/services/document/dtos/document.dto.ts @@ -1,5 +1,5 @@ -import { BaseDto } from '../../base/base.dto' import { AppId } from '../../application/application.entity' +import { BaseDto } from '../../base/base.dto' import { DocumentMetadata } from '../document.entity' export type DocumentDto = BaseDto & { diff --git a/libs/engine/src/app/app.tsx b/libs/engine/src/app/app.tsx index 5d4c38d7..835b93a8 100644 --- a/libs/engine/src/app/app.tsx +++ b/libs/engine/src/app/app.tsx @@ -12,6 +12,7 @@ import { PickerProvider } from './contexts/picker-context' import { ContextHighlighter } from './components/context-highlighter' import { HighlighterProvider } from './contexts/highlighter-context' import { ModalContextState } from './contexts/modal-context/modal-context' +import { DocumentProvider } from './contexts/document-context' export const App: FC<{ config: EngineConfig @@ -28,24 +29,26 @@ export const App: FC<{ return ( - - - - - - - - - - - {children} - - - + + + + + + + + + + + + {children} + + + + ) diff --git a/libs/engine/src/app/components/context-manager.tsx b/libs/engine/src/app/components/context-manager.tsx index 1fb2cd54..466798fb 100644 --- a/libs/engine/src/app/components/context-manager.tsx +++ b/libs/engine/src/app/components/context-manager.tsx @@ -32,6 +32,7 @@ import { useMutableWeb } from '../contexts/mutable-web-context' import { useAppControllers } from '../contexts/mutable-web-context/use-app-controllers' import { useContextApps } from '../contexts/mutable-web-context/use-context-apps' import { useUserLinks } from '../contexts/mutable-web-context/use-user-links' +import { useDocument } from '../contexts/document-context' interface WidgetProps { context: TransferableContext @@ -103,9 +104,11 @@ const ContextHandler: FC<{ context: IContextNode; insPoints: InsertionPointWithE const { controllers } = useAppControllers(context) const { links, createUserLink, deleteUserLink } = useUserLinks(context) const { apps } = useContextApps(context) - const { engine, selectedMutation, refreshMutation, activeApps } = useMutableWeb() + const { engine, selectedMutation, refreshMutation } = useMutableWeb() const { portals } = useEngine() + const { setDocumentTask } = useDocument() + const portalComponents = useMemo(() => { return Array.from(portals.values()) .filter(({ target }) => utils.isTargetMet(target, context)) @@ -278,7 +281,13 @@ const ContextHandler: FC<{ context: IContextNode; insPoints: InsertionPointWithE ) if (!appInstance) throw new Error('The app is not active') - // ToDo: show fork dialog + // Show fork dialog for documents committing to the origin + // Fork dialog is able to edit a name, description and image + if (document.source === EntitySourceType.Origin) { + document = await new Promise((onResolve, onReject) => + setDocumentTask({ document, appInstanceId, onResolve, onReject }) + ) + } const { mutation, document: savedDocument } = await engine.documentService.commitDocumentToMutation( diff --git a/libs/engine/src/app/contexts/document-context/document-context.tsx b/libs/engine/src/app/contexts/document-context/document-context.tsx new file mode 100644 index 00000000..82f415d8 --- /dev/null +++ b/libs/engine/src/app/contexts/document-context/document-context.tsx @@ -0,0 +1,25 @@ +import { DocumentCommitDto } from '@mweb/backend' +import { createContext } from 'react' + +export type DocumentTask = { + document: DocumentCommitDto + appInstanceId: string + onReject: () => void + onResolve: (document: DocumentCommitDto) => void +} + +export type DocumentContextState = { + documentTask: DocumentTask | null + setDocumentTask: (newTask: DocumentTask | null) => void + rejectDocumentTask: () => void + resolveDocumentTask: (document: DocumentCommitDto) => void +} + +const contextDefaultValues: DocumentContextState = { + documentTask: null, + setDocumentTask: () => undefined, + rejectDocumentTask: () => undefined, + resolveDocumentTask: () => undefined, +} + +export const DocumentContext = createContext(contextDefaultValues) diff --git a/libs/engine/src/app/contexts/document-context/document-provider.tsx b/libs/engine/src/app/contexts/document-context/document-provider.tsx new file mode 100644 index 00000000..52e77606 --- /dev/null +++ b/libs/engine/src/app/contexts/document-context/document-provider.tsx @@ -0,0 +1,34 @@ +import React, { FC, ReactElement, useEffect } from 'react' +import { DocumentContext, DocumentContextState, DocumentTask } from './document-context' +import { DocumentCommitDto } from '@mweb/backend' + +type Props = { + children?: ReactElement +} + +const DocumentProvider: FC = ({ children }) => { + const [documentTask, setDocumentTask] = React.useState(null) + + const rejectDocumentTask = () => { + if (!documentTask) return + documentTask.onReject() + setDocumentTask(null) + } + + const commitDocumentTask = (document: DocumentCommitDto) => { + if (!documentTask) return + documentTask.onResolve(document) + setDocumentTask(null) + } + + const state: DocumentContextState = { + documentTask, + setDocumentTask, + resolveDocumentTask: commitDocumentTask, + rejectDocumentTask, + } + + return {children} +} + +export { DocumentProvider } diff --git a/libs/engine/src/app/contexts/document-context/index.ts b/libs/engine/src/app/contexts/document-context/index.ts new file mode 100644 index 00000000..34bbd4c3 --- /dev/null +++ b/libs/engine/src/app/contexts/document-context/index.ts @@ -0,0 +1,3 @@ +export { DocumentContext } from './document-context' +export { DocumentProvider } from './document-provider' +export { useDocument } from './use-document' diff --git a/libs/engine/src/app/contexts/document-context/use-document.ts b/libs/engine/src/app/contexts/document-context/use-document.ts new file mode 100644 index 00000000..27247db9 --- /dev/null +++ b/libs/engine/src/app/contexts/document-context/use-document.ts @@ -0,0 +1,6 @@ +import { useContext } from 'react' +import { DocumentContext } from './document-context' + +export function useDocument() { + return useContext(DocumentContext) +} diff --git a/libs/engine/src/index.ts b/libs/engine/src/index.ts index 8101fb89..2e15bca1 100644 --- a/libs/engine/src/index.ts +++ b/libs/engine/src/index.ts @@ -1,6 +1,7 @@ export * as customElements from './custom-elements' export { App } from './app/app' export { useEngine } from './app/contexts/engine-context' +export { useDocument } from './app/contexts/document-context' export { useMutation, useMutations,