diff --git a/apps/extension/src/contentscript/multitable-panel/assets/vectors.tsx b/apps/extension/src/contentscript/multitable-panel/assets/vectors.tsx
index 74d6af22..fa8d17bf 100644
--- a/apps/extension/src/contentscript/multitable-panel/assets/vectors.tsx
+++ b/apps/extension/src/contentscript/multitable-panel/assets/vectors.tsx
@@ -168,3 +168,52 @@ export const AvailableIcon = () => (
/>
)
+
+export const PlusCircle = () => (
+
+)
+
+export const MinusCircle = () => (
+
+)
diff --git a/apps/extension/src/contentscript/multitable-panel/components/application-card.tsx b/apps/extension/src/contentscript/multitable-panel/components/application-card.tsx
index 851dc2a7..bc74fb04 100644
--- a/apps/extension/src/contentscript/multitable-panel/components/application-card.tsx
+++ b/apps/extension/src/contentscript/multitable-panel/components/application-card.tsx
@@ -1,15 +1,19 @@
-import { AppMetadata } from '@mweb/engine'
+import { AppMetadata, Document, useAppDocuments } from '@mweb/engine'
import React from 'react'
import styled from 'styled-components'
import { Image } from './image'
+import { DocumentCard } from './document-card'
+import { AppInMutation } from '@mweb/engine/lib/app/services/mutation/mutation.entity'
+import { Spin } from 'antd'
-const Card = styled.div`
+const Card = styled.div<{ $backgroundColor?: string }>`
position: relative;
width: 100%;
border-radius: 10px;
- background: #fff;
+ background: ${(p) => p.$backgroundColor};
border: 1px solid #eceef0;
- font-family: sans-serif;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
+ 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
&:hover {
background: rgba(24, 121, 206, 0.1);
}
@@ -36,12 +40,20 @@ const CardContent = styled.div`
width: 100%;
`
-const TextLink = styled.div<{ bold?: boolean; small?: boolean; ellipsis?: boolean }>`
+type TTextLink = {
+ bold?: boolean
+ small?: boolean
+ ellipsis?: boolean
+ $color?: string
+}
+
+const TextLink = styled.div`
display: block;
margin: 0;
font-size: 14px;
line-height: 18px;
- color: ${(p) => (p.bold ? '#11181C !important' : '#687076 !important')};
+ color: ${(p) =>
+ p.$color ? `${p.$color} !important` : p.bold ? '#11181C !important' : '#687076 !important'};
font-weight: ${(p) => (p.bold ? '600' : '400')};
font-size: ${(p) => (p.small ? '12px' : '14px')};
overflow: ${(p) => (p.ellipsis ? 'hidden' : 'visible')};
@@ -50,29 +62,13 @@ const TextLink = styled.div<{ bold?: boolean; small?: boolean; ellipsis?: boolea
outline: none;
`
-// const Text = styled.p<{ bold?: boolean; small?: boolean; ellipsis?: boolean }>`
-// margin: 0;
-// font-size: 14px;
-// line-height: 20px;
-// color: ${(p) => (p.bold ? '#11181C' : '#687076')};
-// font-weight: ${(p) => (p.bold ? '600' : '400')};
-// font-size: ${(p) => (p.small ? '12px' : '14px')};
-// overflow: ${(p) => (p.ellipsis ? 'hidden' : '')};
-// text-overflow: ${(p) => (p.ellipsis ? 'ellipsis' : '')};
-// white-space: nowrap;
-
-// i {
-// margin-right: 3px;
-// }
-// `
-
-const Thumbnail = styled.div`
+const Thumbnail = styled.div<{ $shape: 'circle' | 'default' }>`
display: block;
width: 60px;
height: 60px;
flex-shrink: 0;
border: 1px solid #eceef0;
- border-radius: 8px;
+ border-radius: ${(props) => (props.$shape === 'circle' ? '99em' : '8px')};
overflow: hidden;
outline: none;
transition: border-color 200ms;
@@ -108,6 +104,45 @@ const ButtonLink = styled.button`
}
`
+const DocumentsWrapper = styled.div`
+ display: flex;
+ padding-bottom: 10px;
+`
+
+const SideLine = styled.div`
+ border: 1px solid #c1c6ce;
+ margin: 0 10px;
+`
+
+const DocumentCardList = styled.div`
+ width: 100%;
+ margin-right: 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+`
+
+const MoreIcon = () => (
+
+)
+
const UncheckedIcon = () => (
)
-export interface Props {
+export interface ISimpleApplicationCardProps {
src: string
metadata: AppMetadata['metadata']
+ disabled: boolean
isChecked: boolean
onChange: (isChecked: boolean) => void
+ iconShape?: 'circle'
+ textColor?: string
+ backgroundColor?: string
+}
+
+export interface IApplicationCardWithDocsProps {
+ src: string
+ metadata: AppMetadata['metadata']
disabled: boolean
+ docsIds: AppInMutation['documentId'][]
+ onDocCheckboxChange: (docId: string | null, isChecked: boolean) => void
+ onOpenDocumentsModal: (docs: Document[]) => void
+}
+
+interface IApplicationCard
+ extends ISimpleApplicationCardProps,
+ Omit {
+ hasDocuments: boolean
+ usingDocs: (Document | null)[]
+ allDocs: Document[]
}
-export const ApplicationCard: React.FC = ({
+const ApplicationCard: React.FC = ({
src,
metadata,
+ disabled,
+ hasDocuments,
+ iconShape,
+ textColor,
+ backgroundColor,
isChecked,
+ usingDocs,
+ allDocs,
onChange,
- disabled,
+ onDocCheckboxChange,
+ onOpenDocumentsModal,
}) => {
- const [accountId, , widgetName] = src.split('/')
-
+ const [accountId, , appId] = src.split('/')
return (
-
+
-
+
= ({
-
- {metadata.name || widgetName}
+
+ {metadata.name || appId}
@{accountId}
+
onChange(!isChecked)}
+ onClick={hasDocuments ? () => onOpenDocumentsModal(allDocs) : () => onChange(!isChecked)}
>
- {isChecked ? : }
+ {hasDocuments ? : isChecked ? : }
+
+ {hasDocuments && usingDocs.length ? (
+
+
+
+ {usingDocs.map((doc) => (
+ onDocCheckboxChange(doc?.id ?? null, false)}
+ disabled={disabled}
+ appMetadata={metadata}
+ />
+ ))}
+
+
+ ) : null}
)
}
+
+export const SimpleApplicationCard: React.FC = (props) => (
+ null}
+ onDocCheckboxChange={() => null}
+ usingDocs={[]}
+ allDocs={[]}
+ />
+)
+
+export const ApplicationCardWithDocs: React.FC = (props) => {
+ const { src, docsIds } = props
+ const { documents, isLoading } = useAppDocuments(src)
+ const usingDocs: (Document | null)[] = documents?.filter((doc) => docsIds.includes(doc.id))
+ if (docsIds.includes(null)) usingDocs.unshift(null)
+
+ return isLoading ? (
+
+
+
+
+
+ ) : (
+ null}
+ usingDocs={usingDocs}
+ allDocs={documents}
+ />
+ )
+}
diff --git a/apps/extension/src/contentscript/multitable-panel/components/button.tsx b/apps/extension/src/contentscript/multitable-panel/components/button.tsx
index a9d25d91..57442f05 100644
--- a/apps/extension/src/contentscript/multitable-panel/components/button.tsx
+++ b/apps/extension/src/contentscript/multitable-panel/components/button.tsx
@@ -1,11 +1,12 @@
import styled from 'styled-components'
-export const Button = styled.button`
+export const Button = styled.button<{ primary?: boolean }>`
display: flex;
justify-content: center;
align-items: center;
- border: 1px solid rgba(226, 226, 229, 1);
- color: rgba(2, 25, 58, 1);
+ border: ${(p) => (p.primary ? 'none' : '1px solid rgba(226, 226, 229, 1)')};
+ color: ${(p) => (p.primary ? '#fff' : 'rgba(2, 25, 58, 1)')};
+ background: ${(p) => (p.primary ? 'rgba(56, 75, 255, 1)' : 'inherit')};
width: 175px;
height: 42px;
border-radius: 10px;
diff --git a/apps/extension/src/contentscript/multitable-panel/components/document-card.tsx b/apps/extension/src/contentscript/multitable-panel/components/document-card.tsx
new file mode 100644
index 00000000..18e63b10
--- /dev/null
+++ b/apps/extension/src/contentscript/multitable-panel/components/document-card.tsx
@@ -0,0 +1,186 @@
+import { AppMetadata } from '@mweb/engine'
+import React from 'react'
+import styled from 'styled-components'
+import { Image } from './image'
+import { DocumentMetadata } from '@mweb/engine/lib/app/services/document/document.entity'
+
+const Card = styled.div`
+ position: relative;
+ width: 100%;
+ border-radius: 10px;
+ background: #f8f9ff;
+ border: 1px solid #eceef0;
+ font-family: sans-serif;
+ &:hover {
+ background: rgba(24, 121, 206, 0.1);
+ }
+ &.disabled {
+ opacity: 0.7;
+ }
+ &.disabled:hover {
+ background: #f8f9ff;
+ }
+`
+
+const CardBody = styled.div`
+ padding: 10px 6px;
+ display: flex;
+ gap: 6px;
+ align-items: center;
+
+ > * {
+ min-width: 0;
+ }
+`
+
+const ThumbnailGroup = styled.div`
+ position: relative;
+ width: 32px;
+ height: 32px;
+ flex-shrink: 0;
+`
+
+const Thumbnail = styled.div`
+ border: 1px solid #eceef0;
+ border-radius: 99em;
+ overflow: hidden;
+ outline: none;
+ transition: border-color 200ms;
+
+ &:focus,
+ &:hover {
+ border-color: #d0d5dd;
+ }
+
+ img {
+ object-fit: cover;
+ width: 100%;
+ height: 100%;
+ }
+`
+
+const ThumbnailMini = styled.div`
+ display: block;
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+ border: 1px solid #eceef0;
+ border-radius: 4px;
+ overflow: hidden;
+ outline: none;
+ transition: border-color 200ms;
+ position: absolute;
+ bottom: 0;
+ right: 0;
+
+ &:focus,
+ &:hover {
+ border-color: #d0d5dd;
+ }
+
+ img {
+ object-fit: cover;
+ vertical-align: unset;
+ width: 100%;
+ height: 100%;
+ }
+`
+
+const CardContent = styled.div`
+ width: 100%;
+`
+
+const TextLink = styled.div<{ bold?: boolean; small?: boolean; ellipsis?: boolean }>`
+ display: block;
+ margin: 0;
+ font-size: 14px;
+ line-height: 18px;
+ color: ${(p) => (p.bold ? '#11181C !important' : '#687076 !important')};
+ font-weight: ${(p) => (p.bold ? '600' : '400')};
+ font-size: ${(p) => (p.small ? '12px' : '14px')};
+ overflow: ${(p) => (p.ellipsis ? 'hidden' : 'visible')};
+ text-overflow: ${(p) => (p.ellipsis ? 'ellipsis' : 'unset')};
+ white-space: nowrap;
+ outline: none;
+`
+
+const ButtonLink = styled.button`
+ padding: 8px;
+ cursor: pointer;
+ text-decoration: none;
+ outline: none;
+ border: none;
+ background: inherit;
+ &:hover,
+ &:focus {
+ text-decoration: none;
+ outline: none;
+ border: none;
+ background: inherit;
+ }
+ &.disabled {
+ cursor: default;
+ }
+`
+
+const CheckedIcon = () => (
+
+)
+
+const FALLBACK_IMAGE_URL =
+ 'https://ipfs.near.social/ipfs/bafkreifc4burlk35hxom3klq4mysmslfirj7slueenbj7ddwg7pc6ixomu'
+
+export interface Props {
+ src: string | null
+ metadata: DocumentMetadata | null
+ onChange: () => void
+ disabled: boolean
+ appMetadata: AppMetadata['metadata']
+}
+
+export const DocumentCard: React.FC = ({
+ src,
+ metadata,
+ onChange,
+ disabled,
+ appMetadata,
+}) => {
+ const srcParts = src?.split('/')
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {metadata?.name || (srcParts && srcParts[2]) || 'New Document'}
+
+
+
+ {srcParts && `@${srcParts[0]}`}
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/extension/src/contentscript/multitable-panel/components/documents-modal.tsx b/apps/extension/src/contentscript/multitable-panel/components/documents-modal.tsx
new file mode 100644
index 00000000..5e77704f
--- /dev/null
+++ b/apps/extension/src/contentscript/multitable-panel/components/documents-modal.tsx
@@ -0,0 +1,243 @@
+import { Document } from '@mweb/engine'
+import React, { FC, useState } from 'react'
+import styled from 'styled-components'
+import { SimpleApplicationCard } from './application-card'
+import { Button } from './button'
+import { MinusCircle, PlusCircle } from '../assets/vectors'
+
+const Wrapper = styled.div`
+ position: absolute;
+ z-index: 3;
+ top: calc(50% - 10px);
+ transform: translateY(-50%);
+ left: 0;
+ width: calc(100% - 20px);
+ max-height: calc(100% - 20px);
+ margin: 10px;
+ padding: 10px;
+ border: 1px solid #000;
+ border-radius: 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ font-family: sans-serif;
+ background: #f8f9ff;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
+ 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+`
+
+const Close = styled.span`
+ cursor: pointer;
+ svg {
+ margin: 0;
+ width: 23px;
+ height: 23px;
+
+ path {
+ stroke: #838891;
+ }
+ }
+ &:hover {
+ opacity: 0.5;
+ }
+`
+
+const Header = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: rgba(2, 25, 58, 1);
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 21.09px;
+ text-align: left;
+ gap: 20px;
+
+ .edit {
+ margin-right: auto;
+ margin-bottom: 2px;
+ }
+`
+
+const Title = styled.div`
+ color: #02193a;
+`
+
+const AppsList = styled.div`
+ overflow: hidden;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding: 10px;
+ background: white;
+ border-radius: 10px;
+ overscroll-behavior: contain;
+
+ &::-webkit-scrollbar {
+ cursor: pointer;
+ width: 4px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: rgb(244 244 244);
+ background: linear-gradient(
+ 90deg,
+ rgb(244 244 244 / 0%) 10%,
+ rgb(227 227 227 / 100%) 50%,
+ rgb(244 244 244 / 0%) 90%
+ );
+ }
+
+ &::-webkit-scrollbar-thumb {
+ width: 4px;
+ height: 2px;
+ background: #384bff;
+ border-radius: 2px;
+ box-shadow: 0 2px 6px rgb(0 0 0 / 9%), 0 2px 2px rgb(38 117 209 / 4%);
+ }
+`
+
+const InlineButton = styled.button`
+ align-self: center;
+ width: fit-content;
+ border: none;
+ display: flex;
+ gap: 5px;
+ background: none;
+ color: #384bff;
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 150%;
+ text-decoration: none;
+ cursor: pointer;
+ &:hover {
+ opacity: 0.5;
+ }
+`
+
+const ButtonsBlock = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+`
+
+const CloseIcon = () => (
+
+)
+
+export interface Props {
+ docs: Document[] | null
+ chosenDocumentsIds: (string | null)[]
+ setDocumentsIds: (ids: (string | null)[]) => void
+ onClose: () => void
+}
+
+export const DocumentsModal: FC = ({
+ docs,
+ chosenDocumentsIds,
+ setDocumentsIds,
+ onClose,
+}) => {
+ const [chosenDocsIds, setChosenDocsIds] = useState<(string | null)[]>(chosenDocumentsIds)
+
+ const handleDocCheckboxChange = (id: string | null) =>
+ setChosenDocsIds((val) =>
+ chosenDocsIds.includes(id) ? val.filter((docId) => docId !== id) : [...val, id]
+ )
+
+ return (
+
+
+ Select your guide
+
+
+
+
+
+ handleDocCheckboxChange(null)}>
+ {chosenDocsIds.includes(null) ? (
+ <>
+
+ Delete document builder
+ >
+ ) : (
+ <>
+
+ Create from scratch
+ >
+ )}
+
+
+
+ {docs?.map((doc) => (
+ handleDocCheckboxChange(doc.id)}
+ disabled={false}
+ iconShape="circle"
+ textColor="#4E5E76"
+ backgroundColor="#F8F9FF"
+ />
+ ))}
+
+
+
+
+
+
+
+ )
+}
+
+const hasArrayTheSameData = (a: (string | null)[], b: (string | null)[]) => {
+ if (a.length !== b.length) {
+ return false
+ }
+
+ const aMap = new Map()
+ const bMap = new Map()
+
+ for (const item of a) {
+ aMap.set(item, (aMap.get(item) ?? 0) + 1 || 1)
+ }
+
+ for (const item of b) {
+ bMap.set(item, (bMap.get(item) ?? 0) + 1 || 1)
+ }
+
+ for (const [key, value] of aMap) {
+ if (bMap.get(key) !== value) {
+ return false
+ }
+ }
+
+ return true
+}
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 ea610490..0037cc34 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
@@ -1,5 +1,6 @@
import {
AppMetadata,
+ Document,
Mutation,
useCreateMutation,
useEditMutation,
@@ -19,11 +20,12 @@ import {
} from '../../helpers'
import { useEscape } from '../../hooks/use-escape'
import { Alert, AlertProps } from './alert'
-import { ApplicationCard } from './application-card'
+import { ApplicationCardWithDocs, SimpleApplicationCard } from './application-card'
import { Button } from './button'
import { DropdownButton } from './dropdown-button'
import { Input } from './input'
import { InputImage } from './upload-image'
+import { DocumentsModal } from './documents-modal'
const SelectedMutationEditorWrapper = styled.div`
display: flex;
@@ -40,6 +42,8 @@ const SelectedMutationEditorWrapper = styled.div`
background: #f8f9ff;
width: 400px;
max-height: 70vh;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
+ 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
`
const Close = styled.span`
@@ -80,6 +84,8 @@ const AppsList = styled.div`
display: flex;
flex-direction: column;
gap: 5px;
+ overscroll-behavior: contain;
+
&::-webkit-scrollbar {
cursor: pointer;
width: 4px;
@@ -112,6 +118,18 @@ const ButtonsBlock = styled.div`
align-items: center;
`
+const BlurredBackground = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgb(255 255 255 / 75%);
+ backdrop-filter: blur(5px);
+ border-radius: 9px;
+ z-index: 3;
+`
+
const CloseIcon = () => (