Merge branch 'master' of https://github.com/toeverything/AFFiNE into feature/page-tree-code-style
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"projectName": "Ligo-Virgo",
|
"projectName": "toeverything",
|
||||||
"projectOwner": "toeverything",
|
"projectOwner": "toeverything",
|
||||||
"repoType": "github",
|
"repoType": "github",
|
||||||
"repoHost": "https://github.com",
|
"repoHost": "https://github.com",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"login": "tzhangchi",
|
"login": "tzhangchi",
|
||||||
"name": "Chi Zhang",
|
"name": "Chi Zhang",
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4",
|
||||||
"profile": "https://zhangchi.blog.csdn.net/",
|
"profile": "http://zhangchi.page/",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code",
|
"code",
|
||||||
"doc"
|
"doc"
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"login": "alt1o",
|
"login": "alt1o",
|
||||||
"name": "alt1o",
|
"name": "wang xinglong",
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4",
|
||||||
"profile": "https://github.com/alt1o",
|
"profile": "https://github.com/alt1o",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"login": "DiamondThree",
|
"login": "DiamondThree",
|
||||||
"name": "Diamond",
|
"name": "DiamondThree",
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4",
|
||||||
"profile": "https://github.com/DiamondThree",
|
"profile": "https://github.com/DiamondThree",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"login": "zuoxiaodong0815",
|
"login": "zuoxiaodong0815",
|
||||||
"name": "zuoxiaodong0815",
|
"name": "xiaodong zuo",
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4",
|
||||||
"profile": "https://github.com/zuoxiaodong0815",
|
"profile": "https://github.com/zuoxiaodong0815",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"login": "SaikaSakura",
|
"login": "SaikaSakura",
|
||||||
"name": "SaikaSakura",
|
"name": "MingLIang Wang",
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4",
|
||||||
"profile": "https://github.com/SaikaSakura",
|
"profile": "https://github.com/SaikaSakura",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
@@ -92,10 +92,10 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"login": "tuluffy",
|
"login": "mitsuhatu",
|
||||||
"name": "tuluffy",
|
"name": "mitsuhatu",
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/26808339?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/110213079?v=4",
|
||||||
"profile": "https://tuluffy.github.io/angular.github.io/",
|
"profile": "https://github.com/mitsuhatu",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code",
|
"code",
|
||||||
"doc"
|
"doc"
|
||||||
|
|||||||
28
.github/deployment/Caddyfile-affine
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
:3000 {
|
||||||
|
root /* ./dist
|
||||||
|
|
||||||
|
file_server {
|
||||||
|
precompressed br
|
||||||
|
}
|
||||||
|
|
||||||
|
encode {
|
||||||
|
zstd
|
||||||
|
gzip 9
|
||||||
|
}
|
||||||
|
|
||||||
|
@notStatic {
|
||||||
|
not path /*.css
|
||||||
|
not path /*.js
|
||||||
|
not path /*.png
|
||||||
|
not path /*.jpg
|
||||||
|
not path /*.svg
|
||||||
|
not path /*.ttf
|
||||||
|
not path /*.eot
|
||||||
|
not path /*.woff
|
||||||
|
not path /*.woff2
|
||||||
|
}
|
||||||
|
|
||||||
|
handle @notStatic {
|
||||||
|
try_files {path} /index.html
|
||||||
|
}
|
||||||
|
}
|
||||||
21
.github/deployment/Dockerfile-affine
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM node:16-alpine as builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN apk add g++ make python3 git libpng-dev
|
||||||
|
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:local
|
||||||
|
|
||||||
|
FROM node:16-alpine as relocate
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/dist/apps/ligo-virgo ./dist
|
||||||
|
COPY --from=builder /app/.github/deployment/Caddyfile-affine ./Caddyfile
|
||||||
|
RUN rm ./dist/*.txt
|
||||||
|
|
||||||
|
# =============
|
||||||
|
# AFFiNE image
|
||||||
|
# =============
|
||||||
|
FROM caddy:2.4.6-alpine as AFFiNE
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=relocate /app .
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["caddy", "run"]
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
FROM node:16-alpine as builder
|
FROM node:16-alpine as builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN apk add g++ make python3 git
|
RUN apk add g++ make python3 git libpng-dev
|
||||||
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:keck
|
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:keck
|
||||||
|
|
||||||
FROM node:16-alpine as node_modules
|
FROM node:16-alpine as node_modules
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
FROM node:16-alpine as builder
|
FROM node:16-alpine as builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN apk add g++ make python3 git
|
RUN apk add g++ make python3 git libpng-dev
|
||||||
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build
|
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build
|
||||||
|
|
||||||
FROM node:16-alpine as relocate
|
FROM node:16-alpine as relocate
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /app/dist/apps/ligo-virgo ./dist
|
COPY --from=builder /app/dist/apps/ligo-virgo ./dist
|
||||||
COPY --from=builder /app/Caddyfile ./
|
COPY --from=builder /app/.github/deployment/Caddyfile-lisa ./Caddyfile
|
||||||
RUN rm ./dist/*.txt
|
RUN rm ./dist/*.txt
|
||||||
|
|
||||||
# =============
|
# =============
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
FROM node:16-alpine as builder
|
FROM node:16-alpine as builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN apk add g++ make python3 git
|
RUN apk add g++ make python3 git libpng-dev
|
||||||
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:venus
|
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:venus
|
||||||
|
|
||||||
FROM node:16-alpine as relocate
|
FROM node:16-alpine as relocate
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /app/dist/apps/venus ./dist
|
COPY --from=builder /app/dist/apps/venus ./dist
|
||||||
COPY --from=builder /app/Caddyfile-venus ./Caddyfile
|
COPY --from=builder /app/.github/deployment/Caddyfile-venus ./Caddyfile
|
||||||
RUN rm ./dist/*.txt
|
RUN rm ./dist/*.txt
|
||||||
|
|
||||||
# =============
|
# =============
|
||||||
57
.github/workflows/affine.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: Build AFFiNE-Local
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
# Cancels all previous workflow runs for pull requests that have not completed.
|
||||||
|
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
||||||
|
concurrency:
|
||||||
|
# The concurrency group contains the workflow name and the branch name for
|
||||||
|
# pull requests or the commit hash for any other events.
|
||||||
|
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
NAMESPACE: toeverything
|
||||||
|
AFFINE_IMAGE_NAME: AFFiNE
|
||||||
|
IMAGE_TAG_LATEST: nightly-latest
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ligo-virgo:
|
||||||
|
runs-on: self-hosted
|
||||||
|
environment: development
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker (AFFiNE-Local)
|
||||||
|
id: meta_affine
|
||||||
|
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.AFFINE_IMAGE_NAME }}
|
||||||
|
tags: ${{ env.IMAGE_TAG_LATEST }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image (AFFINE-Local)
|
||||||
|
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./.github/deployment/Dockerfile-affine
|
||||||
|
push: ${{ github.ref == 'refs/heads/master' && true || false }}
|
||||||
|
tags: ${{ steps.meta_affine.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta_affine.outputs.labels }}
|
||||||
|
target: AFFiNE
|
||||||
4
.github/workflows/keck.yml
vendored
@@ -6,11 +6,13 @@ on:
|
|||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- 'apps/keck/**'
|
- 'apps/keck/**'
|
||||||
|
- '.github/deployment'
|
||||||
- '.github/workflows/keck.yml'
|
- '.github/workflows/keck.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- 'apps/keck/**'
|
- 'apps/keck/**'
|
||||||
|
- '.github/deployment'
|
||||||
- '.github/workflows/keck.yml'
|
- '.github/workflows/keck.yml'
|
||||||
|
|
||||||
# Cancels all previous workflow runs for pull requests that have not completed.
|
# Cancels all previous workflow runs for pull requests that have not completed.
|
||||||
@@ -60,7 +62,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile-keck
|
file: ./.github/deployment/Dockerfile-keck
|
||||||
push: ${{ github.ref == 'refs/heads/field' && true || false }}
|
push: ${{ github.ref == 'refs/heads/field' && true || false }}
|
||||||
tags: ${{ steps.meta_keck.outputs.tags }}
|
tags: ${{ steps.meta_keck.outputs.tags }}
|
||||||
labels: ${{ steps.meta_keck.outputs.labels }}
|
labels: ${{ steps.meta_keck.outputs.labels }}
|
||||||
|
|||||||
1
.github/workflows/lisa.yml
vendored
@@ -53,6 +53,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
file: ./.github/deployment/Dockerfile-lisa
|
||||||
push: ${{ github.ref == 'refs/heads/master' && true || false }}
|
push: ${{ github.ref == 'refs/heads/master' && true || false }}
|
||||||
tags: ${{ steps.meta_lisa.outputs.tags }}
|
tags: ${{ steps.meta_lisa.outputs.tags }}
|
||||||
labels: ${{ steps.meta_lisa.outputs.labels }}
|
labels: ${{ steps.meta_lisa.outputs.labels }}
|
||||||
|
|||||||
3
.github/workflows/venus.yml
vendored
@@ -5,6 +5,7 @@ on:
|
|||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- 'apps/venus/**'
|
- 'apps/venus/**'
|
||||||
|
- '.github/deployment'
|
||||||
- '.github/workflows/venus.yml'
|
- '.github/workflows/venus.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
@@ -59,7 +60,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile-venus
|
file: ./.github/deployment/Dockerfile-venus
|
||||||
push: ${{ github.ref == 'refs/heads/master' && true || false }}
|
push: ${{ github.ref == 'refs/heads/master' && true || false }}
|
||||||
tags: ${{ steps.meta_venus.outputs.tags }}
|
tags: ${{ steps.meta_venus.outputs.tags }}
|
||||||
labels: ${{ steps.meta_venus.outputs.labels }}
|
labels: ${{ steps.meta_venus.outputs.labels }}
|
||||||
|
|||||||
55
README.md
@@ -14,7 +14,7 @@ Planning, Sorting and Creating all Together. Open-source, Privacy-First, and Fre
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Make New Badge Pattern badges inline
|
Make New Badge Pattern badges inline
|
||||||
See https://github.com/all-contributors/all-contributors/issues/361#issuecomment-637166066
|
See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
|
||||||
-->
|
-->
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
|
|
||||||
@@ -38,21 +38,29 @@ See https://github.com/all-contributors/all-contributors/issues/361#issuecomment
|
|||||||
<a href="https://t.me/affineworkos"><b>Telegram</b></a>
|
<a href="https://t.me/affineworkos"><b>Telegram</b></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center"><img width="1920" alt="affine_screen" src="https://user-images.githubusercontent.com/79301703/182363099-48b479c3-dc26-4fc3-8f9b-45f9cf358f9a.png"><p/>
|
<p align="center"><img width="1920" alt="affine_screen" src="https://user-images.githubusercontent.com/21084335/182552060-972cac0e-6258-4ccb-85bd-3bb466c30ccd.png"><p/>
|
||||||
|
|
||||||
# Stay Up-to-Date
|
# Stay Up-to-Date and Support Us
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
# How to use
|
||||||
|
|
||||||
|
If you have experience in front-end development, please [refer to here](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine); if you want to experience our latest version, please wait a moment, we will launch a web version in the near future.
|
||||||
|
And, thanks to Lee who [made a desktop build with Tauri](https://github.com/m1911star/affine-client) for you to try out.
|
||||||
|
Please notice that AFFiNE is still under Alpha stage and is not ready for production use.
|
||||||
|
|
||||||
# Table of contents
|
# Table of contents
|
||||||
|
|
||||||
- [Stay Up-to-Date](#stay-up-to-date)
|
- [Stay Up-to-Date and Support Us](#stay-up-to-date-and-support-us)
|
||||||
|
- [How to Use](#how-to-use)
|
||||||
- [Table of contents](#table-of-contents)
|
- [Table of contents](#table-of-contents)
|
||||||
- [Shape your page](#shape-your-page)
|
- [Shape your page](#shape-your-page)
|
||||||
- [Plan your task](#plan-your-task)
|
- [Plan your task](#plan-your-task)
|
||||||
- [Sort your knowledge](#sort-your-knowledge)
|
- [Sort your knowledge](#sort-your-knowledge)
|
||||||
- [Create your story](#create-your-story)
|
- [Create your story](#create-your-story)
|
||||||
- [Getting Started with development](#getting-started-with-development)
|
- [Documentation](#documentation)
|
||||||
|
- [Getting Started with development](#getting-started-with-development)
|
||||||
- [Roadmap](#roadmap)
|
- [Roadmap](#roadmap)
|
||||||
- [Releases](#releases)
|
- [Releases](#releases)
|
||||||
- [Feature requests](#feature-requests)
|
- [Feature requests](#feature-requests)
|
||||||
@@ -60,6 +68,7 @@ See https://github.com/all-contributors/all-contributors/issues/361#issuecomment
|
|||||||
- [The Philosophy of AFFiNE](#the-philosophy-of-affine)
|
- [The Philosophy of AFFiNE](#the-philosophy-of-affine)
|
||||||
- [Community](#community)
|
- [Community](#community)
|
||||||
- [Contributors](#contributors)
|
- [Contributors](#contributors)
|
||||||
|
- [Acknowledgments](#acknowledgments)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
## Shape your page
|
## Shape your page
|
||||||
@@ -80,9 +89,13 @@ We want your data always to be yours, and we don't want to make any sacrifice to
|
|||||||
Collaboration isn't only necessary for teams -- you may take and insert pics on your phone, then edit them on your desktop, and share them with your collaborators.
|
Collaboration isn't only necessary for teams -- you may take and insert pics on your phone, then edit them on your desktop, and share them with your collaborators.
|
||||||
Affine is fully built with web technologies so that consistency and accessibility are always guaranteed on Mac, Windows and Linux. The local file system support will be available when version 0.0.1beta is released.
|
Affine is fully built with web technologies so that consistency and accessibility are always guaranteed on Mac, Windows and Linux. The local file system support will be available when version 0.0.1beta is released.
|
||||||
|
|
||||||
# Getting Started with development
|
# Documentation
|
||||||
|
|
||||||
Please view the [documentation](https://affine.gitbook.io/affine/) in Contribute-to-AFFiNE/Software-Contributions/Environment-setup.
|
AFFiNE is not yet ready for production use. To install, you may check how to build or depoly the AFFiNE in [quick-start](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine/quick-start). For the full documentation, please view it [here](https://affine.gitbook.io/affine/).
|
||||||
|
|
||||||
|
## Getting Started with development
|
||||||
|
|
||||||
|
Please view the path Contribute-to-AFFiNE/Software-Contributions/Quick-Start in documentation.
|
||||||
|
|
||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
@@ -110,7 +123,7 @@ It is all perfect... If there are not so many waste operations and redundant inf
|
|||||||
That's why we are making AFFiNE. Some of the most important features are:
|
That's why we are making AFFiNE. Some of the most important features are:
|
||||||
|
|
||||||
- Transformable
|
- Transformable
|
||||||
- Every block can be transformed equally as a database
|
- Every block can be transformed equally well as a database
|
||||||
- e.g. you can now set up a to-do with MarkDown in text view and edit it in kanban view.
|
- e.g. you can now set up a to-do with MarkDown in text view and edit it in kanban view.
|
||||||
- Every doc can be turned into a whiteboard
|
- Every doc can be turned into a whiteboard
|
||||||
- An always good-to-read, structured docs-form page is the best for your notes, but a boundless doodle surface is better for collaboration and creativity.
|
- An always good-to-read, structured docs-form page is the best for your notes, but a boundless doodle surface is better for collaboration and creativity.
|
||||||
@@ -134,9 +147,17 @@ We would like to give special thanks to the innovators and pioneers who greatly
|
|||||||
|
|
||||||
We would also like to give thanks to open-source projects that make affine possible:
|
We would also like to give thanks to open-source projects that make affine possible:
|
||||||
|
|
||||||
- Yjs & Yrs
|
- [Yjs](https://github.com/yjs/yjs) & [Yrs](https://github.com/y-crdt/y-crdt) -- Fundamental support of CRDTs for our implements on state management and data sync.
|
||||||
- React
|
- [React](https://github.com/facebook/react) -- View layer support and web GUI framework.
|
||||||
- Rust
|
- [Rust](https://github.com/rust-lang/rust) -- High performance language that extends the ability and availability of our real-time backend, JWST.
|
||||||
|
- [Fossil](https://www2.fossil-scm.org/home/doc/trunk/www/index.wiki) -- Source code management tool made with CRDTs which inspired our design on block data structure.
|
||||||
|
- [slatejs](https://github.com/ianstormtaylor/slate) -- Customizable rich-text editor.
|
||||||
|
- [Jotai](https://github.com/pmndrs/jotai) -- Minimal state management tool for frontend.
|
||||||
|
- [Tldraw](https://github.com/tldraw/tldraw) -- Excellent drawing board.
|
||||||
|
- [MUI](https://github.com/mui/material-ui) -- Our most used graphic UI component library.
|
||||||
|
- Other [dependancies](https://github.com/toeverything/AFFiNE/network/dependencies)
|
||||||
|
|
||||||
|
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
|
||||||
|
|
||||||
# Community
|
# Community
|
||||||
|
|
||||||
@@ -152,16 +173,16 @@ For help, discussion about best practices, or any other conversation that would
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://darksky.eu.org/"><img src="https://avatars.githubusercontent.com/u/25152247?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DarkSky</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=darkskygit" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=darkskygit" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://darksky.eu.org/"><img src="https://avatars.githubusercontent.com/u/25152247?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DarkSky</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=darkskygit" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=darkskygit" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/tzhangchi"><img src="https://avatars.githubusercontent.com/u/5910926?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chi Zhang</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=tzhangchi" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=tzhangchi" title="Documentation">📖</a></td>
|
<td align="center"><a href="http://zhangchi.page/"><img src="https://avatars.githubusercontent.com/u/5910926?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chi Zhang</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=tzhangchi" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=tzhangchi" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/alt1o"><img src="https://avatars.githubusercontent.com/u/21084335?v=4?s=100" width="100px;" alt=""/><br /><sub><b>alt1o</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=alt1o" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=alt1o" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/alt1o"><img src="https://avatars.githubusercontent.com/u/21084335?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wang xinglong</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=alt1o" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=alt1o" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/DiamondThree"><img src="https://avatars.githubusercontent.com/u/24630517?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Diamond</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=DiamondThree" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=DiamondThree" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/DiamondThree"><img src="https://avatars.githubusercontent.com/u/24630517?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DiamondThree</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=DiamondThree" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=DiamondThree" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://lawvs.github.io/profile/"><img src="https://avatars.githubusercontent.com/u/18554747?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Whitewater</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=lawvs" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=lawvs" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://lawvs.github.io/profile/"><img src="https://avatars.githubusercontent.com/u/18554747?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Whitewater</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=lawvs" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=lawvs" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/zuoxiaodong0815"><img src="https://avatars.githubusercontent.com/u/53252747?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zuoxiaodong0815</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/zuoxiaodong0815"><img src="https://avatars.githubusercontent.com/u/53252747?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xiaodong zuo</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/SaikaSakura"><img src="https://avatars.githubusercontent.com/u/11530942?v=4?s=100" width="100px;" alt=""/><br /><sub><b>SaikaSakura</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/SaikaSakura"><img src="https://avatars.githubusercontent.com/u/11530942?v=4?s=100" width="100px;" alt=""/><br /><sub><b>MingLIang Wang</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Documentation">📖</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/QiShaoXuan"><img src="https://avatars.githubusercontent.com/u/22772830?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Qi</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/QiShaoXuan"><img src="https://avatars.githubusercontent.com/u/22772830?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Qi</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://tuluffy.github.io/angular.github.io/"><img src="https://avatars.githubusercontent.com/u/26808339?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tuluffy</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=tuluffy" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=tuluffy" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/mitsuhatu"><img src="https://avatars.githubusercontent.com/u/110213079?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mitsuhatu</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=mitsuhatu" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=mitsuhatu" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://shockwave.me/"><img src="https://avatars.githubusercontent.com/u/15013925?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Austaras</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://shockwave.me/"><img src="https://avatars.githubusercontent.com/u/15013925?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Austaras</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/uptonking?tab=repositories&type=source"><img src="https://avatars.githubusercontent.com/u/11391549?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jin Yao</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/uptonking?tab=repositories&type=source"><img src="https://avatars.githubusercontent.com/u/11391549?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jin Yao</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Documentation">📖</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
/* eslint-disable filename-rules/match */
|
/* eslint-disable filename-rules/match */
|
||||||
import {
|
import { useEffect, useRef, type UIEvent, useState } from 'react';
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
type UIEvent,
|
|
||||||
useState,
|
|
||||||
useLayoutEffect,
|
|
||||||
} from 'react';
|
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import {
|
import {
|
||||||
MuiBox as Box,
|
MuiBox as Box,
|
||||||
MuiCircularProgress as CircularProgress,
|
MuiCircularProgress as CircularProgress,
|
||||||
MuiDivider as Divider,
|
|
||||||
styled,
|
styled,
|
||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import { AffineEditor } from '@toeverything/components/affine-editor';
|
import { AffineEditor } from '@toeverything/components/affine-editor';
|
||||||
@@ -31,7 +24,7 @@ import { WorkspaceName } from './workspace-name';
|
|||||||
import { CollapsiblePageTree } from './collapsible-page-tree';
|
import { CollapsiblePageTree } from './collapsible-page-tree';
|
||||||
import { useFlag } from '@toeverything/datasource/feature-flags';
|
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||||
import { type BlockEditor } from '@toeverything/components/editor-core';
|
import { type BlockEditor } from '@toeverything/components/editor-core';
|
||||||
|
import { Tabs } from './components/tabs';
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
};
|
};
|
||||||
@@ -40,30 +33,8 @@ export function Page(props: PageProps) {
|
|||||||
const { page_id } = useParams();
|
const { page_id } = useParams();
|
||||||
const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } =
|
const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } =
|
||||||
useShowSpaceSidebar();
|
useShowSpaceSidebar();
|
||||||
const { user } = useUserAndSpaces();
|
|
||||||
const dailyNotesFlag = useFlag('BooleanDailyNotes', false);
|
const dailyNotesFlag = useFlag('BooleanDailyNotes', false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!user?.id || !page_id) return;
|
|
||||||
const updateRecentPages = async () => {
|
|
||||||
// TODO: deal with it temporarily
|
|
||||||
await services.api.editorBlock.getWorkspaceDbBlock(
|
|
||||||
props.workspace,
|
|
||||||
{
|
|
||||||
userId: user.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// await services.api.userConfig.addRecentPage(
|
|
||||||
// props.workspace,
|
|
||||||
// user.id,
|
|
||||||
// page_id
|
|
||||||
// );
|
|
||||||
await services.api.editorBlock.clearUndoRedo(props.workspace);
|
|
||||||
};
|
|
||||||
updateRecentPages();
|
|
||||||
}, [user, props.workspace, page_id]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LigoApp>
|
<LigoApp>
|
||||||
<LigoLeftContainer style={{ width: fixedDisplay ? '300px' : 0 }}>
|
<LigoLeftContainer style={{ width: fixedDisplay ? '300px' : 0 }}>
|
||||||
@@ -80,7 +51,9 @@ export function Page(props: PageProps) {
|
|||||||
onMouseLeave={() => setSpaceSidebarVisible(false)}
|
onMouseLeave={() => setSpaceSidebarVisible(false)}
|
||||||
>
|
>
|
||||||
<WorkspaceName />
|
<WorkspaceName />
|
||||||
<Divider light={true} sx={{ my: 1, margin: '6px 0px' }} />
|
|
||||||
|
<Tabs />
|
||||||
|
|
||||||
<WorkspaceSidebarContent>
|
<WorkspaceSidebarContent>
|
||||||
<div>
|
<div>
|
||||||
{dailyNotesFlag && (
|
{dailyNotesFlag && (
|
||||||
@@ -92,14 +65,14 @@ export function Page(props: PageProps) {
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<CollapsibleTitle
|
<CollapsibleTitle
|
||||||
title="Activities"
|
title="ACTIVITIES"
|
||||||
initialOpen={false}
|
initialOpen={false}
|
||||||
>
|
>
|
||||||
<Activities />
|
<Activities />
|
||||||
</CollapsibleTitle>
|
</CollapsibleTitle>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CollapsiblePageTree title="Page Tree">
|
<CollapsiblePageTree title="PAGES">
|
||||||
{page_id ? <PageTree /> : null}
|
{page_id ? <PageTree /> : null}
|
||||||
</CollapsiblePageTree>
|
</CollapsiblePageTree>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,38 +93,29 @@ const EditorContainer = ({
|
|||||||
workspace: string;
|
workspace: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [lockScroll, setLockScroll] = useState(false);
|
const [lockScroll, setLockScroll] = useState(false);
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>();
|
const [scrollContainer, setScrollContainer] = useState<HTMLElement>();
|
||||||
const editorRef = useRef<BlockEditor>();
|
const editorRef = useRef<BlockEditor>();
|
||||||
|
|
||||||
const onScroll = (event: UIEvent) => {
|
const onScroll = (event: UIEvent) => {
|
||||||
editorRef.current.getHooks().onRootNodeScroll(event);
|
editorRef.current.getHooks().onRootNodeScroll(event);
|
||||||
editorRef.current.scrollManager.emitScrollEvent(event);
|
editorRef.current.scrollManager.emitScrollEvent(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
editorRef.current.scrollManager.scrollContainer =
|
|
||||||
scrollContainerRef.current;
|
|
||||||
|
|
||||||
editorRef.current.scrollManager.scrollController = {
|
|
||||||
lockScroll: () => setLockScroll(true),
|
|
||||||
unLockScroll: () => setLockScroll(false),
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { setPageClientWidth } = usePageClientWidth();
|
const { setPageClientWidth } = usePageClientWidth();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scrollContainerRef.current) {
|
if (scrollContainer) {
|
||||||
const obv = new ResizeObserver(e => {
|
const obv = new ResizeObserver(e => {
|
||||||
setPageClientWidth(e[0].contentRect.width);
|
setPageClientWidth(e[0].contentRect.width);
|
||||||
});
|
});
|
||||||
obv.observe(scrollContainerRef.current);
|
obv.observe(scrollContainer);
|
||||||
return () => obv.disconnect();
|
return () => obv.disconnect();
|
||||||
}
|
}
|
||||||
});
|
}, [setPageClientWidth, scrollContainer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledEditorContainer
|
<StyledEditorContainer
|
||||||
lockScroll={lockScroll}
|
lockScroll={lockScroll}
|
||||||
ref={scrollContainerRef}
|
ref={ref => setScrollContainer(ref)}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
>
|
>
|
||||||
{pageId ? (
|
{pageId ? (
|
||||||
@@ -159,6 +123,11 @@ const EditorContainer = ({
|
|||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
rootBlockId={pageId}
|
rootBlockId={pageId}
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
|
scrollContainer={scrollContainer}
|
||||||
|
scrollController={{
|
||||||
|
lockScroll: () => setLockScroll(true),
|
||||||
|
unLockScroll: () => setLockScroll(false),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
@@ -198,7 +167,7 @@ const LigoLeftContainer = styled('div')({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
});
|
});
|
||||||
|
|
||||||
const WorkspaceSidebar = styled('div')(({ hidden }) => ({
|
const WorkspaceSidebar = styled('div')(({ theme }) => ({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: '48px',
|
bottom: '48px',
|
||||||
top: '12px',
|
top: '12px',
|
||||||
@@ -208,7 +177,7 @@ const WorkspaceSidebar = styled('div')(({ hidden }) => ({
|
|||||||
width: 300,
|
width: 300,
|
||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
borderRadius: '0px 10px 10px 0px',
|
borderRadius: '0px 10px 10px 0px',
|
||||||
boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: '#FFFFFF',
|
||||||
transitionProperty: 'left',
|
transitionProperty: 'left',
|
||||||
transitionDuration: '0.35s',
|
transitionDuration: '0.35s',
|
||||||
@@ -219,4 +188,5 @@ const WorkspaceSidebar = styled('div')(({ hidden }) => ({
|
|||||||
const WorkspaceSidebarContent = styled('div')({
|
const WorkspaceSidebarContent = styled('div')({
|
||||||
flex: 'auto',
|
flex: 'auto',
|
||||||
overflow: 'hidden auto',
|
overflow: 'hidden auto',
|
||||||
|
marginTop: '18px',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import clsx from 'clsx';
|
|
||||||
import style9 from 'style9';
|
|
||||||
import {
|
import {
|
||||||
MuiBox as Box,
|
MuiBox as Box,
|
||||||
MuiButton as Button,
|
MuiButton as Button,
|
||||||
MuiCollapse as Collapse,
|
MuiCollapse as Collapse,
|
||||||
MuiIconButton as IconButton,
|
MuiIconButton as IconButton,
|
||||||
|
styled,
|
||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
import {
|
||||||
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
|
ArrowDropDownIcon,
|
||||||
|
ArrowRightIcon,
|
||||||
|
} from '@toeverything/components/icons';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
import { NewpageIcon } from '@toeverything/components/common';
|
|
||||||
import {
|
import {
|
||||||
usePageTree,
|
usePageTree,
|
||||||
useCalendarHeatmap,
|
useCalendarHeatmap,
|
||||||
} from '@toeverything/components/layout';
|
} from '@toeverything/components/layout';
|
||||||
|
import { AddIcon } from '@toeverything/components/icons';
|
||||||
|
|
||||||
const styles = style9.create({
|
const StyledContainer = styled('div')({
|
||||||
ligoButton: {
|
height: '32px',
|
||||||
textTransform: 'none',
|
display: 'flex',
|
||||||
},
|
alignItems: 'center',
|
||||||
newPage: {
|
});
|
||||||
color: '#B6C7D3',
|
|
||||||
width: '26px',
|
const StyledBtn = styled('div')({
|
||||||
fontSize: '18px',
|
color: '#98ACBD',
|
||||||
textAlign: 'center',
|
textTransform: 'none',
|
||||||
cursor: 'pointer',
|
fontSize: '12px',
|
||||||
},
|
fontWeight: '600',
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CollapsiblePageTreeProps = {
|
export type CollapsiblePageTreeProps = {
|
||||||
@@ -73,31 +73,35 @@ export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingRight: 1,
|
paddingRight: 1,
|
||||||
|
'&:hover': {
|
||||||
|
background: '#f5f7f8',
|
||||||
|
borderRadius: '5px',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setNewPageBtnVisible(true)}
|
onMouseEnter={() => setNewPageBtnVisible(true)}
|
||||||
onMouseLeave={() => setNewPageBtnVisible(false)}
|
onMouseLeave={() => setNewPageBtnVisible(false)}
|
||||||
>
|
>
|
||||||
<Button
|
<StyledContainer>
|
||||||
startIcon={
|
{open ? (
|
||||||
open ? <ArrowDropDownIcon /> : <ArrowRightIcon />
|
<ArrowDropDownIcon sx={{ color: '#566B7D' }} />
|
||||||
}
|
) : (
|
||||||
onClick={() => setOpen(prev => !prev)}
|
<ArrowRightIcon sx={{ color: '#566B7D' }} />
|
||||||
sx={{ color: '#566B7D', textTransform: 'none' }}
|
)}
|
||||||
className={clsx(styles('ligoButton'), className)}
|
<StyledBtn onClick={() => setOpen(prev => !prev)}>
|
||||||
style={style}
|
{title}
|
||||||
disableElevation
|
</StyledBtn>
|
||||||
disableRipple
|
</StyledContainer>
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{newPageBtnVisible && (
|
{newPageBtnVisible && (
|
||||||
<div
|
<AddIcon
|
||||||
|
style={{
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
color: '#98ACBD',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
onClick={create_page}
|
onClick={create_page}
|
||||||
className={clsx(styles('newPage'), className)}
|
/>
|
||||||
>
|
|
||||||
+
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{children ? (
|
{children ? (
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export const Logo = ({ color, style, ...props }) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="52"
|
||||||
|
height="52"
|
||||||
|
viewBox="0 0 52 52"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect width="52" height="52" rx="10" fill="#3E6FDB" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M24.2189 11.4392L14.6321 38.7943H20.2472L26.3453 19.8747L32.4461 38.7943H38.0423L28.454 11.4392H24.2189Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { Logo } from './Logo';
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { MuiDivider as Divider, styled } from '@toeverything/components/ui';
|
||||||
|
import type { ValueOf } from '@toeverything/utils';
|
||||||
|
|
||||||
|
const StyledTabs = styled('div')({
|
||||||
|
width: '100%',
|
||||||
|
height: '12px',
|
||||||
|
marginTop: '12px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledDivider = styled(Divider)<{ isActive?: boolean }>(
|
||||||
|
({ isActive }) => {
|
||||||
|
return {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: isActive ? '#3E6FDB' : '#ECF1FB',
|
||||||
|
borderWidth: '2px',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const TAB_TITLE = {
|
||||||
|
PAGES: 'pages',
|
||||||
|
GALLERY: 'gallery',
|
||||||
|
TOC: 'toc',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const TabMap = new Map<TabKey, TabValue>([
|
||||||
|
['PAGES', 'pages'],
|
||||||
|
['GALLERY', 'gallery'],
|
||||||
|
['TOC', 'toc'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
type TabKey = keyof typeof TAB_TITLE;
|
||||||
|
type TabValue = ValueOf<typeof TAB_TITLE>;
|
||||||
|
|
||||||
|
const Tabs = () => {
|
||||||
|
const [activeTab, setActiveTab] = useState<TabValue>(TAB_TITLE.PAGES);
|
||||||
|
|
||||||
|
const onClick = (v: TabValue) => {
|
||||||
|
setActiveTab(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledTabs>
|
||||||
|
{[...TabMap.entries()].map(([k, v]) => {
|
||||||
|
return (
|
||||||
|
<StyledDivider
|
||||||
|
key={v}
|
||||||
|
isActive={v === activeTab}
|
||||||
|
onClick={() => onClick(v)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</StyledTabs>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Tabs };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { Tabs } from './Tabs';
|
||||||
@@ -1,27 +1,28 @@
|
|||||||
import {
|
import {
|
||||||
MuiButton as Button,
|
|
||||||
Switch,
|
|
||||||
styled,
|
styled,
|
||||||
MuiOutlinedInput as OutlinedInput,
|
MuiOutlinedInput as OutlinedInput,
|
||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import { LogoIcon } from '@toeverything/components/icons';
|
import { PinIcon } from '@toeverything/components/icons';
|
||||||
import {
|
import {
|
||||||
useUserAndSpaces,
|
useUserAndSpaces,
|
||||||
useShowSpaceSidebar,
|
useShowSpaceSidebar,
|
||||||
} from '@toeverything/datasource/state';
|
} from '@toeverything/datasource/state';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
|
import { Logo } from './components/logo/Logo';
|
||||||
|
|
||||||
const WorkspaceContainer = styled('div')({
|
const WorkspaceContainer = styled('div')({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
flexDirection: 'column',
|
||||||
minHeight: 60,
|
paddingBottom: '12px',
|
||||||
padding: '12px 0px',
|
|
||||||
color: '#566B7D',
|
color: '#566B7D',
|
||||||
});
|
});
|
||||||
const LeftContainer = styled('div')({
|
const LeftContainer = styled('div')({
|
||||||
flex: 'auto',
|
flex: 'auto',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
height: '52px',
|
||||||
|
alignItems: 'center',
|
||||||
|
margin: '0 12px',
|
||||||
});
|
});
|
||||||
|
|
||||||
const LogoContainer = styled('div')({
|
const LogoContainer = styled('div')({
|
||||||
@@ -32,20 +33,36 @@ const LogoContainer = styled('div')({
|
|||||||
minWidth: 24,
|
minWidth: 24,
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledLogoIcon = styled(LogoIcon)(({ theme }) => {
|
const StyledPin = styled('div')({
|
||||||
return {
|
display: 'flex',
|
||||||
color: theme.affine.palette.primary,
|
justifyContent: 'end',
|
||||||
width: '16px !important',
|
alignItems: 'center',
|
||||||
height: '16px !important',
|
});
|
||||||
};
|
|
||||||
|
const StyledWorkspace = styled('div')({
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginLeft: '12px',
|
||||||
|
paddingLeft: '12px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledWorkspaceDesc = styled('div')({
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#98ACBD',
|
||||||
|
height: '18px',
|
||||||
|
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
});
|
});
|
||||||
|
|
||||||
const WorkspaceNameContainer = styled('div')({
|
const WorkspaceNameContainer = styled('div')({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flex: 'auto',
|
flex: 'auto',
|
||||||
width: '100px',
|
width: '165px',
|
||||||
marginRight: '10px',
|
height: '34px',
|
||||||
input: {
|
input: {
|
||||||
padding: '5px 10px',
|
padding: '5px 10px',
|
||||||
},
|
},
|
||||||
@@ -58,21 +75,15 @@ const WorkspaceNameContainer = styled('div')({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const WorkspaceReNameContainer = styled('div')({
|
const WorkspaceReNameContainer = styled('div')({
|
||||||
marginRight: '10px',
|
height: '34px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
|
||||||
input: {
|
input: {
|
||||||
padding: '5px 10px',
|
padding: '5px 10px',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ToggleDisplayContainer = styled('div')({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
fontSize: 12,
|
|
||||||
color: '#3E6FDB',
|
|
||||||
padding: 6,
|
|
||||||
minWidth: 64,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const WorkspaceName = () => {
|
export const WorkspaceName = () => {
|
||||||
const { currentSpaceId } = useUserAndSpaces();
|
const { currentSpaceId } = useUserAndSpaces();
|
||||||
const { fixedDisplay, toggleSpaceSidebar } = useShowSpaceSidebar();
|
const { fixedDisplay, toggleSpaceSidebar } = useShowSpaceSidebar();
|
||||||
@@ -139,35 +150,46 @@ export const WorkspaceName = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceContainer>
|
<WorkspaceContainer>
|
||||||
|
<StyledPin>
|
||||||
|
<PinIcon
|
||||||
|
style={{
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
color: fixedDisplay ? '#3E6FDB' : '',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onClick={toggleSpaceSidebar}
|
||||||
|
/>
|
||||||
|
</StyledPin>
|
||||||
<LeftContainer>
|
<LeftContainer>
|
||||||
<LogoContainer>
|
<LogoContainer>
|
||||||
<StyledLogoIcon />
|
<Logo color={undefined} style={undefined} />
|
||||||
</LogoContainer>
|
</LogoContainer>
|
||||||
|
|
||||||
{inRename ? (
|
<StyledWorkspace>
|
||||||
<WorkspaceReNameContainer>
|
{inRename ? (
|
||||||
<OutlinedInput
|
<WorkspaceReNameContainer>
|
||||||
value={workspaceName}
|
<OutlinedInput
|
||||||
onChange={handleChange}
|
style={{ width: '140px', height: '28px' }}
|
||||||
onKeyDown={handleKeyDown}
|
value={workspaceName}
|
||||||
onMouseLeave={() => setInRename(false)}
|
onChange={handleChange}
|
||||||
/>
|
onKeyDown={handleKeyDown}
|
||||||
</WorkspaceReNameContainer>
|
onMouseLeave={() => setInRename(false)}
|
||||||
) : (
|
/>
|
||||||
<WorkspaceNameContainer>
|
</WorkspaceReNameContainer>
|
||||||
<span onClick={() => setInRename(true)}>
|
) : (
|
||||||
{workspaceName || workspaceId}
|
<WorkspaceNameContainer>
|
||||||
</span>
|
<span onClick={() => setInRename(true)}>
|
||||||
</WorkspaceNameContainer>
|
{workspaceName || workspaceId}
|
||||||
)}
|
</span>
|
||||||
|
</WorkspaceNameContainer>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<StyledWorkspaceDesc>
|
||||||
|
To shape, Not to Adapt.
|
||||||
|
</StyledWorkspaceDesc>
|
||||||
|
</StyledWorkspace>
|
||||||
</LeftContainer>
|
</LeftContainer>
|
||||||
<ToggleDisplayContainer onClick={toggleSpaceSidebar}>
|
|
||||||
<Switch
|
|
||||||
checked={fixedDisplay}
|
|
||||||
checkedLabel="ON"
|
|
||||||
uncheckedLabel="OFF"
|
|
||||||
/>
|
|
||||||
</ToggleDisplayContainer>
|
|
||||||
</WorkspaceContainer>
|
</WorkspaceContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"mini-css-extract-plugin": "^2.6.1",
|
"mini-css-extract-plugin": "^2.6.1",
|
||||||
"webpack": "^5.73.0"
|
"image-minimizer-webpack-plugin": "^3.2.3",
|
||||||
|
"imagemin": "^8.0.1",
|
||||||
|
"imagemin-optipng": "^8.0.0",
|
||||||
|
"webpack": "^5.74.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
apps/venus/src/app/collaboration.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
@@ -6,13 +6,18 @@ import clsx from 'clsx';
|
|||||||
|
|
||||||
import { CssVarsProvider, styled } from '@mui/joy/styles';
|
import { CssVarsProvider, styled } from '@mui/joy/styles';
|
||||||
import { Box, Button, Container, Grid, SvgIcon, Typography } from '@mui/joy';
|
import { Box, Button, Container, Grid, SvgIcon, Typography } from '@mui/joy';
|
||||||
import Card from '@mui/joy/Card';
|
|
||||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||||
import RedditIcon from '@mui/icons-material/Reddit';
|
import RedditIcon from '@mui/icons-material/Reddit';
|
||||||
import TelegramIcon from '@mui/icons-material/Telegram';
|
import TelegramIcon from '@mui/icons-material/Telegram';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { useMediaQuery } from '@mui/material';
|
import { useMediaQuery } from '@mui/material';
|
||||||
|
|
||||||
|
import LogoImage from './logo.png';
|
||||||
|
import CollaborationImage from './collaboration.png';
|
||||||
|
import PageImage from './page.png';
|
||||||
|
import ShapeImage from './shape.png';
|
||||||
|
import TaskImage from './task.png';
|
||||||
|
|
||||||
const DiscordIcon = (props: any) => {
|
const DiscordIcon = (props: any) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
@@ -20,12 +25,12 @@ const DiscordIcon = (props: any) => {
|
|||||||
width="71"
|
width="71"
|
||||||
height="55"
|
height="55"
|
||||||
viewBox="0 0 71 55"
|
viewBox="0 0 71 55"
|
||||||
fill="none"
|
fill="currentcolor"
|
||||||
>
|
>
|
||||||
<g clip-path="url(#clip0)">
|
<g clip-path="url(#clip0)">
|
||||||
<path
|
<path
|
||||||
d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z"
|
d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z"
|
||||||
fill="#23272A"
|
fill="currentcolor"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
<defs>
|
<defs>
|
||||||
@@ -407,10 +412,7 @@ export function App() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AffineImage
|
<AffineImage src={PageImage} alt="AFFiNE main ui" />
|
||||||
src="/assets/page.png"
|
|
||||||
alt="AFFiNE main ui"
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid xs={12} sx={{ display: 'flex' }}>
|
<Grid xs={12} sx={{ display: 'flex' }}>
|
||||||
@@ -515,14 +517,16 @@ export function App() {
|
|||||||
justifyContent: 'left',
|
justifyContent: 'left',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
transition: 'all .5s',
|
transition: 'all .5s',
|
||||||
// boxShadow: '2px 2px 40px #08f2',
|
transform: 'scale(0.98)',
|
||||||
// ':hover': {
|
boxShadow: '2px 2px 40px #0002',
|
||||||
// boxShadow: '2px 2px 40px #08f4',
|
':hover': {
|
||||||
// },
|
transform: 'scale(1)',
|
||||||
|
boxShadow: '2px 2px 40px #0004',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AffineImage
|
<AffineImage
|
||||||
src="/assets/shape.png"
|
src={ShapeImage}
|
||||||
alt="AFFiNE Shape Your Page"
|
alt="AFFiNE Shape Your Page"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -537,10 +541,10 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Grid
|
<Grid
|
||||||
xs={matches ? 12 : 4}
|
xs={matches ? 12 : 6}
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
...(matches ? {} : { marginRight: '4em' }),
|
...(matches ? {} : { marginLeft: '4em' }),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@@ -584,7 +588,7 @@ export function App() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
xs={matches ? 12 : 8}
|
xs={matches ? 12 : 6}
|
||||||
sx={{ display: 'flex', width: '100%' }}
|
sx={{ display: 'flex', width: '100%' }}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@@ -595,14 +599,16 @@ export function App() {
|
|||||||
justifyContent: 'left',
|
justifyContent: 'left',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
transition: 'all .5s',
|
transition: 'all .5s',
|
||||||
// boxShadow: '2px 2px 40px #08f2',
|
transform: 'scale(0.98)',
|
||||||
// ':hover': {
|
boxShadow: '2px 2px 40px #0002',
|
||||||
// boxShadow: '2px 2px 40px #08f4',
|
':hover': {
|
||||||
// },
|
transform: 'scale(1)',
|
||||||
|
boxShadow: '2px 2px 40px #0004',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AffineImage
|
<AffineImage
|
||||||
src="/assets/task.png"
|
src={TaskImage}
|
||||||
alt="AFFiNE Plan Your Task"
|
alt="AFFiNE Plan Your Task"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -656,14 +662,14 @@ export function App() {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
transition: 'all .5s',
|
transition: 'all .5s',
|
||||||
// boxShadow: '2px 2px 40px #08f2',
|
transform: 'scale(0.98)',
|
||||||
// ':hover': {
|
':hover': {
|
||||||
// boxShadow: '2px 2px 40px #08f4',
|
transform: 'scale(1)',
|
||||||
// },
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AffineImage
|
<AffineImage
|
||||||
src="/assets/collaboration.png"
|
src={CollaborationImage}
|
||||||
alt="AFFiNE Privacy-first, and collaborative"
|
alt="AFFiNE Privacy-first, and collaborative"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -676,7 +682,7 @@ export function App() {
|
|||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AffineImage src="/assets/logo.png" alt="AFFiNE Logo" />
|
<AffineImage src={LogoImage} alt="AFFiNE Logo" />
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid xs={12} sx={{ display: 'flex' }}>
|
<Grid xs={12} sx={{ display: 'flex' }}>
|
||||||
@@ -738,13 +744,20 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', width: '100%' }}>
|
<Box sx={{ display: 'flex', width: '100%' }}>
|
||||||
<Card
|
<Button
|
||||||
|
variant="plain"
|
||||||
sx={{
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
minWidth: '4em',
|
padding: '1em',
|
||||||
'--Card-padding': '4px',
|
minWidth: '6em',
|
||||||
'--Card-radius': '0',
|
|
||||||
}}
|
}}
|
||||||
|
onClick={() =>
|
||||||
|
window.open(
|
||||||
|
'https://github.com/toeverything/AFFiNE/'
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Grid
|
<Grid
|
||||||
xs={12}
|
xs={12}
|
||||||
@@ -767,21 +780,30 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ display: 'flex', color: '#888' }}
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
color: '#888',
|
||||||
|
fontSize: '0.5em',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
GitHub
|
GitHub
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Card>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', width: '100%' }}>
|
<Box sx={{ display: 'flex', width: '100%' }}>
|
||||||
<Card
|
<Button
|
||||||
|
variant="plain"
|
||||||
sx={{
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
minWidth: '4em',
|
padding: '1em',
|
||||||
'--Card-padding': '4px',
|
minWidth: '6em',
|
||||||
'--Card-radius': '0',
|
|
||||||
}}
|
}}
|
||||||
|
onClick={() =>
|
||||||
|
window.open('https://www.reddit.com/r/Affine/')
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Grid
|
<Grid
|
||||||
xs={12}
|
xs={12}
|
||||||
@@ -804,12 +826,16 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ display: 'flex', color: '#888' }}
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
color: '#888',
|
||||||
|
fontSize: '0.5em',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Reddit
|
Reddit
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Card>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -817,13 +843,18 @@ export function App() {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card
|
<Button
|
||||||
|
variant="plain"
|
||||||
sx={{
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
minWidth: '4em',
|
padding: '1em',
|
||||||
'--Card-padding': '4px',
|
minWidth: '6em',
|
||||||
'--Card-radius': '0',
|
|
||||||
}}
|
}}
|
||||||
|
onClick={() =>
|
||||||
|
window.open('https://t.me/affineworkos')
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Grid
|
<Grid
|
||||||
xs={12}
|
xs={12}
|
||||||
@@ -846,21 +877,30 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ display: 'flex', color: '#888' }}
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
color: '#888',
|
||||||
|
fontSize: '0.5em',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Telegram
|
Telegram
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Card>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', width: '100%' }}>
|
<Box sx={{ display: 'flex', width: '100%' }}>
|
||||||
<Card
|
<Button
|
||||||
|
variant="plain"
|
||||||
sx={{
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
minWidth: '4em',
|
padding: '1em',
|
||||||
'--Card-padding': '4px',
|
minWidth: '6em',
|
||||||
'--Card-radius': '0',
|
|
||||||
}}
|
}}
|
||||||
|
onClick={() =>
|
||||||
|
window.open('https://discord.gg/yz6tGVsf5p')
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Grid
|
<Grid
|
||||||
xs={12}
|
xs={12}
|
||||||
@@ -870,7 +910,11 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DiscordIcon
|
<DiscordIcon
|
||||||
sx={{ width: '36px', height: '36px' }}
|
sx={{
|
||||||
|
width: '36px',
|
||||||
|
height: '36px',
|
||||||
|
color: '#09449d',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
@@ -883,12 +927,16 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ display: 'flex', color: '#888' }}
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
color: '#888',
|
||||||
|
fontSize: '0.5em',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Discord
|
Discord
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Card>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Grid xs={12} sx={{ display: 'flex', marginBottom: '2em' }}>
|
<Grid xs={12} sx={{ display: 'flex', marginBottom: '2em' }}>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
BIN
apps/venus/src/app/page.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
apps/venus/src/app/shape.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
apps/venus/src/app/task.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
@@ -1,10 +0,0 @@
|
|||||||
<svg width="71" height="55" viewBox="0 0 71 55" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0)">
|
|
||||||
<path d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z" fill="#23272A"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0">
|
|
||||||
<rect width="71" height="55" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -7,6 +7,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||||
const CompressionPlugin = require('compression-webpack-plugin');
|
const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
|
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
const Style9Plugin = require('style9/webpack');
|
const Style9Plugin = require('style9/webpack');
|
||||||
|
|
||||||
@@ -61,6 +62,14 @@ module.exports = function (webpackConfig) {
|
|||||||
parallel: true,
|
parallel: true,
|
||||||
}),
|
}),
|
||||||
new CssMinimizerPlugin(),
|
new CssMinimizerPlugin(),
|
||||||
|
new ImageMinimizerPlugin({
|
||||||
|
minimizer: {
|
||||||
|
implementation: ImageMinimizerPlugin.imageminMinify,
|
||||||
|
options: {
|
||||||
|
plugins: [['optipng', { optimizationLevel: 5 }]],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ interface AffineEditorProps {
|
|||||||
scrollBlank?: boolean;
|
scrollBlank?: boolean;
|
||||||
|
|
||||||
isWhiteboard?: boolean;
|
isWhiteboard?: boolean;
|
||||||
|
|
||||||
|
scrollContainer?: HTMLElement;
|
||||||
|
scrollController?: {
|
||||||
|
lockScroll: () => void;
|
||||||
|
unLockScroll: () => void;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function _useConstantWithDispose(
|
function _useConstantWithDispose(
|
||||||
@@ -53,13 +59,32 @@ function _useConstantWithDispose(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
|
export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
|
||||||
({ workspace, rootBlockId, scrollBlank = true, isWhiteboard }, ref) => {
|
(
|
||||||
|
{
|
||||||
|
workspace,
|
||||||
|
rootBlockId,
|
||||||
|
scrollBlank = true,
|
||||||
|
isWhiteboard,
|
||||||
|
scrollController,
|
||||||
|
scrollContainer,
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const editor = _useConstantWithDispose(
|
const editor = _useConstantWithDispose(
|
||||||
workspace,
|
workspace,
|
||||||
rootBlockId,
|
rootBlockId,
|
||||||
isWhiteboard
|
isWhiteboard
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (scrollContainer) {
|
||||||
|
editor.scrollManager.scrollContainer = scrollContainer;
|
||||||
|
}
|
||||||
|
if (scrollController) {
|
||||||
|
editor.scrollManager.scrollController = scrollController;
|
||||||
|
}
|
||||||
|
}, [editor, scrollContainer, scrollController]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => editor);
|
useImperativeHandle(ref, () => editor);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { ErrorBoundary } from 'react-error-boundary';
|
|||||||
import { ErrorFallback } from './components/error-fallback';
|
import { ErrorFallback } from './components/error-fallback';
|
||||||
import { ZoomBar } from './components/zoom-bar';
|
import { ZoomBar } from './components/zoom-bar';
|
||||||
import { CommandPanel } from './components/command-panel';
|
import { CommandPanel } from './components/command-panel';
|
||||||
|
import { usePageClientWidth } from '@toeverything/datasource/state';
|
||||||
|
|
||||||
export interface TldrawProps extends TldrawAppCtorProps {
|
export interface TldrawProps extends TldrawAppCtorProps {
|
||||||
/**
|
/**
|
||||||
@@ -132,6 +133,9 @@ export function Tldraw({
|
|||||||
tools,
|
tools,
|
||||||
}: TldrawProps) {
|
}: TldrawProps) {
|
||||||
const [sId, set_sid] = React.useState(id);
|
const [sId, set_sid] = React.useState(id);
|
||||||
|
const { pageClientWidth } = usePageClientWidth();
|
||||||
|
// page padding left and right total 300px
|
||||||
|
const editorShapeInitSize = pageClientWidth - 300;
|
||||||
|
|
||||||
// Create a new app when the component mounts.
|
// Create a new app when the component mounts.
|
||||||
const [app, setApp] = React.useState(() => {
|
const [app, setApp] = React.useState(() => {
|
||||||
@@ -140,6 +144,7 @@ export function Tldraw({
|
|||||||
callbacks,
|
callbacks,
|
||||||
commands,
|
commands,
|
||||||
getSession,
|
getSession,
|
||||||
|
editorShapeInitSize,
|
||||||
tools,
|
tools,
|
||||||
});
|
});
|
||||||
return app;
|
return app;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { StrokeLineStyleConfig } from './stroke-line-style-config';
|
|||||||
import { Group, UnGroup } from './GroupOperation';
|
import { Group, UnGroup } from './GroupOperation';
|
||||||
import { DeleteShapes } from './DeleteOperation';
|
import { DeleteShapes } from './DeleteOperation';
|
||||||
import { Lock, Unlock } from './LockOperation';
|
import { Lock, Unlock } from './LockOperation';
|
||||||
|
import { FrameFillColorConfig } from './FrameFillColorConfig';
|
||||||
|
|
||||||
export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
||||||
const state = app.useStore();
|
const state = app.useStore();
|
||||||
@@ -51,6 +52,13 @@ export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
|||||||
shapes={config.fill.selectedShapes}
|
shapes={config.fill.selectedShapes}
|
||||||
/>
|
/>
|
||||||
) : null,
|
) : null,
|
||||||
|
frameFill: config.frameFill.selectedShapes.length ? (
|
||||||
|
<FrameFillColorConfig
|
||||||
|
key="fill"
|
||||||
|
app={app}
|
||||||
|
shapes={config.frameFill.selectedShapes}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
font: config.font.selectedShapes.length ? (
|
font: config.font.selectedShapes.length ? (
|
||||||
<FontSizeConfig
|
<FontSizeConfig
|
||||||
key="font"
|
key="font"
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import type { FC } from 'react';
|
||||||
|
import type { TldrawApp } from '@toeverything/components/board-state';
|
||||||
|
import type { TDShape } from '@toeverything/components/board-types';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
Tooltip,
|
||||||
|
IconButton,
|
||||||
|
useTheme,
|
||||||
|
} from '@toeverything/components/ui';
|
||||||
|
import {
|
||||||
|
ShapeColorNoneIcon,
|
||||||
|
ShapeColorDuotoneIcon,
|
||||||
|
} from '@toeverything/components/icons';
|
||||||
|
import { countBy, maxBy } from '@toeverything/utils';
|
||||||
|
import { getShapeIds } from './utils';
|
||||||
|
import { Palette } from '../palette';
|
||||||
|
|
||||||
|
interface BorderColorConfigProps {
|
||||||
|
app: TldrawApp;
|
||||||
|
shapes: TDShape[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type ColorType = 'none' | string;
|
||||||
|
|
||||||
|
const _colors: ColorType[] = [
|
||||||
|
'rgba(255, 133, 137, 0.5)',
|
||||||
|
'rgba(255, 159, 101, 0.5)',
|
||||||
|
'rgba(255, 251, 69, 0.5)',
|
||||||
|
'rgba(64, 255, 138, 0.5)',
|
||||||
|
'rgba(26, 252, 255, 0.5)',
|
||||||
|
'rgba(198, 156, 255, 0.5)',
|
||||||
|
'rgba(255, 143, 224, 0.5)',
|
||||||
|
'rgba(152, 172, 189, 0.5)',
|
||||||
|
'rgba(216, 226, 248, 0.5)',
|
||||||
|
];
|
||||||
|
|
||||||
|
const _getIconRenderColor = (shapes: TDShape[]) => {
|
||||||
|
const counted = countBy(shapes, shape => shape.style.fill);
|
||||||
|
const max = maxBy(Object.entries(counted), ([c, n]) => n);
|
||||||
|
return max[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FrameFillColorConfig: FC<BorderColorConfigProps> = ({
|
||||||
|
app,
|
||||||
|
shapes,
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const setFillColor = (color: ColorType) => {
|
||||||
|
app.style(
|
||||||
|
{ fill: color, isFilled: color !== 'none' },
|
||||||
|
getShapeIds(shapes)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconColor = _getIconRenderColor(shapes);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
trigger="hover"
|
||||||
|
placement="bottom-start"
|
||||||
|
content={
|
||||||
|
<Palette
|
||||||
|
colors={_colors}
|
||||||
|
selected={iconColor}
|
||||||
|
onSelect={setFillColor}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Tooltip content="Frame Background Color" placement="top-start">
|
||||||
|
<IconButton>
|
||||||
|
{iconColor === 'none' ? (
|
||||||
|
<ShapeColorNoneIcon />
|
||||||
|
) : (
|
||||||
|
<ShapeColorDuotoneIcon
|
||||||
|
style={{
|
||||||
|
color: iconColor,
|
||||||
|
border:
|
||||||
|
iconColor === '#FFFFFF'
|
||||||
|
? `1px solid ${theme.affine.palette.tagHover}`
|
||||||
|
: 0,
|
||||||
|
borderRadius: '5px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -7,6 +7,7 @@ interface Config {
|
|||||||
type:
|
type:
|
||||||
| 'stroke'
|
| 'stroke'
|
||||||
| 'fill'
|
| 'fill'
|
||||||
|
| 'frameFill'
|
||||||
| 'font'
|
| 'font'
|
||||||
| 'group'
|
| 'group'
|
||||||
| 'ungroup'
|
| 'ungroup'
|
||||||
@@ -22,6 +23,10 @@ const _createInitConfig = (): Record<Config['type'], Config> => {
|
|||||||
type: 'fill',
|
type: 'fill',
|
||||||
selectedShapes: [],
|
selectedShapes: [],
|
||||||
},
|
},
|
||||||
|
frameFill: {
|
||||||
|
type: 'frameFill',
|
||||||
|
selectedShapes: [],
|
||||||
|
},
|
||||||
stroke: {
|
stroke: {
|
||||||
type: 'stroke',
|
type: 'stroke',
|
||||||
selectedShapes: [],
|
selectedShapes: [],
|
||||||
@@ -64,6 +69,7 @@ const _isSupportStroke = (shape: TDShape): boolean => {
|
|||||||
TDShapeType.Pencil,
|
TDShapeType.Pencil,
|
||||||
TDShapeType.Laser,
|
TDShapeType.Laser,
|
||||||
TDShapeType.Highlight,
|
TDShapeType.Highlight,
|
||||||
|
TDShapeType.Draw,
|
||||||
TDShapeType.Arrow,
|
TDShapeType.Arrow,
|
||||||
TDShapeType.Line,
|
TDShapeType.Line,
|
||||||
].some(type => type === shape.type);
|
].some(type => type === shape.type);
|
||||||
@@ -91,6 +97,10 @@ const _isSupportFont = (shape: TDShape): boolean => {
|
|||||||
].some(type => type === shape.type);
|
].some(type => type === shape.type);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _isSupportFrameFill = (shape: TDShape): boolean => {
|
||||||
|
return shape.type === TDShapeType.Frame;
|
||||||
|
};
|
||||||
|
|
||||||
export const useConfig = (app: TldrawApp): Record<Config['type'], Config> => {
|
export const useConfig = (app: TldrawApp): Record<Config['type'], Config> => {
|
||||||
const state = app.useStore();
|
const state = app.useStore();
|
||||||
const selectedShapes = TLDR.get_selected_shapes(state, app.currentPageId);
|
const selectedShapes = TLDR.get_selected_shapes(state, app.currentPageId);
|
||||||
@@ -105,6 +115,9 @@ export const useConfig = (app: TldrawApp): Record<Config['type'], Config> => {
|
|||||||
if (_isSupportFont(cur)) {
|
if (_isSupportFont(cur)) {
|
||||||
acc.font.selectedShapes.push(cur);
|
acc.font.selectedShapes.push(cur);
|
||||||
}
|
}
|
||||||
|
if (_isSupportFrameFill(cur)) {
|
||||||
|
acc.frameFill.selectedShapes.push(cur);
|
||||||
|
}
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
_createInitConfig()
|
_createInitConfig()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
TldrawPatch,
|
TldrawPatch,
|
||||||
TDShape,
|
TDShape,
|
||||||
TDStatus,
|
TDStatus,
|
||||||
|
TDShapeType,
|
||||||
} from '@toeverything/components/board-types';
|
} from '@toeverything/components/board-types';
|
||||||
import { TLDR } from '@toeverything/components/board-state';
|
import { TLDR } from '@toeverything/components/board-state';
|
||||||
import { BaseSession } from './base-session';
|
import { BaseSession } from './base-session';
|
||||||
@@ -75,6 +76,10 @@ export class RotateSession extends BaseSession {
|
|||||||
app: { currentPageId, currentPoint, shiftKey },
|
app: { currentPageId, currentPoint, shiftKey },
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
|
const filteredShapes = initialShapes.filter(
|
||||||
|
shape => shape.shape.type !== TDShapeType.Editor
|
||||||
|
);
|
||||||
|
|
||||||
const shapes: Record<string, Partial<TDShape>> = {};
|
const shapes: Record<string, Partial<TDShape>> = {};
|
||||||
|
|
||||||
let directionDelta =
|
let directionDelta =
|
||||||
@@ -85,7 +90,7 @@ export class RotateSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the shapes
|
// Update the shapes
|
||||||
initialShapes.forEach(({ center, shape }) => {
|
filteredShapes.forEach(({ center, shape }) => {
|
||||||
const { rotation = 0 } = shape;
|
const { rotation = 0 } = shape;
|
||||||
let shapeDelta = 0;
|
let shapeDelta = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import * as React from 'react';
|
/* eslint-disable no-restricted-syntax */
|
||||||
import { Utils, SVGContainer } from '@tldraw/core';
|
import { Utils, SVGContainer } from '@tldraw/core';
|
||||||
import {
|
import {
|
||||||
FrameShape,
|
FrameShape,
|
||||||
DashStyle,
|
|
||||||
TDShapeType,
|
TDShapeType,
|
||||||
TDMeta,
|
TDMeta,
|
||||||
GHOSTED_OPACITY,
|
|
||||||
LABEL_POINT,
|
|
||||||
} from '@toeverything/components/board-types';
|
} from '@toeverything/components/board-types';
|
||||||
import { TDShapeUtil } from '../TDShapeUtil';
|
import { TDShapeUtil } from '../TDShapeUtil';
|
||||||
import {
|
import {
|
||||||
@@ -14,14 +11,13 @@ import {
|
|||||||
getShapeStyle,
|
getShapeStyle,
|
||||||
getBoundsRectangle,
|
getBoundsRectangle,
|
||||||
transformRectangle,
|
transformRectangle,
|
||||||
getFontStyle,
|
|
||||||
transformSingleRectangle,
|
transformSingleRectangle,
|
||||||
} from '../shared';
|
} from '../shared';
|
||||||
import { DrawFrame } from './components/draw-frame';
|
import { Frame } from './components/Frame';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
|
|
||||||
type T = FrameShape;
|
type T = FrameShape;
|
||||||
type E = HTMLDivElement;
|
type E = SVGSVGElement;
|
||||||
|
|
||||||
export class FrameUtil extends TDShapeUtil<T, E> {
|
export class FrameUtil extends TDShapeUtil<T, E> {
|
||||||
type = TDShapeType.Frame as const;
|
type = TDShapeType.Frame as const;
|
||||||
@@ -56,10 +52,7 @@ export class FrameUtil extends TDShapeUtil<T, E> {
|
|||||||
(
|
(
|
||||||
{
|
{
|
||||||
shape,
|
shape,
|
||||||
isEditing,
|
|
||||||
isBinding,
|
|
||||||
isSelected,
|
isSelected,
|
||||||
isGhost,
|
|
||||||
meta,
|
meta,
|
||||||
bounds,
|
bounds,
|
||||||
events,
|
events,
|
||||||
@@ -70,21 +63,20 @@ export class FrameUtil extends TDShapeUtil<T, E> {
|
|||||||
) => {
|
) => {
|
||||||
const { id, size, style } = shape;
|
const { id, size, style } = shape;
|
||||||
return (
|
return (
|
||||||
<FullWrapper ref={ref} {...events}>
|
<SVGContainer
|
||||||
<SVGContainer
|
ref={ref}
|
||||||
id={shape.id + '_svg'}
|
{...events}
|
||||||
opacity={1}
|
id={shape.id + '_svg'}
|
||||||
fill={'#fff'}
|
opacity={1}
|
||||||
>
|
>
|
||||||
<DrawFrame
|
<Frame
|
||||||
id={id}
|
id={id}
|
||||||
style={style}
|
style={style}
|
||||||
size={size}
|
size={size}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
isDarkMode={meta.isDarkMode}
|
isDarkMode={meta.isDarkMode}
|
||||||
/>
|
/>
|
||||||
</SVGContainer>
|
</SVGContainer>
|
||||||
</FullWrapper>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -121,27 +113,9 @@ export class FrameUtil extends TDShapeUtil<T, E> {
|
|||||||
override transform = transformRectangle;
|
override transform = transformRectangle;
|
||||||
|
|
||||||
override transformSingle = transformSingleRectangle;
|
override transformSingle = transformSingleRectangle;
|
||||||
|
|
||||||
override hitTestPoint = (shape: T, point: number[]): boolean => {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
override hitTestLineSegment = (
|
|
||||||
shape: T,
|
|
||||||
A: number[],
|
|
||||||
B: number[]
|
|
||||||
): boolean => {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const FullWrapper = styled('div')({
|
const FullWrapper = styled('div')({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
'.tl-fill-hitarea': {
|
|
||||||
fill: '#F7F9FF',
|
|
||||||
},
|
|
||||||
'.tl-stroke-hitarea': {
|
|
||||||
fill: '#F7F9FF',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { BINDING_DISTANCE } from '@toeverything/components/board-types';
|
||||||
|
import type { ShapeStyles } from '@toeverything/components/board-types';
|
||||||
|
import { getShapeStyle } from '../../shared';
|
||||||
|
|
||||||
|
interface RectangleSvgProps {
|
||||||
|
id: string;
|
||||||
|
style: ShapeStyles;
|
||||||
|
isSelected: boolean;
|
||||||
|
size: number[];
|
||||||
|
isDarkMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Frame = React.memo(function DashedRectangle({
|
||||||
|
id,
|
||||||
|
style,
|
||||||
|
size,
|
||||||
|
isSelected,
|
||||||
|
isDarkMode,
|
||||||
|
}: RectangleSvgProps) {
|
||||||
|
const { strokeWidth, fill } = getShapeStyle(style, isDarkMode);
|
||||||
|
|
||||||
|
const _fill = fill && fill !== 'none' ? fill : '#F7F9FF';
|
||||||
|
|
||||||
|
const sw = 1 + strokeWidth * 1.618;
|
||||||
|
|
||||||
|
const w = Math.max(0, size[0] - sw / 2);
|
||||||
|
const h = Math.max(0, size[1] - sw / 2);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<rect
|
||||||
|
className={
|
||||||
|
isSelected || style.isFilled
|
||||||
|
? 'tl-fill-hitarea'
|
||||||
|
: 'tl-stroke-hitarea'
|
||||||
|
}
|
||||||
|
x={sw / 2}
|
||||||
|
y={sw / 2}
|
||||||
|
width={w}
|
||||||
|
height={h}
|
||||||
|
strokeWidth={BINDING_DISTANCE}
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x={sw / 2}
|
||||||
|
y={sw / 2}
|
||||||
|
width={w}
|
||||||
|
height={h}
|
||||||
|
fill={_fill}
|
||||||
|
pointerEvents="none"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { BINDING_DISTANCE } from '@toeverything/components/board-types';
|
|
||||||
import type { ShapeStyles } from '@toeverything/components/board-types';
|
|
||||||
import { getShapeStyle } from '../../shared';
|
|
||||||
|
|
||||||
interface RectangleSvgProps {
|
|
||||||
id: string;
|
|
||||||
style: ShapeStyles;
|
|
||||||
isSelected: boolean;
|
|
||||||
size: number[];
|
|
||||||
isDarkMode: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DrawFrame = React.memo(function DashedRectangle({
|
|
||||||
id,
|
|
||||||
style,
|
|
||||||
size,
|
|
||||||
isSelected,
|
|
||||||
isDarkMode,
|
|
||||||
}: RectangleSvgProps) {
|
|
||||||
const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
|
|
||||||
|
|
||||||
const sw = 1 + strokeWidth * 1.618;
|
|
||||||
|
|
||||||
const w = Math.max(0, size[0] - sw / 2);
|
|
||||||
const h = Math.max(0, size[1] - sw / 2);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<rect
|
|
||||||
className={
|
|
||||||
isSelected || style.isFilled
|
|
||||||
? 'tl-fill-hitarea'
|
|
||||||
: 'tl-stroke-hitarea'
|
|
||||||
}
|
|
||||||
x={sw / 2}
|
|
||||||
y={sw / 2}
|
|
||||||
width={w}
|
|
||||||
height={h}
|
|
||||||
strokeWidth={BINDING_DISTANCE}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1 +1 @@
|
|||||||
export * from './frame-util';
|
export * from './FrameUtil';
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ import { StateManager } from './manager/state-manager';
|
|||||||
import { getClipboard, setClipboard } from './idb-clipboard';
|
import { getClipboard, setClipboard } from './idb-clipboard';
|
||||||
import type { Commands } from './types/commands';
|
import type { Commands } from './types/commands';
|
||||||
import type { BaseTool } from './types/tool';
|
import type { BaseTool } from './types/tool';
|
||||||
|
import { MIN_PAGE_WIDTH } from '@toeverything/components/editor-core';
|
||||||
|
|
||||||
const uuid = Utils.uniqueId();
|
const uuid = Utils.uniqueId();
|
||||||
|
|
||||||
@@ -178,6 +179,7 @@ export interface TldrawAppCtorProps {
|
|||||||
getSession: (type: SessionType) => {
|
getSession: (type: SessionType) => {
|
||||||
new (app: TldrawApp, ...args: any[]): BaseSessionType;
|
new (app: TldrawApp, ...args: any[]): BaseSessionType;
|
||||||
};
|
};
|
||||||
|
editorShapeInitSize?: number;
|
||||||
commands: Commands;
|
commands: Commands;
|
||||||
tools: Record<string, { new (app: TldrawApp): BaseTool }>;
|
tools: Record<string, { new (app: TldrawApp): BaseTool }>;
|
||||||
}
|
}
|
||||||
@@ -223,6 +225,8 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
|
|
||||||
fileSystemHandle: FileSystemHandle | null = null;
|
fileSystemHandle: FileSystemHandle | null = null;
|
||||||
|
|
||||||
|
editorShapeInitSize = MIN_PAGE_WIDTH;
|
||||||
|
|
||||||
viewport = Utils.getBoundsFromPoints([
|
viewport = Utils.getBoundsFromPoints([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[100, 100],
|
[100, 100],
|
||||||
@@ -285,6 +289,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, BaseTool>);
|
}, {} as Record<string, BaseTool>);
|
||||||
this.currentTool = this.tools['select'];
|
this.currentTool = this.tools['select'];
|
||||||
|
|
||||||
|
if (props.editorShapeInitSize) {
|
||||||
|
this.editorShapeInitSize = props.editorShapeInitSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------- Internal -------------------- */
|
/* -------------------- Internal -------------------- */
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export class EditorTool extends BaseTool {
|
|||||||
const {
|
const {
|
||||||
currentPoint,
|
currentPoint,
|
||||||
currentGrid,
|
currentGrid,
|
||||||
|
editorShapeInitSize,
|
||||||
settings: { showGrid },
|
settings: { showGrid },
|
||||||
appState: { currentPageId, currentStyle },
|
appState: { currentPageId, currentStyle },
|
||||||
document: { id: workspace },
|
document: { id: workspace },
|
||||||
@@ -47,6 +48,7 @@ export class EditorTool extends BaseTool {
|
|||||||
? Vec.snap(currentPoint, currentGrid)
|
? Vec.snap(currentPoint, currentGrid)
|
||||||
: currentPoint,
|
: currentPoint,
|
||||||
style: { ...currentStyle },
|
style: { ...currentStyle },
|
||||||
|
size: [editorShapeInitSize, 200],
|
||||||
workspace,
|
workspace,
|
||||||
});
|
});
|
||||||
// In order to make the cursor just positioned at the beginning of the first line, it needs to be adjusted according to the padding newShape.point = Vec.sub(newShape.point, [50, 30]);
|
// In order to make the cursor just positioned at the beginning of the first line, it needs to be adjusted according to the padding newShape.point = Vec.sub(newShape.point, [50, 30]);
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import clsx from 'clsx';
|
|
||||||
import style9 from 'style9';
|
|
||||||
import {
|
import {
|
||||||
MuiButton as Button,
|
MuiButton as Button,
|
||||||
MuiCollapse as Collapse,
|
MuiCollapse as Collapse,
|
||||||
|
styled,
|
||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
import {
|
||||||
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
|
ArrowDropDownIcon,
|
||||||
|
ArrowRightIcon,
|
||||||
|
} from '@toeverything/components/icons';
|
||||||
|
|
||||||
const styles = style9.create({
|
const StyledContainer = styled('div')({
|
||||||
ligoButton: {
|
display: 'flex',
|
||||||
textTransform: 'none',
|
alignItems: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover': {
|
||||||
|
background: '#f5f7f8',
|
||||||
|
borderRadius: '5px',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -24,29 +29,32 @@ export type CollapsibleTitleProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function CollapsibleTitle(props: CollapsibleTitleProps) {
|
export function CollapsibleTitle(props: CollapsibleTitleProps) {
|
||||||
const { className, style, children, title, initialOpen = true } = props;
|
const { children, title, initialOpen = true } = props;
|
||||||
|
|
||||||
const [open, setOpen] = useState(initialOpen);
|
const [open, setOpen] = useState(initialOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<StyledContainer onClick={() => setOpen(prev => !prev)}>
|
||||||
startIcon={
|
{open ? (
|
||||||
open ? (
|
<ArrowDropDownIcon sx={{ color: '#566B7D' }} />
|
||||||
<ArrowDropDownIcon sx={{ color: '#566B7D' }} />
|
) : (
|
||||||
) : (
|
<ArrowRightIcon sx={{ color: '#566B7D' }} />
|
||||||
<ArrowRightIcon sx={{ color: '#566B7D' }} />
|
)}
|
||||||
)
|
<div
|
||||||
}
|
style={{
|
||||||
onClick={() => setOpen(prev => !prev)}
|
color: '#98ACBD',
|
||||||
sx={{ color: '#566B7D', textTransform: 'none' }}
|
textTransform: 'none',
|
||||||
className={clsx(styles('ligoButton'), className)}
|
fontSize: '12px',
|
||||||
style={style}
|
fontWeight: '600',
|
||||||
disableElevation
|
height: '32px',
|
||||||
disableRipple
|
display: 'flex',
|
||||||
>
|
alignItems: 'center',
|
||||||
{title}
|
}}
|
||||||
</Button>
|
>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
</StyledContainer>
|
||||||
{children ? (
|
{children ? (
|
||||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -121,11 +121,11 @@ const isLinkActive = (editor: ReactEditor) => {
|
|||||||
|
|
||||||
const LinkStyledTooltip = styled(({ className, ...props }: MuiTooltipProps) => (
|
const LinkStyledTooltip = styled(({ className, ...props }: MuiTooltipProps) => (
|
||||||
<Tooltip {...props} classes={{ popper: className }} />
|
<Tooltip {...props} classes={{ popper: className }} />
|
||||||
))(() => ({
|
))(({ theme }) => ({
|
||||||
[`& .${muiTooltipClasses.tooltip}`]: {
|
[`& .${muiTooltipClasses.tooltip}`]: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
color: '#4C6275',
|
color: '#4C6275',
|
||||||
boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
},
|
},
|
||||||
[`& .MuiTooltip-tooltipPlacementBottom`]: {
|
[`& .MuiTooltip-tooltipPlacementBottom`]: {
|
||||||
@@ -412,8 +412,7 @@ export const LinkModal = memo((props: LinkModalProps) => {
|
|||||||
visible && (
|
visible && (
|
||||||
<>
|
<>
|
||||||
<LinkBehavior onMousedown={handle_mouse_down} rects={rects} />
|
<LinkBehavior onMousedown={handle_mouse_down} rects={rects} />
|
||||||
<div
|
<LinkModalContainer
|
||||||
className={styles('linkModalContainer')}
|
|
||||||
style={{
|
style={{
|
||||||
top: top + height + GAP_BETWEEN_CONTENT_AND_MODAL,
|
top: top + height + GAP_BETWEEN_CONTENT_AND_MODAL,
|
||||||
left,
|
left,
|
||||||
@@ -431,7 +430,7 @@ export const LinkModal = memo((props: LinkModalProps) => {
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
ref={inputEl}
|
ref={inputEl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</LinkModalContainer>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
body
|
body
|
||||||
@@ -491,19 +490,20 @@ const LinkBehavior = (props: {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LinkModalContainer = styled('div')(({ theme }) => ({
|
||||||
|
position: 'fixed',
|
||||||
|
width: '354px',
|
||||||
|
height: '40px',
|
||||||
|
padding: '12px',
|
||||||
|
display: 'flex',
|
||||||
|
borderRadius: '4px',
|
||||||
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: '1',
|
||||||
|
}));
|
||||||
|
|
||||||
const styles = style9.create({
|
const styles = style9.create({
|
||||||
linkModalContainer: {
|
|
||||||
position: 'fixed',
|
|
||||||
width: '354px',
|
|
||||||
height: '40px',
|
|
||||||
padding: '12px',
|
|
||||||
display: 'flex',
|
|
||||||
borderRadius: '4px',
|
|
||||||
boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
alignItems: 'center',
|
|
||||||
zIndex: '1',
|
|
||||||
},
|
|
||||||
linkModalContainerIcon: {
|
linkModalContainerIcon: {
|
||||||
width: '16px',
|
width: '16px',
|
||||||
margin: '0 16px 0 4px',
|
margin: '0 16px 0 4px',
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
supportChildren,
|
supportChildren,
|
||||||
RenderBlockChildren,
|
RenderBlockChildren,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { List } from '../../components/style-container';
|
import { List } from '../../components/style-container';
|
||||||
import { getChildrenType, BulletIcon, NumberType } from './data';
|
import { getChildrenType, BulletIcon, NumberType } from './data';
|
||||||
@@ -188,7 +188,7 @@ export const BulletView: FC<CreateView> = ({ block, editor }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BlockContainer editor={editor} block={block} selected={isSelect}>
|
<BlockContainer editor={editor} block={block} selected={isSelect}>
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<List>
|
<List>
|
||||||
<BulletLeft>
|
<BulletLeft>
|
||||||
<BulletIcon numberType={properties.numberType} />
|
<BulletIcon numberType={properties.numberType} />
|
||||||
@@ -206,7 +206,7 @@ export const BulletView: FC<CreateView> = ({ block, editor }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</List>
|
</List>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
<IndentWrapper>
|
<IndentWrapper>
|
||||||
<RenderBlockChildren block={block} />
|
<RenderBlockChildren block={block} />
|
||||||
</IndentWrapper>
|
</IndentWrapper>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ import { Option, Select } from '@toeverything/components/ui';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { copyToClipboard } from '@toeverything/utils';
|
import { copyToClipboard } from '@toeverything/utils';
|
||||||
interface CreateCodeView extends CreateView {
|
interface CreateCodeView extends CreateView {
|
||||||
@@ -163,7 +163,7 @@ export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
|||||||
editor.selectionManager.activePreviousNode(block.id, 'start');
|
editor.selectionManager.activePreviousNode(block.id, 'start');
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
onKeyDown={e => {
|
onKeyDown={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -222,6 +222,6 @@ export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
|||||||
handleKeyArrowUp={handleKeyArrowUp}
|
handleKeyArrowUp={handleKeyArrowUp}
|
||||||
/>
|
/>
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
import { CreateView } from '@toeverything/framework/virgo';
|
import { CreateView } from '@toeverything/framework/virgo';
|
||||||
import {
|
import {
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { Upload } from '../../components/upload/upload';
|
import { Upload } from '../../components/upload/upload';
|
||||||
@@ -33,7 +33,7 @@ export const EmbedLinkView: FC<EmbedLinkView> = props => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<LinkContainer>
|
<LinkContainer>
|
||||||
{embedLinkUrl ? (
|
{embedLinkUrl ? (
|
||||||
<SourceView
|
<SourceView
|
||||||
@@ -53,6 +53,6 @@ export const EmbedLinkView: FC<EmbedLinkView> = props => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { FC, useState } from 'react';
|
|||||||
import { CreateView } from '@toeverything/framework/virgo';
|
import { CreateView } from '@toeverything/framework/virgo';
|
||||||
import {
|
import {
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { Upload } from '../../components/upload/upload';
|
import { Upload } from '../../components/upload/upload';
|
||||||
import { SourceView } from '../../components/source-view';
|
import { SourceView } from '../../components/source-view';
|
||||||
@@ -30,7 +30,7 @@ export const FigmaView: FC<FigmaView> = ({ block, editor }) => {
|
|||||||
setIsSelect(isSelect);
|
setIsSelect(isSelect);
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<LinkContainer>
|
<LinkContainer>
|
||||||
{figmaUrl ? (
|
{figmaUrl ? (
|
||||||
<SourceView
|
<SourceView
|
||||||
@@ -52,6 +52,6 @@ export const FigmaView: FC<FigmaView> = ({ block, editor }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ type GridHandleProps = {
|
|||||||
blockId: string;
|
blockId: string;
|
||||||
enabledAddItem: boolean;
|
enabledAddItem: boolean;
|
||||||
draggable: boolean;
|
draggable: boolean;
|
||||||
|
alertHandleId: string;
|
||||||
|
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GridHandle: FC<GridHandleProps> = function ({
|
export const GridHandle: FC<GridHandleProps> = function ({
|
||||||
@@ -21,6 +23,8 @@ export const GridHandle: FC<GridHandleProps> = function ({
|
|||||||
onDrag,
|
onDrag,
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
draggable,
|
draggable,
|
||||||
|
alertHandleId,
|
||||||
|
onMouseEnter,
|
||||||
}) {
|
}) {
|
||||||
const [isMouseDown, setIsMouseDown] = useState<boolean>(false);
|
const [isMouseDown, setIsMouseDown] = useState<boolean>(false);
|
||||||
const handleMouseDown: React.MouseEventHandler<HTMLDivElement> = e => {
|
const handleMouseDown: React.MouseEventHandler<HTMLDivElement> = e => {
|
||||||
@@ -44,16 +48,17 @@ export const GridHandle: FC<GridHandleProps> = function ({
|
|||||||
editor.selectionManager.setActivatedNodeId(textBlock.id);
|
editor.selectionManager.setActivatedNodeId(textBlock.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMouseEnter: React.MouseEventHandler<HTMLDivElement> = e => {
|
||||||
|
onMouseEnter && onMouseEnter(e);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridHandleContainer
|
<GridHandleContainer
|
||||||
style={
|
isMouseDown={isMouseDown}
|
||||||
isMouseDown
|
|
||||||
? {
|
|
||||||
backgroundColor: '#3E6FDB',
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
isAlert={alertHandleId === blockId}
|
||||||
>
|
>
|
||||||
{enabledAddItem ? (
|
{enabledAddItem ? (
|
||||||
<AddGridHandle
|
<AddGridHandle
|
||||||
@@ -67,7 +72,10 @@ export const GridHandle: FC<GridHandleProps> = function ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GridHandleContainer = styled('div')(({ theme }) => ({
|
const GridHandleContainer = styled('div')<{
|
||||||
|
isMouseDown: boolean;
|
||||||
|
isAlert: boolean;
|
||||||
|
}>(({ theme, isMouseDown, isAlert }) => ({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: '10px',
|
width: '10px',
|
||||||
flexGrow: '0',
|
flexGrow: '0',
|
||||||
@@ -78,11 +86,15 @@ const GridHandleContainer = styled('div')(({ theme }) => ({
|
|||||||
borderRadius: '1px',
|
borderRadius: '1px',
|
||||||
backgroundClip: 'content-box',
|
backgroundClip: 'content-box',
|
||||||
' &:hover': {
|
' &:hover': {
|
||||||
backgroundColor: theme.affine.palette.primary,
|
backgroundColor: isAlert ? 'red' : theme.affine.palette.primary,
|
||||||
[`.${GRID_ADD_HANDLE_NAME}`]: {
|
[`.${GRID_ADD_HANDLE_NAME}`]: {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
...(isMouseDown &&
|
||||||
|
(isAlert
|
||||||
|
? { backgroundColor: 'red' }
|
||||||
|
: { backgroundColor: theme.affine.palette.primary })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const AddGridHandle = styled('div')(({ theme }) => ({
|
const AddGridHandle = styled('div')(({ theme }) => ({
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
const gridItemCountRef = useRef<number>();
|
const gridItemCountRef = useRef<number>();
|
||||||
const originalLeftWidth = useRef<number>(GRID_ITEM_MIN_WIDTH);
|
const originalLeftWidth = useRef<number>(GRID_ITEM_MIN_WIDTH);
|
||||||
const originalRightWidth = useRef<number>(GRID_ITEM_MIN_WIDTH);
|
const originalRightWidth = useRef<number>(GRID_ITEM_MIN_WIDTH);
|
||||||
|
const [alertHandleId, setAlertHandleId] = useState<string>(null);
|
||||||
|
|
||||||
const getLeftRightGridItemDomByIndex = (index: number) => {
|
const getLeftRightGridItemDomByIndex = (index: number) => {
|
||||||
const gridItems = Array.from(gridContainerRef.current?.children).filter(
|
const gridItems = Array.from(gridContainerRef.current?.children).filter(
|
||||||
@@ -117,7 +118,7 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
itemDom.style.width = width;
|
itemDom.style.width = width;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragGrid = (e: MouseEvent, index: number) => {
|
const handleDragGrid = async (e: MouseEvent, index: number) => {
|
||||||
setIsOnDrag(true);
|
setIsOnDrag(true);
|
||||||
window.getSelection().removeAllRanges();
|
window.getSelection().removeAllRanges();
|
||||||
if (!isSetMouseUp.current) {
|
if (!isSetMouseUp.current) {
|
||||||
@@ -165,39 +166,47 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
setItemWidth(leftGrid, newLeft);
|
setItemWidth(leftGrid, newLeft);
|
||||||
setItemWidth(rightGrid, newRight);
|
setItemWidth(rightGrid, newRight);
|
||||||
updateDbWidth(leftBlockId, newLeft, rightBlockId, newRight);
|
updateDbWidth(leftBlockId, newLeft, rightBlockId, newRight);
|
||||||
|
[leftBlockId, rightBlockId].forEach(async blockId => {
|
||||||
|
if (await checkGridItemHasOverflow(blockId)) {
|
||||||
|
setAlertHandleId(leftBlockId);
|
||||||
|
} else {
|
||||||
|
setAlertHandleId(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const children = (
|
const checkGridItemHasOverflow = async (blockId: string) => {
|
||||||
<>
|
let isOverflow = false;
|
||||||
{block.childrenIds.map((id, i) => {
|
const block = await editor.getBlockById(blockId);
|
||||||
return (
|
if (block) {
|
||||||
<GridItem
|
const blockDom = block.dom;
|
||||||
style={{
|
if (blockDom) {
|
||||||
transition: isOnDrag
|
block.dom.style.overflow = 'scroll';
|
||||||
? 'none'
|
if (block.dom.clientWidth !== block.dom.scrollWidth) {
|
||||||
: 'all 0.2s ease-in-out',
|
isOverflow = true;
|
||||||
}}
|
}
|
||||||
key={id}
|
blockDom.style.overflow = 'visible';
|
||||||
className={GRID_ITEM_CLASS_NAME}
|
}
|
||||||
>
|
}
|
||||||
<RenderBlock hasContainer={false} blockId={id} />
|
return isOverflow;
|
||||||
<GridHandle
|
};
|
||||||
onDrag={event => handleDragGrid(event, i)}
|
|
||||||
editor={editor}
|
const handleHandleMouseEnter = (
|
||||||
onMouseDown={event => handleMouseDown(event, i)}
|
e: React.MouseEvent<HTMLDivElement>,
|
||||||
blockId={id}
|
index: number
|
||||||
enabledAddItem={
|
) => {
|
||||||
block.childrenIds.length < MAX_ITEM_COUNT
|
const leftBlockId = block.childrenIds[index];
|
||||||
}
|
const rightBlockId = block.childrenIds[index + 1];
|
||||||
draggable={i !== block.childrenIds.length - 1}
|
[leftBlockId, rightBlockId].forEach(async blockId => {
|
||||||
/>
|
if (await checkGridItemHasOverflow(blockId)) {
|
||||||
</GridItem>
|
setAlertHandleId(leftBlockId);
|
||||||
);
|
} else {
|
||||||
})}
|
setAlertHandleId(null);
|
||||||
</>
|
}
|
||||||
);
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -206,7 +215,35 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
ref={gridContainerRef}
|
ref={gridContainerRef}
|
||||||
isOnDrag={isOnDrag}
|
isOnDrag={isOnDrag}
|
||||||
>
|
>
|
||||||
{children}
|
{block.childrenIds.map((id, i) => {
|
||||||
|
return (
|
||||||
|
<GridItem
|
||||||
|
style={{
|
||||||
|
transition: isOnDrag
|
||||||
|
? 'none'
|
||||||
|
: 'all 0.2s ease-in-out',
|
||||||
|
}}
|
||||||
|
key={id}
|
||||||
|
className={GRID_ITEM_CLASS_NAME}
|
||||||
|
>
|
||||||
|
<RenderBlock hasContainer={false} blockId={id} />
|
||||||
|
<GridHandle
|
||||||
|
onDrag={event => handleDragGrid(event, i)}
|
||||||
|
editor={editor}
|
||||||
|
onMouseDown={event => handleMouseDown(event, i)}
|
||||||
|
blockId={id}
|
||||||
|
enabledAddItem={
|
||||||
|
block.childrenIds.length < MAX_ITEM_COUNT
|
||||||
|
}
|
||||||
|
onMouseEnter={event =>
|
||||||
|
handleHandleMouseEnter(event, i)
|
||||||
|
}
|
||||||
|
alertHandleId={alertHandleId}
|
||||||
|
draggable={i !== block.childrenIds.length - 1}
|
||||||
|
/>
|
||||||
|
</GridItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</GridContainer>
|
</GridContainer>
|
||||||
{isOnDrag
|
{isOnDrag
|
||||||
? ReactDOM.createPortal(<GridMask />, window.document.body)
|
? ReactDOM.createPortal(<GridMask />, window.document.body)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const GroupContainer = styled('div')<{ isSelect?: boolean }>(
|
|||||||
({ isSelect, theme }) => ({
|
({ isSelect, theme }) => ({
|
||||||
background: theme.affine.palette.white,
|
background: theme.affine.palette.white,
|
||||||
border: '2px solid rgba(236,241,251,.5)',
|
border: '2px solid rgba(236,241,251,.5)',
|
||||||
padding: '15px 12px',
|
padding: `15px 16px 0 16px`,
|
||||||
borderRadius: '10px',
|
borderRadius: '10px',
|
||||||
...(isSelect
|
...(isSelect
|
||||||
? {
|
? {
|
||||||
@@ -69,7 +69,7 @@ const GroupContainer = styled('div')<{ isSelect?: boolean }>(
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
boxShadow: '0px 1px 10px rgb(152 172 189 / 60%)',
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { styled } from '@toeverything/components/ui';
|
|||||||
import type { ComponentPropsWithRef, MouseEvent } from 'react';
|
import type { ComponentPropsWithRef, MouseEvent } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
const StyledPanel = styled('div')(() => ({
|
const StyledPanel = styled('div')(({ theme }) => ({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 50,
|
top: 50,
|
||||||
background: '#FFFFFF',
|
background: '#FFFFFF',
|
||||||
boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
padding: '12px 24px',
|
padding: '12px 24px',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const getKanbanColor = (
|
|||||||
return DEFAULT_COLOR;
|
return DEFAULT_COLOR;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
group.type === PropertyType.Status ||
|
||||||
group.type === PropertyType.Select ||
|
group.type === PropertyType.Select ||
|
||||||
group.type === PropertyType.MultiSelect ||
|
group.type === PropertyType.MultiSelect ||
|
||||||
group.type === DEFAULT_GROUP_ID
|
group.type === DEFAULT_GROUP_ID
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const AddCard = ({ group }: { group: KanbanGroup }) => {
|
|||||||
const { addCard } = useKanban();
|
const { addCard } = useKanban();
|
||||||
const handleClick = useCallback(async () => {
|
const handleClick = useCallback(async () => {
|
||||||
await addCard(group);
|
await addCard(group);
|
||||||
}, [addCard]);
|
}, [addCard, group]);
|
||||||
return <AddCardWrapper onClick={handleClick}>+</AddCardWrapper>;
|
return <AddCardWrapper onClick={handleClick}>+</AddCardWrapper>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import type { KanbanCard } from '@toeverything/components/editor-core';
|
import type { KanbanCard } from '@toeverything/components/editor-core';
|
||||||
import { RenderBlock, useKanban } from '@toeverything/components/editor-core';
|
import {
|
||||||
|
RenderBlock,
|
||||||
|
useKanban,
|
||||||
|
useRefPage,
|
||||||
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
|
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||||
|
|
||||||
const CardContent = styled('div')({
|
const CardContent = styled('div')({
|
||||||
margin: '20px',
|
margin: '20px',
|
||||||
@@ -58,18 +63,24 @@ export const CardItem = ({
|
|||||||
block: KanbanCard['block'];
|
block: KanbanCard['block'];
|
||||||
}) => {
|
}) => {
|
||||||
const { addSubItem } = useKanban();
|
const { addSubItem } = useKanban();
|
||||||
|
const { openSubPage } = useRefPage();
|
||||||
|
const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false);
|
||||||
const onAddItem = async () => {
|
const onAddItem = async () => {
|
||||||
await addSubItem(block);
|
await addSubItem(block);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onClickCard = async () => {
|
||||||
|
showKanbanRefPageFlag && openSubPage(id);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContainer>
|
<CardContainer onClick={onClickCard}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<RenderBlock blockId={id} />
|
<RenderBlock blockId={id} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions onClick={onAddItem}>
|
<CardActions onClick={onAddItem}>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
<span>Add item</span>
|
<span>Add a sub-block</span>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</CardContainer>
|
</CardContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
useCurrentView,
|
useCurrentView,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
@@ -143,13 +143,13 @@ export const ImageView: FC<ImageView> = ({ block, editor }) => {
|
|||||||
type: 'link',
|
type: 'link',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const handle_click = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handle_click = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
//TODO clear active selection
|
//TODO clear active selection
|
||||||
// document.getElementsByTagName('body')[0].click();
|
// document.getElementsByTagName('body')[0].click();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.nativeEvent.stopPropagation();
|
e.nativeEvent.stopPropagation();
|
||||||
editor.selectionManager.setSelectedNodesIds([block.id]);
|
await editor.selectionManager.setSelectedNodesIds([block.id]);
|
||||||
editor.selectionManager.activeNodeByNodeId(block.id);
|
await editor.selectionManager.activeNodeByNodeId(block.id, 'end');
|
||||||
};
|
};
|
||||||
const down_file = () => {
|
const down_file = () => {
|
||||||
if (down_ref) {
|
if (down_ref) {
|
||||||
@@ -158,7 +158,7 @@ export const ImageView: FC<ImageView> = ({ block, editor }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<ImageBlock>
|
<ImageBlock>
|
||||||
<div ref={resize_box}>
|
<div ref={resize_box}>
|
||||||
{imgUrl ? (
|
{imgUrl ? (
|
||||||
@@ -229,6 +229,6 @@ export const ImageView: FC<ImageView> = ({ block, editor }) => {
|
|||||||
</div> */}
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</ImageBlock>
|
</ImageBlock>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ import {
|
|||||||
supportChildren,
|
supportChildren,
|
||||||
RenderBlockChildren,
|
RenderBlockChildren,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
|
||||||
import { List } from '../../components/style-container';
|
import { List } from '../../components/style-container';
|
||||||
import { BlockContainer } from '../../components/BlockContainer';
|
import { BlockContainer } from '../../components/BlockContainer';
|
||||||
|
|
||||||
@@ -185,7 +184,7 @@ export const NumberedView: FC<CreateView> = ({ block, editor }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BlockContainer editor={editor} block={block} selected={isSelect}>
|
<BlockContainer editor={editor} block={block} selected={isSelect}>
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<List>
|
<List>
|
||||||
<div className={'checkBoxContainer'}>
|
<div className={'checkBoxContainer'}>
|
||||||
{getNumber(properties.numberType, number)}.
|
{getNumber(properties.numberType, number)}.
|
||||||
@@ -203,7 +202,7 @@ export const NumberedView: FC<CreateView> = ({ block, editor }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</List>
|
</List>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
|
|
||||||
<IndentWrapper>
|
<IndentWrapper>
|
||||||
<RenderBlockChildren block={block} />
|
<RenderBlockChildren block={block} />
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
supportChildren,
|
supportChildren,
|
||||||
unwrapGroup,
|
unwrapGroup,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
@@ -99,7 +99,7 @@ export const TextView: FC<CreateTextView> = ({
|
|||||||
if (!parentBlock) {
|
if (!parentBlock) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const preParent = await parentBlock.previousSibling();
|
||||||
if (Protocol.Block.Type.group === parentBlock.type) {
|
if (Protocol.Block.Type.group === parentBlock.type) {
|
||||||
const children = await block.children();
|
const children = await block.children();
|
||||||
const preNode = await block.physicallyPerviousSibling();
|
const preNode = await block.physicallyPerviousSibling();
|
||||||
@@ -129,34 +129,19 @@ export const TextView: FC<CreateTextView> = ({
|
|||||||
'start'
|
'start'
|
||||||
);
|
);
|
||||||
if (block.blockProvider.isEmpty()) {
|
if (block.blockProvider.isEmpty()) {
|
||||||
block.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// TODO remove timing problem
|
|
||||||
const prevGroupBlock = await parentBlock.previousSibling();
|
|
||||||
|
|
||||||
if (!prevGroupBlock) {
|
|
||||||
const childrenBlock = await parentBlock.children();
|
|
||||||
if (childrenBlock.length) {
|
|
||||||
if (children.length) {
|
|
||||||
await parentBlock.append(...children);
|
|
||||||
}
|
|
||||||
await block.remove();
|
await block.remove();
|
||||||
return true;
|
const parentChild = await parentBlock.children();
|
||||||
|
if (
|
||||||
|
parentBlock.type ===
|
||||||
|
Protocol.Block.Type.group &&
|
||||||
|
!parentChild.length
|
||||||
|
) {
|
||||||
|
await editor.selectionManager.setSelectedNodesIds(
|
||||||
|
[preParent?.id ?? editor.getRootBlockId()]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parentBlock.remove();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (prevGroupBlock.type !== Protocol.Block.Type.group) {
|
|
||||||
unwrapGroup(parentBlock);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeGroup(prevGroupBlock, parentBlock);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +216,7 @@ export const TextView: FC<CreateTextView> = ({
|
|||||||
selected={isSelect}
|
selected={isSelect}
|
||||||
className={containerClassName}
|
className={containerClassName}
|
||||||
>
|
>
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
block={block}
|
block={block}
|
||||||
type={block.type}
|
type={block.type}
|
||||||
@@ -242,7 +227,7 @@ export const TextView: FC<CreateTextView> = ({
|
|||||||
handleConvert={handleConvert}
|
handleConvert={handleConvert}
|
||||||
handleTab={onTab}
|
handleTab={onTab}
|
||||||
/>
|
/>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
<IndentWrapper>
|
<IndentWrapper>
|
||||||
<RenderBlockChildren block={block} />
|
<RenderBlockChildren block={block} />
|
||||||
</IndentWrapper>
|
</IndentWrapper>
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import type { AsyncBlock } from '@toeverything/components/editor-core';
|
import {
|
||||||
|
AsyncBlock,
|
||||||
|
useCurrentView,
|
||||||
|
useLazyIframe,
|
||||||
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import type { FC } from 'react';
|
import {
|
||||||
|
FC,
|
||||||
|
ReactElement,
|
||||||
|
ReactNode,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { SCENE_CONFIG } from '../../blocks/group/config';
|
||||||
import { BlockPreview } from './BlockView';
|
import { BlockPreview } from './BlockView';
|
||||||
import { formatUrl } from './format-url';
|
import { formatUrl } from './format-url';
|
||||||
|
|
||||||
@@ -15,7 +27,18 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getHost = (url: string) => new URL(url).host;
|
const getHost = (url: string) => new URL(url).host;
|
||||||
|
const MouseMaskContainer = styled('div')({
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 1,
|
||||||
|
top: '0px',
|
||||||
|
left: '0px',
|
||||||
|
right: '0px',
|
||||||
|
bottom: '0px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
pointerEvents: 'none',
|
||||||
|
},
|
||||||
|
});
|
||||||
const LinkContainer = styled('div')<{
|
const LinkContainer = styled('div')<{
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
}>(({ theme, isSelected }) => {
|
}>(({ theme, isSelected }) => {
|
||||||
@@ -38,12 +61,28 @@ const LinkContainer = styled('div')<{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const _getLinkStyle = (scene: string) => {
|
||||||
|
switch (scene) {
|
||||||
|
case SCENE_CONFIG.PAGE:
|
||||||
|
return {
|
||||||
|
width: '420px',
|
||||||
|
height: '198px',
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
width: '252px',
|
||||||
|
height: '126px',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
const SourceViewContainer = styled('div')<{
|
const SourceViewContainer = styled('div')<{
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
}>(({ theme, isSelected }) => {
|
scene: string;
|
||||||
|
}>(({ theme, isSelected, scene }) => {
|
||||||
return {
|
return {
|
||||||
|
..._getLinkStyle(scene),
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
position: 'relative',
|
||||||
borderRadius: theme.affine.shape.borderRadius,
|
borderRadius: theme.affine.shape.borderRadius,
|
||||||
background: isSelected ? 'rgba(152, 172, 189, 0.1)' : 'transparent',
|
background: isSelected ? 'rgba(152, 172, 189, 0.1)' : 'transparent',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
@@ -52,32 +91,96 @@ const SourceViewContainer = styled('div')<{
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
border: '1px solid #EAEEF2',
|
border: '1px solid #EAEEF2',
|
||||||
borderRadius: theme.affine.shape.borderRadius,
|
borderRadius: theme.affine.shape.borderRadius,
|
||||||
|
userSelect: 'none',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const LazyIframe = ({
|
||||||
|
src,
|
||||||
|
delay = 3000,
|
||||||
|
fallback,
|
||||||
|
}: {
|
||||||
|
src: string;
|
||||||
|
delay?: number;
|
||||||
|
fallback?: ReactNode;
|
||||||
|
}) => {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const timer = useRef<number>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Hide iframe when the src changed
|
||||||
|
setShow(false);
|
||||||
|
}, [src]);
|
||||||
|
|
||||||
|
const onLoad = () => {
|
||||||
|
clearTimeout(timer.current);
|
||||||
|
timer.current = window.setTimeout(() => {
|
||||||
|
// Prevent iframe scrolling parent container
|
||||||
|
// Remove the delay after the issue is resolved
|
||||||
|
// See W3C https://github.com/w3c/csswg-drafts/issues/7134
|
||||||
|
// See https://forum.figma.com/t/prevent-figmas-embed-code-from-automatically-scrolling-to-it-on-page-load/26029/6
|
||||||
|
setShow(true);
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
onMouseDown={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
style={{ display: show ? 'block' : 'none', height: '100%' }}
|
||||||
|
>
|
||||||
|
<iframe src={src} onLoad={onLoad} />
|
||||||
|
</div>
|
||||||
|
{!show && fallback}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Loading = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
lineHeight: '100%',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
border: '1px solid #EAEEF2',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const LoadingContiner = () => {
|
||||||
|
return <Loading>loading...</Loading>;
|
||||||
|
};
|
||||||
|
|
||||||
export const SourceView: FC<Props> = props => {
|
export const SourceView: FC<Props> = props => {
|
||||||
const { link, isSelected, block, editorElement } = props;
|
const { link, isSelected, block, editorElement } = props;
|
||||||
const src = formatUrl(link);
|
const src = formatUrl(link);
|
||||||
const openTabOnBrowser = () => {
|
// let iframeShow = useLazyIframe(src, 3000, iframeContainer);
|
||||||
window.open(link, '_blank');
|
const [currentView] = useCurrentView();
|
||||||
};
|
const { type } = currentView;
|
||||||
if (src?.startsWith('http')) {
|
if (src?.startsWith('http')) {
|
||||||
return (
|
return (
|
||||||
<LinkContainer
|
<div style={{ display: 'flex' }}>
|
||||||
isSelected={isSelected}
|
<SourceViewContainer isSelected={isSelected} scene={type}>
|
||||||
onMouseDown={e => e.preventDefault()}
|
<MouseMaskContainer />
|
||||||
onClick={openTabOnBrowser}
|
|
||||||
>
|
<LazyIframe
|
||||||
<p>{getHost(src)}</p>
|
src={src}
|
||||||
<p>{src}</p>
|
fallback={LoadingContiner()}
|
||||||
</LinkContainer>
|
></LazyIframe>
|
||||||
|
</SourceViewContainer>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
} else if (src?.startsWith('affine')) {
|
} else if (src?.startsWith('affine')) {
|
||||||
return (
|
return (
|
||||||
<SourceViewContainer
|
<SourceViewContainer
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
style={{ padding: '0' }}
|
style={{ padding: '0' }}
|
||||||
|
scene={type}
|
||||||
>
|
>
|
||||||
<BlockPreview
|
<BlockPreview
|
||||||
block={block}
|
block={block}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
RenderBlock,
|
RenderBlock,
|
||||||
useCurrentView,
|
useCurrentView,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import type {
|
import type {
|
||||||
@@ -13,7 +13,6 @@ import type {
|
|||||||
ReactElement,
|
ReactElement,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { forwardRef, useState } from 'react';
|
import { forwardRef, useState } from 'react';
|
||||||
import style9 from 'style9';
|
|
||||||
import { SCENE_CONFIG } from '../blocks/group/config';
|
import { SCENE_CONFIG } from '../blocks/group/config';
|
||||||
import { BlockContainer } from '../components/BlockContainer';
|
import { BlockContainer } from '../components/BlockContainer';
|
||||||
|
|
||||||
@@ -30,29 +29,15 @@ const TreeView = forwardRef<
|
|||||||
{ lastItem?: boolean } & ComponentPropsWithRef<'div'>
|
{ lastItem?: boolean } & ComponentPropsWithRef<'div'>
|
||||||
>(({ lastItem, children, onClick, ...restProps }, ref) => {
|
>(({ lastItem, children, onClick, ...restProps }, ref) => {
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={treeStyles('treeWrapper')} {...restProps}>
|
<TreeWrapper ref={ref} {...restProps}>
|
||||||
<div className={treeStyles('treeView')}>
|
<StyledTreeView>
|
||||||
<div
|
<VerticalLine last={lastItem} onClick={onClick} />
|
||||||
className={treeStyles({
|
<HorizontalLine last={lastItem} onClick={onClick} />
|
||||||
line: true,
|
{lastItem && <LastItemRadius />}
|
||||||
verticalLine: true,
|
</StyledTreeView>
|
||||||
lastItemVerticalLine: lastItem,
|
|
||||||
})}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={treeStyles({
|
|
||||||
line: true,
|
|
||||||
horizontalLine: true,
|
|
||||||
lastItemHorizontalLine: lastItem,
|
|
||||||
})}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
{lastItem && <div className={treeStyles('lastItemRadius')} />}
|
|
||||||
</div>
|
|
||||||
{/* maybe need a child wrapper */}
|
{/* maybe need a child wrapper */}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</TreeWrapper>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,10 +56,7 @@ const ChildrenView = ({
|
|||||||
const isKanbanScene = currentView.type === SCENE_CONFIG.KANBAN;
|
const isKanbanScene = currentView.type === SCENE_CONFIG.KANBAN;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Children style={{ ...(!isKanbanScene && { marginLeft: indent }) }}>
|
||||||
className={styles('children')}
|
|
||||||
style={{ ...(!isKanbanScene && { marginLeft: indent }) }}
|
|
||||||
>
|
|
||||||
{childrenIds.map((childId, idx) => {
|
{childrenIds.map((childId, idx) => {
|
||||||
if (isKanbanScene) {
|
if (isKanbanScene) {
|
||||||
return (
|
return (
|
||||||
@@ -94,7 +76,7 @@ const ChildrenView = ({
|
|||||||
</TreeView>
|
</TreeView>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</Children>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,9 +86,7 @@ const CollapsedNode = forwardRef<
|
|||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
return (
|
return (
|
||||||
<TreeView ref={ref} lastItem={true} {...props}>
|
<TreeView ref={ref} lastItem={true} {...props}>
|
||||||
<div className={treeStyles('collapsed')} onClick={props.onClick}>
|
<Collapsed onClick={props.onClick}>···</Collapsed>
|
||||||
···
|
|
||||||
</div>
|
|
||||||
</TreeView>
|
</TreeView>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -146,11 +126,11 @@ export const withTreeViewChildren = (
|
|||||||
editor={props.editor}
|
editor={props.editor}
|
||||||
block={block}
|
block={block}
|
||||||
selected={isSelect}
|
selected={isSelect}
|
||||||
className={styles('wrapper')}
|
className={Wrapper.toString()}
|
||||||
>
|
>
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<div className={styles('node')}>{creator(props)}</div>
|
<div>{creator(props)}</div>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
|
|
||||||
{collapsed && (
|
{collapsed && (
|
||||||
<CollapsedNode
|
<CollapsedNode
|
||||||
@@ -170,93 +150,79 @@ export const withTreeViewChildren = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = style9.create({
|
const Wrapper = styled('div')({ display: 'flex', flexDirection: 'column' });
|
||||||
wrapper: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
node: {},
|
|
||||||
|
|
||||||
children: {
|
const Children = Wrapper;
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
const TREE_COLOR = '#D5DFE6';
|
||||||
},
|
// TODO determine the position of the horizontal line by the type of the item
|
||||||
|
const ITEM_POINT_HEIGHT = '12.5px'; // '50%'
|
||||||
|
|
||||||
|
const TreeWrapper = styled('div')({
|
||||||
|
position: 'relative',
|
||||||
});
|
});
|
||||||
|
|
||||||
const treeColor = '#D5DFE6';
|
const StyledTreeView = styled('div')({
|
||||||
// TODO determine the position of the horizontal line by the type of the item
|
position: 'absolute',
|
||||||
const itemPointHeight = '12.5px'; // '50%'
|
left: '-21px',
|
||||||
|
height: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
const treeStyles = style9.create({
|
const Line = styled('div')({
|
||||||
treeWrapper: {
|
position: 'absolute',
|
||||||
position: 'relative',
|
cursor: 'pointer',
|
||||||
},
|
backgroundColor: TREE_COLOR,
|
||||||
|
// somehow tldraw would override this
|
||||||
|
boxSizing: 'content-box!important' as any,
|
||||||
|
// See [Can I add background color only for padding?](https://stackoverflow.com/questions/14628601/can-i-add-background-color-only-for-padding)
|
||||||
|
backgroundClip: 'content-box',
|
||||||
|
backgroundOrigin: 'content-box',
|
||||||
|
// Increase click hot spot
|
||||||
|
padding: '10px',
|
||||||
|
});
|
||||||
|
|
||||||
treeView: {
|
const VerticalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
||||||
position: 'absolute',
|
width: '1px',
|
||||||
left: '-21px',
|
height: last ? ITEM_POINT_HEIGHT : '100%',
|
||||||
height: '100%',
|
paddingTop: 0,
|
||||||
},
|
paddingBottom: 0,
|
||||||
line: {
|
transform: 'translate(-50%, 0)',
|
||||||
position: 'absolute',
|
|
||||||
cursor: 'pointer',
|
|
||||||
backgroundColor: treeColor,
|
|
||||||
boxSizing: 'content-box',
|
|
||||||
// See [Can I add background color only for padding?](https://stackoverflow.com/questions/14628601/can-i-add-background-color-only-for-padding)
|
|
||||||
backgroundClip: 'content-box',
|
|
||||||
backgroundOrigin: 'content-box',
|
|
||||||
// Increase click hot spot
|
|
||||||
padding: '10px',
|
|
||||||
},
|
|
||||||
verticalLine: {
|
|
||||||
width: '1px',
|
|
||||||
height: '100%',
|
|
||||||
paddingTop: 0,
|
|
||||||
paddingBottom: 0,
|
|
||||||
transform: 'translate(-50%, 0)',
|
|
||||||
},
|
|
||||||
horizontalLine: {
|
|
||||||
width: '16px',
|
|
||||||
height: '1px',
|
|
||||||
paddingLeft: 0,
|
|
||||||
paddingRight: 0,
|
|
||||||
top: itemPointHeight,
|
|
||||||
transform: 'translate(0, -50%)',
|
|
||||||
},
|
|
||||||
noItemHorizontalLine: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
lastItemHorizontalLine: {
|
opacity: last ? 0 : 'unset',
|
||||||
opacity: 0,
|
}));
|
||||||
},
|
|
||||||
lastItemVerticalLine: {
|
|
||||||
height: itemPointHeight,
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
lastItemRadius: {
|
|
||||||
boxSizing: 'content-box',
|
|
||||||
position: 'absolute',
|
|
||||||
left: '-0.5px',
|
|
||||||
top: 0,
|
|
||||||
height: itemPointHeight,
|
|
||||||
bottom: '50%',
|
|
||||||
width: '16px',
|
|
||||||
borderWidth: '1px',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderLeftColor: treeColor,
|
|
||||||
borderBottomColor: treeColor,
|
|
||||||
borderTop: 'none',
|
|
||||||
borderRight: 'none',
|
|
||||||
borderRadius: '0 0 0 3px',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
collapsed: {
|
const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
||||||
cursor: 'pointer',
|
width: '16px',
|
||||||
display: 'inline-block',
|
height: '1px',
|
||||||
color: '#B9CAD5',
|
paddingLeft: 0,
|
||||||
},
|
paddingRight: 0,
|
||||||
|
top: ITEM_POINT_HEIGHT,
|
||||||
|
transform: 'translate(0, -50%)',
|
||||||
|
opacity: last ? 0 : 'unset',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Collapsed = styled('div')({
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'inline-block',
|
||||||
|
color: '#B9CAD5',
|
||||||
|
});
|
||||||
|
|
||||||
|
const LastItemRadius = styled('div')({
|
||||||
|
boxSizing: 'content-box',
|
||||||
|
position: 'absolute',
|
||||||
|
left: '-0.5px',
|
||||||
|
top: 0,
|
||||||
|
height: ITEM_POINT_HEIGHT,
|
||||||
|
bottom: '50%',
|
||||||
|
width: '16px',
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderLeftColor: TREE_COLOR,
|
||||||
|
borderBottomColor: TREE_COLOR,
|
||||||
|
borderTop: 'none',
|
||||||
|
borderRight: 'none',
|
||||||
|
borderRadius: '0 0 0 3px',
|
||||||
|
pointerEvents: 'none',
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledBorder = styled('div')({
|
const StyledBorder = styled('div')({
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
import type { BlockEditor, AsyncBlock } from './editor';
|
import type { BlockEditor, AsyncBlock } from './editor';
|
||||||
import type { Column } from '@toeverything/datasource/db-service';
|
|
||||||
import { genErrorObj } from '@toeverything/utils';
|
import { genErrorObj } from '@toeverything/utils';
|
||||||
|
|
||||||
export const RootContext = createContext<{
|
const RootContext = createContext<{
|
||||||
editor: BlockEditor;
|
editor: BlockEditor;
|
||||||
// TODO: Temporary fix, dependencies in the new architecture are bottom-up, editors do not need to be passed down from the top
|
// TODO: Temporary fix, dependencies in the new architecture are bottom-up, editors do not need to be passed down from the top
|
||||||
editorElement: () => JSX.Element;
|
editorElement: () => JSX.Element;
|
||||||
@@ -14,6 +13,8 @@ export const RootContext = createContext<{
|
|||||||
) as any
|
) as any
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const EditorProvider = RootContext.Provider;
|
||||||
|
|
||||||
export const useEditor = () => {
|
export const useEditor = () => {
|
||||||
return useContext(RootContext);
|
return useContext(RootContext);
|
||||||
};
|
};
|
||||||
@@ -22,16 +23,3 @@ export const useEditor = () => {
|
|||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export const BlockContext = createContext<AsyncBlock>(null as any);
|
export const BlockContext = createContext<AsyncBlock>(null as any);
|
||||||
|
|
||||||
/**
|
|
||||||
* Context of column information
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export const ColumnsContext = createContext<{
|
|
||||||
fromId: string;
|
|
||||||
columns: Column[];
|
|
||||||
}>({
|
|
||||||
fromId: '',
|
|
||||||
columns: [],
|
|
||||||
});
|
|
||||||
@@ -2,14 +2,14 @@ import type { BlockEditor } from './editor';
|
|||||||
import { styled, usePatchNodes } from '@toeverything/components/ui';
|
import { styled, usePatchNodes } from '@toeverything/components/ui';
|
||||||
import type { FC, PropsWithChildren } from 'react';
|
import type { FC, PropsWithChildren } from 'react';
|
||||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||||
import { RootContext } from './contexts';
|
import { EditorProvider } from './Contexts';
|
||||||
import { SelectionRect, SelectionRef } from './Selection';
|
import { SelectionRect, SelectionRef } from './Selection';
|
||||||
import {
|
import {
|
||||||
Protocol,
|
Protocol,
|
||||||
services,
|
services,
|
||||||
type ReturnUnobserve,
|
type ReturnUnobserve,
|
||||||
} from '@toeverything/datasource/db-service';
|
} from '@toeverything/datasource/db-service';
|
||||||
import { addNewGroup } from './recast-block';
|
import { addNewGroup, appendNewGroup } from './recast-block';
|
||||||
import { useIsOnDrag } from './hooks';
|
import { useIsOnDrag } from './hooks';
|
||||||
|
|
||||||
interface RenderRootProps {
|
interface RenderRootProps {
|
||||||
@@ -151,7 +151,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootContext.Provider value={{ editor, editorElement }}>
|
<EditorProvider value={{ editor, editorElement }}>
|
||||||
<Container
|
<Container
|
||||||
isWhiteboard={editor.isWhiteboard}
|
isWhiteboard={editor.isWhiteboard}
|
||||||
ref={ref => {
|
ref={ref => {
|
||||||
@@ -183,7 +183,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
{editor.isWhiteboard ? null : <ScrollBlank editor={editor} />}
|
{editor.isWhiteboard ? null : <ScrollBlank editor={editor} />}
|
||||||
{patchedNodes}
|
{patchedNodes}
|
||||||
</Container>
|
</Container>
|
||||||
</RootContext.Provider>
|
</EditorProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -199,24 +199,32 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
|
|||||||
mouseMoved.current = false;
|
mouseMoved.current = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastBlock = await editor.getRootLastChildrenBlock();
|
const rootBlock = await editor.getBlockById(
|
||||||
|
editor.getRootBlockId()
|
||||||
|
);
|
||||||
|
if (!rootBlock) {
|
||||||
|
throw new Error('root block is not found');
|
||||||
|
}
|
||||||
|
|
||||||
const lastGroupBlock = await editor.getRootLastChildrenBlock();
|
const lastRootChildren = await rootBlock.lastChild();
|
||||||
// If last block is not a group
|
// If last block is not a group
|
||||||
// create a group with a empty text
|
// create a group with a empty text
|
||||||
if (lastGroupBlock.type !== 'group') {
|
if (lastRootChildren == null) {
|
||||||
addNewGroup(editor, lastBlock, true);
|
appendNewGroup(editor, rootBlock, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastGroupBlock.childrenIds.length > 1) {
|
if (
|
||||||
addNewGroup(editor, lastBlock, true);
|
lastRootChildren.type !== Protocol.Block.Type.group ||
|
||||||
|
lastRootChildren.childrenIds.length > 1
|
||||||
|
) {
|
||||||
|
addNewGroup(editor, lastRootChildren, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the **only** block in the group is text and is empty
|
// If the **only** block in the group is text and is empty
|
||||||
// active the text block
|
// active the text block
|
||||||
const theGroupChildBlock = await lastGroupBlock.firstChild();
|
const theGroupChildBlock = await lastRootChildren.firstChild();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
theGroupChildBlock &&
|
theGroupChildBlock &&
|
||||||
@@ -229,7 +237,7 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// else create a new group
|
// else create a new group
|
||||||
addNewGroup(editor, lastBlock, true);
|
addNewGroup(editor, lastRootChildren, true);
|
||||||
},
|
},
|
||||||
[editor]
|
[editor]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import { AsyncBlock, BlockEditor } from '../editor';
|
|
||||||
import type { FC, ReactElement } from 'react';
|
|
||||||
import { BlockPendantProvider } from '../block-pendant';
|
|
||||||
import { DragDropWrapper } from '../drag-drop-wrapper';
|
|
||||||
|
|
||||||
type BlockContentWrapperProps = {
|
|
||||||
block: AsyncBlock;
|
|
||||||
editor: BlockEditor;
|
|
||||||
children: ReactElement | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WrapperWithPendantAndDragDrop: FC<BlockContentWrapperProps> =
|
|
||||||
function ({ block, children, editor }) {
|
|
||||||
return (
|
|
||||||
<DragDropWrapper block={block} editor={editor}>
|
|
||||||
<BlockPendantProvider block={block}>
|
|
||||||
{children}
|
|
||||||
</BlockPendantProvider>
|
|
||||||
</DragDropWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './BlockContentWrapper';
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { FC, PropsWithChildren } from 'react';
|
import type { FC, PropsWithChildren } from 'react';
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import type { AsyncBlock } from '../editor';
|
import type { AsyncBlock } from '../editor';
|
||||||
import { PendantPopover } from './pendant-popover';
|
import { PendantPopover } from './pendant-popover';
|
||||||
@@ -11,74 +10,68 @@ interface BlockTagProps {
|
|||||||
block: AsyncBlock;
|
block: AsyncBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Need to be refactored
|
|
||||||
*/
|
|
||||||
export const BlockPendantProvider: FC<PropsWithChildren<BlockTagProps>> = ({
|
export const BlockPendantProvider: FC<PropsWithChildren<BlockTagProps>> = ({
|
||||||
block,
|
block,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const [container, setContainer] = useState<HTMLElement>(null);
|
|
||||||
const [isHover, setIsHover] = useState(false);
|
|
||||||
return (
|
return (
|
||||||
<Container ref={(dom: HTMLElement) => setContainer(dom)}>
|
<Container>
|
||||||
{children}
|
{children}
|
||||||
{container && (
|
|
||||||
<PendantPopover
|
<PendantPopover block={block}>
|
||||||
block={block}
|
<StyledTriggerLine />
|
||||||
container={container}
|
</PendantPopover>
|
||||||
onVisibleChange={visible => {
|
|
||||||
setIsHover(visible);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledTriggerLine
|
|
||||||
className="triggerLine"
|
|
||||||
isHover={isHover}
|
|
||||||
/>
|
|
||||||
</PendantPopover>
|
|
||||||
)}
|
|
||||||
<PendantRender block={block} />
|
<PendantRender block={block} />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Container = styled('div')({
|
export const LINE_GAP = 16;
|
||||||
|
const TAG_GAP = 4;
|
||||||
|
|
||||||
|
const StyledTriggerLine = styled('div')({
|
||||||
|
padding: `${TAG_GAP}px 0`,
|
||||||
|
width: '100px',
|
||||||
|
cursor: 'default',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-end',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
padding: '4px',
|
|
||||||
'&:hover .triggerLine::before': {
|
'::before': {
|
||||||
display: 'flex',
|
content: "''",
|
||||||
|
width: '100%',
|
||||||
|
height: '2px',
|
||||||
|
background: '#dadada',
|
||||||
|
display: 'none',
|
||||||
|
position: 'absolute',
|
||||||
|
left: '0',
|
||||||
|
top: '4px',
|
||||||
|
},
|
||||||
|
'::after': {
|
||||||
|
content: "''",
|
||||||
|
width: '0',
|
||||||
|
height: '2px',
|
||||||
|
background: '#aac4d5',
|
||||||
|
display: 'block',
|
||||||
|
position: 'absolute',
|
||||||
|
left: '0',
|
||||||
|
top: '4px',
|
||||||
|
transition: 'width .3s',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledTriggerLine = styled('div')<{ isHover: boolean }>(({ isHover }) => {
|
const Container = styled('div')({
|
||||||
return {
|
position: 'relative',
|
||||||
padding: '4px 0',
|
paddingBottom: `${LINE_GAP - TAG_GAP * 2}px`,
|
||||||
width: '100px',
|
'&:hover': {
|
||||||
cursor: 'default',
|
[StyledTriggerLine.toString()]: {
|
||||||
display: 'flex',
|
'&::before': {
|
||||||
alignItems: 'flex-end',
|
display: 'flex',
|
||||||
position: 'relative',
|
},
|
||||||
|
'&::after': {
|
||||||
'::before': {
|
width: '100%',
|
||||||
content: "''",
|
},
|
||||||
width: '100%',
|
|
||||||
height: '2px',
|
|
||||||
background: '#dadada',
|
|
||||||
display: 'none',
|
|
||||||
position: 'absolute',
|
|
||||||
left: '0',
|
|
||||||
top: '4px',
|
|
||||||
},
|
},
|
||||||
'::after': {
|
},
|
||||||
content: "''",
|
|
||||||
width: isHover ? '100%' : '0',
|
|
||||||
height: '2px',
|
|
||||||
background: '#aac4d5',
|
|
||||||
display: 'block',
|
|
||||||
position: 'absolute',
|
|
||||||
left: '0',
|
|
||||||
top: '4px',
|
|
||||||
transition: 'width .3s',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { ReactNode, useRef, useEffect, useState } from 'react';
|
import React, { ReactNode, useRef, useEffect, useState } from 'react';
|
||||||
import { getPendantHistory } from '../utils';
|
|
||||||
import {
|
import {
|
||||||
getRecastItemValue,
|
getRecastItemValue,
|
||||||
RecastMetaProperty,
|
RecastMetaProperty,
|
||||||
@@ -30,22 +29,22 @@ export const PendantHistoryPanel = ({
|
|||||||
|
|
||||||
const [history, setHistory] = useState<RecastBlockValue[]>([]);
|
const [history, setHistory] = useState<RecastBlockValue[]>([]);
|
||||||
const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({});
|
const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({});
|
||||||
|
const { getValueHistory } = getRecastItemValue(block);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
const currentBlockValues = getRecastItemValue(block).getAllValue();
|
const currentBlockValues = getRecastItemValue(block).getAllValue();
|
||||||
const allProperties = getProperties();
|
const missValues = getProperties().filter(
|
||||||
const missProperties = allProperties.filter(
|
|
||||||
property => !currentBlockValues.find(v => v.id === property.id)
|
property => !currentBlockValues.find(v => v.id === property.id)
|
||||||
);
|
);
|
||||||
const pendantHistory = getPendantHistory({
|
const valueHistory = getValueHistory({
|
||||||
recastBlockId: recastBlock.id,
|
recastBlockId: recastBlock.id,
|
||||||
});
|
});
|
||||||
const historyMap = missProperties.reduce<{
|
const historyMap = missValues.reduce<{
|
||||||
[key: RecastPropertyId]: string;
|
[key: RecastPropertyId]: string[];
|
||||||
}>((history, property) => {
|
}>((history, property) => {
|
||||||
if (pendantHistory[property.id]) {
|
if (valueHistory[property.id]) {
|
||||||
history[property.id] = pendantHistory[property.id];
|
history[property.id] = valueHistory[property.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
return history;
|
return history;
|
||||||
@@ -54,18 +53,30 @@ export const PendantHistoryPanel = ({
|
|||||||
const blockHistory = (
|
const blockHistory = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(historyMap).map(
|
Object.entries(historyMap).map(
|
||||||
async ([propertyId, blockId]) => {
|
async ([propertyId, blockIds]) => {
|
||||||
const latestValueBlock = (
|
const blocks = await groupBlock.children();
|
||||||
await groupBlock.children()
|
const latestChangeBlock = blockIds
|
||||||
).find((block: AsyncBlock) => block.id === blockId);
|
.reverse()
|
||||||
|
.reduce<AsyncBlock>((block, id) => {
|
||||||
|
if (!block) {
|
||||||
|
return blocks.find(
|
||||||
|
block => block.id === id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}, null);
|
||||||
|
|
||||||
return getRecastItemValue(
|
if (latestChangeBlock) {
|
||||||
latestValueBlock
|
return getRecastItemValue(
|
||||||
).getValue(propertyId as RecastPropertyId);
|
latestChangeBlock
|
||||||
|
).getValue(propertyId as RecastPropertyId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).filter(v => v);
|
).filter(v => v);
|
||||||
|
|
||||||
setHistory(blockHistory);
|
setHistory(blockHistory);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ModifyPanelContentProps } from './types';
|
|||||||
import { StyledDivider, StyledPopoverSubTitle } from '../StyledComponent';
|
import { StyledDivider, StyledPopoverSubTitle } from '../StyledComponent';
|
||||||
import { BasicSelect } from './Select';
|
import { BasicSelect } from './Select';
|
||||||
import { InformationProperty, InformationValue } from '../../recast-block';
|
import { InformationProperty, InformationValue } from '../../recast-block';
|
||||||
import { genInitialOptions, getPendantIconsConfigByName } from '../utils';
|
import { generateInitialOptions, getPendantIconsConfigByName } from '../utils';
|
||||||
|
|
||||||
export default (props: ModifyPanelContentProps) => {
|
export default (props: ModifyPanelContentProps) => {
|
||||||
const { onPropertyChange, onValueChange, initialValue, property } = props;
|
const { onPropertyChange, onValueChange, initialValue, property } = props;
|
||||||
@@ -38,7 +38,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.emailOptions ||
|
propProperty?.emailOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Email')
|
getPendantIconsConfigByName('Email')
|
||||||
)
|
)
|
||||||
@@ -66,7 +66,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.phoneOptions ||
|
propProperty?.phoneOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Phone')
|
getPendantIconsConfigByName('Phone')
|
||||||
)
|
)
|
||||||
@@ -94,7 +94,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.locationOptions ||
|
propProperty?.locationOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Location')
|
getPendantIconsConfigByName('Location')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ export default ({
|
|||||||
user: { username, nickname, photo },
|
user: { username, nickname, photo },
|
||||||
} = useUserAndSpaces();
|
} = useUserAndSpaces();
|
||||||
|
|
||||||
const [selectedValue, setSelectedValue] = useState(initialValue?.value);
|
const [selectedValue, setSelectedValue] = useState(
|
||||||
|
initialValue?.value || ''
|
||||||
|
);
|
||||||
const [focus, setFocus] = useState(false);
|
const [focus, setFocus] = useState(false);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import { HighLightIconInput } from './IconInput';
|
import { HighLightIconInput } from './IconInput';
|
||||||
import { PendantConfig, IconNames, OptionIdType, OptionType } from '../types';
|
import { PendantConfig, IconNames, OptionIdType, OptionType } from '../types';
|
||||||
import { genBasicOption } from '../utils';
|
import { generateBasicOption } from '../utils';
|
||||||
|
|
||||||
type OptionItemType = {
|
type OptionItemType = {
|
||||||
option: OptionType;
|
option: OptionType;
|
||||||
@@ -66,7 +66,7 @@ export const BasicSelect = ({
|
|||||||
const [selectIds, setSelectIds] = useState<OptionIdType[]>(initialValue);
|
const [selectIds, setSelectIds] = useState<OptionIdType[]>(initialValue);
|
||||||
|
|
||||||
const insertOption = (insertId: OptionIdType) => {
|
const insertOption = (insertId: OptionIdType) => {
|
||||||
const newOption = genBasicOption({
|
const newOption = generateBasicOption({
|
||||||
index: options.length + 1,
|
index: options.length + 1,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
||||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
import { HelpCenterIcon } from '@toeverything/components/icons';
|
||||||
import { AsyncBlock } from '../../editor';
|
import { AsyncBlock } from '../../editor';
|
||||||
@@ -15,13 +14,13 @@ import {
|
|||||||
StyledPopoverSubTitle,
|
StyledPopoverSubTitle,
|
||||||
StyledPopoverWrapper,
|
StyledPopoverWrapper,
|
||||||
} from '../StyledComponent';
|
} from '../StyledComponent';
|
||||||
import { genInitialOptions, getPendantConfigByType } from '../utils';
|
import {
|
||||||
|
generateRandomFieldName,
|
||||||
|
generateInitialOptions,
|
||||||
|
getPendantConfigByType,
|
||||||
|
} from '../utils';
|
||||||
import { useOnCreateSure } from './hooks';
|
import { useOnCreateSure } from './hooks';
|
||||||
|
|
||||||
const upperFirst = (str: string) => {
|
|
||||||
return `${str[0].toUpperCase()}${str.slice(1)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CreatePendantPanel = ({
|
export const CreatePendantPanel = ({
|
||||||
block,
|
block,
|
||||||
onSure,
|
onSure,
|
||||||
@@ -35,7 +34,7 @@ export const CreatePendantPanel = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedOption &&
|
selectedOption &&
|
||||||
setFieldName(upperFirst(`${selectedOption.type}#${nanoid(4)}`));
|
setFieldName(generateRandomFieldName(selectedOption.type));
|
||||||
}, [selectedOption]);
|
}, [selectedOption]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -45,7 +44,7 @@ export const CreatePendantPanel = ({
|
|||||||
<Select
|
<Select
|
||||||
width={284}
|
width={284}
|
||||||
placeholder="Search for a field type"
|
placeholder="Search for a field type"
|
||||||
value={selectedOption}
|
value={selectedOption ?? null}
|
||||||
onChange={(selectedValue: PendantOptions) => {
|
onChange={(selectedValue: PendantOptions) => {
|
||||||
setSelectedOption(selectedValue);
|
setSelectedOption(selectedValue);
|
||||||
}}
|
}}
|
||||||
@@ -93,7 +92,7 @@ export const CreatePendantPanel = ({
|
|||||||
<PendantModifyPanel
|
<PendantModifyPanel
|
||||||
type={selectedOption.type}
|
type={selectedOption.type}
|
||||||
// Select, MultiSelect, Status use this props as initial property
|
// Select, MultiSelect, Status use this props as initial property
|
||||||
initialOptions={genInitialOptions(
|
initialOptions={generateInitialOptions(
|
||||||
selectedOption.type,
|
selectedOption.type,
|
||||||
getPendantConfigByType(selectedOption.type)
|
getPendantConfigByType(selectedOption.type)
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { HelpCenterIcon } from '@toeverything/components/icons';
|
|||||||
import { PendantModifyPanel } from '../pendant-modify-panel';
|
import { PendantModifyPanel } from '../pendant-modify-panel';
|
||||||
import type { AsyncBlock } from '../../editor';
|
import type { AsyncBlock } from '../../editor';
|
||||||
import {
|
import {
|
||||||
|
getRecastItemValue,
|
||||||
type RecastBlockValue,
|
type RecastBlockValue,
|
||||||
type RecastMetaProperty,
|
type RecastMetaProperty,
|
||||||
} from '../../recast-block';
|
} from '../../recast-block';
|
||||||
import { getPendantConfigByType } from '../utils';
|
import { getPendantConfigByType } from '../utils';
|
||||||
import { usePendant } from '../use-pendant';
|
|
||||||
import {
|
import {
|
||||||
StyledPopoverWrapper,
|
StyledPopoverWrapper,
|
||||||
StyledOperationLabel,
|
StyledOperationLabel,
|
||||||
@@ -42,7 +42,8 @@ export const UpdatePendantPanel = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const pendantOption = pendantOptions.find(v => v.type === property.type);
|
const pendantOption = pendantOptions.find(v => v.type === property.type);
|
||||||
const iconConfig = getPendantConfigByType(property.type);
|
const iconConfig = getPendantConfigByType(property.type);
|
||||||
const { removePendant } = usePendant(block);
|
const { removeValue } = getRecastItemValue(block);
|
||||||
|
|
||||||
const Icon = IconMap[iconConfig.iconName];
|
const Icon = IconMap[iconConfig.iconName];
|
||||||
const [fieldName, setFieldName] = useState(property.name);
|
const [fieldName, setFieldName] = useState(property.name);
|
||||||
const onUpdateSure = useOnUpdateSure({ block, property });
|
const onUpdateSure = useOnUpdateSure({ block, property });
|
||||||
@@ -108,7 +109,7 @@ export const UpdatePendantPanel = ({
|
|||||||
onDelete={
|
onDelete={
|
||||||
hasDelete
|
hasDelete
|
||||||
? async () => {
|
? async () => {
|
||||||
await removePendant(property);
|
await removeValue(property.id);
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import {
|
import {
|
||||||
genSelectOptionId,
|
genSelectOptionId,
|
||||||
|
getRecastItemValue,
|
||||||
type InformationProperty,
|
type InformationProperty,
|
||||||
type MultiSelectProperty,
|
type MultiSelectProperty,
|
||||||
type RecastMetaProperty,
|
type RecastMetaProperty,
|
||||||
type SelectOption,
|
type SelectOption,
|
||||||
type SelectProperty,
|
type SelectProperty,
|
||||||
|
useRecastBlock,
|
||||||
useRecastBlockMeta,
|
useRecastBlockMeta,
|
||||||
useSelectProperty,
|
useSelectProperty,
|
||||||
|
SelectValue,
|
||||||
|
MultiSelectValue,
|
||||||
|
StatusValue,
|
||||||
|
InformationValue,
|
||||||
|
TextValue,
|
||||||
|
DateValue,
|
||||||
} from '../../recast-block';
|
} from '../../recast-block';
|
||||||
import { type AsyncBlock } from '../../editor';
|
import { type AsyncBlock } from '../../editor';
|
||||||
import { usePendant } from '../use-pendant';
|
|
||||||
import {
|
import {
|
||||||
type OptionType,
|
type OptionType,
|
||||||
PendantTypes,
|
PendantTypes,
|
||||||
@@ -41,8 +48,8 @@ const genOptionWithId = (options: OptionType[] = []) => {
|
|||||||
export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
||||||
const { addProperty } = useRecastBlockMeta();
|
const { addProperty } = useRecastBlockMeta();
|
||||||
const { createSelect } = useSelectProperty();
|
const { createSelect } = useSelectProperty();
|
||||||
const { setPendant } = usePendant(block);
|
const recastBlock = useRecastBlock();
|
||||||
|
const { setValue } = getRecastItemValue(block);
|
||||||
return async ({
|
return async ({
|
||||||
type,
|
type,
|
||||||
fieldName,
|
fieldName,
|
||||||
@@ -79,7 +86,14 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
|||||||
tempSelectedId: newValue,
|
tempSelectedId: newValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
await setPendant(newProperty, selectedId);
|
await setValue(
|
||||||
|
{
|
||||||
|
id: newProperty.id,
|
||||||
|
type: newProperty.type,
|
||||||
|
value: selectedId,
|
||||||
|
} as SelectValue | MultiSelectValue | StatusValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
} else if (type === PendantTypes.Information) {
|
} else if (type === PendantTypes.Information) {
|
||||||
const emailOptions = genOptionWithId(newPropertyItem.emailOptions);
|
const emailOptions = genOptionWithId(newPropertyItem.emailOptions);
|
||||||
|
|
||||||
@@ -97,26 +111,33 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
|||||||
locationOptions,
|
locationOptions,
|
||||||
} as Omit<InformationProperty, 'id'>);
|
} as Omit<InformationProperty, 'id'>);
|
||||||
|
|
||||||
await setPendant(newProperty, {
|
await setValue(
|
||||||
email: getOfficialSelected({
|
{
|
||||||
isMulti: true,
|
id: newProperty.id,
|
||||||
options: emailOptions,
|
type: newProperty.type,
|
||||||
tempOptions: newPropertyItem.emailOptions,
|
value: {
|
||||||
tempSelectedId: newValue.email,
|
email: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
phone: getOfficialSelected({
|
options: emailOptions,
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.emailOptions,
|
||||||
options: phoneOptions,
|
tempSelectedId: newValue.email,
|
||||||
tempOptions: newPropertyItem.phoneOptions,
|
}),
|
||||||
tempSelectedId: newValue.phone,
|
phone: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
location: getOfficialSelected({
|
options: phoneOptions,
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.phoneOptions,
|
||||||
options: locationOptions,
|
tempSelectedId: newValue.phone,
|
||||||
tempOptions: newPropertyItem.locationOptions,
|
}),
|
||||||
tempSelectedId: newValue.location,
|
location: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
});
|
options: locationOptions,
|
||||||
|
tempOptions: newPropertyItem.locationOptions,
|
||||||
|
tempSelectedId: newValue.location,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} as InformationValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Color and background should use pendant config, but ui is not design now
|
// TODO: Color and background should use pendant config, but ui is not design now
|
||||||
const iconConfig = getPendantConfigByType(type);
|
const iconConfig = getPendantConfigByType(type);
|
||||||
@@ -129,8 +150,14 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
|||||||
color: iconConfig.color as CSSProperties['color'],
|
color: iconConfig.color as CSSProperties['color'],
|
||||||
iconName: iconConfig.iconName,
|
iconName: iconConfig.iconName,
|
||||||
});
|
});
|
||||||
|
await setValue(
|
||||||
await setPendant(newProperty, newValue);
|
{
|
||||||
|
id: newProperty.id,
|
||||||
|
type: newProperty.type,
|
||||||
|
value: newValue,
|
||||||
|
} as TextValue | DateValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -144,8 +171,9 @@ export const useOnUpdateSure = ({
|
|||||||
property: RecastMetaProperty;
|
property: RecastMetaProperty;
|
||||||
}) => {
|
}) => {
|
||||||
const { updateSelect } = useSelectProperty();
|
const { updateSelect } = useSelectProperty();
|
||||||
const { setPendant } = usePendant(block);
|
|
||||||
const { updateProperty } = useRecastBlockMeta();
|
const { updateProperty } = useRecastBlockMeta();
|
||||||
|
const { setValue } = getRecastItemValue(block);
|
||||||
|
const recastBlock = useRecastBlock();
|
||||||
|
|
||||||
return async ({
|
return async ({
|
||||||
type,
|
type,
|
||||||
@@ -199,7 +227,14 @@ export const useOnUpdateSure = ({
|
|||||||
tempSelectedId: newValue,
|
tempSelectedId: newValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
await setPendant(selectProperty, selectedId);
|
await setValue(
|
||||||
|
{
|
||||||
|
id: selectProperty.id,
|
||||||
|
type: selectProperty.type,
|
||||||
|
value: selectedId,
|
||||||
|
} as SelectValue | MultiSelectValue | StatusValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
} else if (type === PendantTypes.Information) {
|
} else if (type === PendantTypes.Information) {
|
||||||
// const { emailOptions, phoneOptions, locationOptions } =
|
// const { emailOptions, phoneOptions, locationOptions } =
|
||||||
// property as InformationProperty;
|
// property as InformationProperty;
|
||||||
@@ -231,28 +266,42 @@ export const useOnUpdateSure = ({
|
|||||||
locationOptions,
|
locationOptions,
|
||||||
} as InformationProperty);
|
} as InformationProperty);
|
||||||
|
|
||||||
await setPendant(newProperty, {
|
await setValue(
|
||||||
email: getOfficialSelected({
|
{
|
||||||
isMulti: true,
|
id: newProperty.id,
|
||||||
options: emailOptions as SelectOption[],
|
type: newProperty.type,
|
||||||
tempOptions: newPropertyItem.emailOptions,
|
value: {
|
||||||
tempSelectedId: newValue.email,
|
email: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
phone: getOfficialSelected({
|
options: emailOptions as SelectOption[],
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.emailOptions,
|
||||||
options: phoneOptions as SelectOption[],
|
tempSelectedId: newValue.email,
|
||||||
tempOptions: newPropertyItem.phoneOptions,
|
}),
|
||||||
tempSelectedId: newValue.phone,
|
phone: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
location: getOfficialSelected({
|
options: phoneOptions as SelectOption[],
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.phoneOptions,
|
||||||
options: locationOptions as SelectOption[],
|
tempSelectedId: newValue.phone,
|
||||||
tempOptions: newPropertyItem.locationOptions,
|
}),
|
||||||
tempSelectedId: newValue.location,
|
location: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
});
|
options: locationOptions as SelectOption[],
|
||||||
|
tempOptions: newPropertyItem.locationOptions,
|
||||||
|
tempSelectedId: newValue.location,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} as InformationValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await setPendant(property, newValue);
|
await setValue(
|
||||||
|
{
|
||||||
|
id: property.id,
|
||||||
|
type: property.type,
|
||||||
|
value: newValue,
|
||||||
|
} as TextValue | DateValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldName !== property.name) {
|
if (fieldName !== property.name) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { FC, useRef } from 'react';
|
import { FC, useRef } from 'react';
|
||||||
import { AsyncBlock } from '../../editor';
|
import { AsyncBlock } from '../../editor';
|
||||||
import { PendantHistoryPanel } from '../pendant-history-panel';
|
import { PendantHistoryPanel } from '../pendant-history-panel';
|
||||||
import {
|
import {
|
||||||
@@ -21,8 +21,6 @@ export const PendantPopover: FC<
|
|||||||
pointerEnterDelay={300}
|
pointerEnterDelay={300}
|
||||||
pointerLeaveDelay={200}
|
pointerLeaveDelay={200}
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
// visible={true}
|
|
||||||
// trigger="click"
|
|
||||||
content={
|
content={
|
||||||
<PendantHistoryPanel
|
<PendantHistoryPanel
|
||||||
block={block}
|
block={block}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
MuiZoom,
|
MuiFade,
|
||||||
Popover,
|
Popover,
|
||||||
PopperHandler,
|
PopperHandler,
|
||||||
styled,
|
styled,
|
||||||
@@ -100,16 +100,15 @@ export const PendantRender = ({ block }: { block: AsyncBlock }) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{hasAddBtn ? (
|
{hasAddBtn ? (
|
||||||
<MuiZoom in={showAddBtn}>
|
<MuiFade in={showAddBtn}>
|
||||||
<div>
|
<div>
|
||||||
<AddPendantPopover
|
<AddPendantPopover
|
||||||
block={block}
|
block={block}
|
||||||
iconStyle={{ marginTop: 4 }}
|
iconStyle={{ marginTop: 4 }}
|
||||||
container={blockRenderContainerRef.current}
|
container={blockRenderContainerRef.current}
|
||||||
trigger="click"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</MuiZoom>
|
</MuiFade>
|
||||||
) : null}
|
) : null}
|
||||||
</BlockPendantContainer>
|
</BlockPendantContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
import { removePropertyValueRecord, setPendantHistory } from './utils';
|
|
||||||
import { AsyncBlock } from '../editor';
|
|
||||||
import {
|
|
||||||
getRecastItemValue,
|
|
||||||
RecastMetaProperty,
|
|
||||||
useRecastBlock,
|
|
||||||
} from '../recast-block';
|
|
||||||
|
|
||||||
export const usePendant = (block: AsyncBlock) => {
|
|
||||||
// const { getProperties, removeProperty } = useRecastBlockMeta();
|
|
||||||
const recastBlock = useRecastBlock();
|
|
||||||
const { getValue, setValue, removeValue } = getRecastItemValue(block);
|
|
||||||
// const { updateSelect } = useSelectProperty();
|
|
||||||
|
|
||||||
const setPendant = async (property: RecastMetaProperty, newValue: any) => {
|
|
||||||
const nv = {
|
|
||||||
id: property.id,
|
|
||||||
type: property.type,
|
|
||||||
value: newValue,
|
|
||||||
};
|
|
||||||
await setValue(nv);
|
|
||||||
setPendantHistory({
|
|
||||||
recastBlockId: recastBlock.id,
|
|
||||||
blockId: block.id,
|
|
||||||
propertyId: property.id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const removePendant = async (property: RecastMetaProperty) => {
|
|
||||||
await removeValue(property.id);
|
|
||||||
removePropertyValueRecord({
|
|
||||||
recastBlockId: block.id,
|
|
||||||
propertyId: property.id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
setPendant,
|
|
||||||
removePendant,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,84 +1,7 @@
|
|||||||
import {
|
import { PropertyType, SelectOption } from '../recast-block';
|
||||||
PropertyType,
|
import { OptionIdType, OptionType, PendantConfig, PendantTypes } from './types';
|
||||||
RecastBlockValue,
|
|
||||||
RecastPropertyId,
|
|
||||||
SelectOption,
|
|
||||||
} from '../recast-block';
|
|
||||||
import { OptionIdType, OptionType } from './types';
|
|
||||||
import { pendantConfig } from './config';
|
import { pendantConfig } from './config';
|
||||||
import { PendantConfig, PendantTypes } from './types';
|
import { nanoid } from 'nanoid';
|
||||||
type Props = {
|
|
||||||
recastBlockId: string;
|
|
||||||
blockId: string;
|
|
||||||
propertyId: RecastPropertyId;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StorageMap = {
|
|
||||||
[recastBlockId: string]: {
|
|
||||||
[propertyId: RecastPropertyId]: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const LOCAL_STORAGE_NAME = 'TEMPORARY_PENDANT_DATA';
|
|
||||||
|
|
||||||
const ensureLocalStorage = () => {
|
|
||||||
const data = localStorage.getItem(LOCAL_STORAGE_NAME);
|
|
||||||
if (!data) {
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify({}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setPendantHistory = ({
|
|
||||||
recastBlockId,
|
|
||||||
blockId,
|
|
||||||
propertyId,
|
|
||||||
}: Props) => {
|
|
||||||
ensureLocalStorage();
|
|
||||||
const data: StorageMap = JSON.parse(
|
|
||||||
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!data[recastBlockId]) {
|
|
||||||
data[recastBlockId] = {};
|
|
||||||
}
|
|
||||||
const propertyValueRecord = data[recastBlockId];
|
|
||||||
propertyValueRecord[propertyId] = blockId;
|
|
||||||
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPendantHistory = ({
|
|
||||||
recastBlockId,
|
|
||||||
}: {
|
|
||||||
recastBlockId: string;
|
|
||||||
}) => {
|
|
||||||
ensureLocalStorage();
|
|
||||||
const data: StorageMap = JSON.parse(
|
|
||||||
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
|
||||||
);
|
|
||||||
|
|
||||||
return data[recastBlockId] ?? {};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removePropertyValueRecord = ({
|
|
||||||
recastBlockId,
|
|
||||||
propertyId,
|
|
||||||
}: {
|
|
||||||
recastBlockId: string;
|
|
||||||
propertyId: RecastPropertyId;
|
|
||||||
}) => {
|
|
||||||
ensureLocalStorage();
|
|
||||||
const data: StorageMap = JSON.parse(
|
|
||||||
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
|
||||||
);
|
|
||||||
if (!data[recastBlockId]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete data[recastBlockId][propertyId];
|
|
||||||
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In select pendant panel, use mock options instead of use `createSelect` when add or delete option
|
* In select pendant panel, use mock options instead of use `createSelect` when add or delete option
|
||||||
@@ -107,7 +30,7 @@ export const getOfficialSelected = ({
|
|||||||
.map(id => {
|
.map(id => {
|
||||||
return tempOptions.findIndex((o: OptionType) => o.id === id);
|
return tempOptions.findIndex((o: OptionType) => o.id === id);
|
||||||
})
|
})
|
||||||
.filter(index => index != -1);
|
.filter(index => index !== -1);
|
||||||
selectedId = selectedIndex.map((index: number) => {
|
selectedId = selectedIndex.map((index: number) => {
|
||||||
return options[index].id;
|
return options[index].id;
|
||||||
});
|
});
|
||||||
@@ -130,7 +53,7 @@ export const getPendantIconsConfigByName = (
|
|||||||
return pendantConfig[pendantName];
|
return pendantConfig[pendantName];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const genBasicOption = ({
|
export const generateBasicOption = ({
|
||||||
index,
|
index,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
name = '',
|
name = '',
|
||||||
@@ -159,22 +82,22 @@ export const genBasicOption = ({
|
|||||||
/**
|
/**
|
||||||
* Status Pendant is a Select Pendant built-in some options
|
* Status Pendant is a Select Pendant built-in some options
|
||||||
* **/
|
* **/
|
||||||
export const genInitialOptions = (
|
export const generateInitialOptions = (
|
||||||
type: PendantTypes,
|
type: PendantTypes,
|
||||||
iconConfig: PendantConfig
|
iconConfig: PendantConfig
|
||||||
) => {
|
) => {
|
||||||
if (type === PendantTypes.Status) {
|
if (type === PendantTypes.Status) {
|
||||||
return [
|
return [
|
||||||
genBasicOption({ index: 0, iconConfig, name: 'No Started' }),
|
generateBasicOption({ index: 0, iconConfig, name: 'No Started' }),
|
||||||
genBasicOption({
|
generateBasicOption({
|
||||||
index: 1,
|
index: 1,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
name: 'In Progress',
|
name: 'In Progress',
|
||||||
}),
|
}),
|
||||||
genBasicOption({ index: 2, iconConfig, name: 'Complete' }),
|
generateBasicOption({ index: 2, iconConfig, name: 'Complete' }),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return [genBasicOption({ index: 0, iconConfig })];
|
return [generateBasicOption({ index: 0, iconConfig })];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkPendantForm = (
|
export const checkPendantForm = (
|
||||||
@@ -222,3 +145,10 @@ export const checkPendantForm = (
|
|||||||
|
|
||||||
return { passed: true, message: 'Check passed !' };
|
return { passed: true, message: 'Check passed !' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const upperFirst = (str: string) => {
|
||||||
|
return `${str[0].toUpperCase()}${str.slice(1)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateRandomFieldName = (type: PendantTypes) =>
|
||||||
|
upperFirst(`${type}#${nanoid(4)}`);
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { AsyncBlock, BlockEditor } from '../editor';
|
|
||||||
import { ReactElement } from 'react';
|
|
||||||
|
|
||||||
interface DragDropWrapperProps {
|
|
||||||
editor: BlockEditor;
|
|
||||||
block: AsyncBlock;
|
|
||||||
children: ReactElement | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DragDropWrapper({
|
|
||||||
children,
|
|
||||||
editor,
|
|
||||||
block,
|
|
||||||
}: DragDropWrapperProps) {
|
|
||||||
const handlerDragOver: React.DragEventHandler<HTMLDivElement> = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (block.dom) {
|
|
||||||
editor.getHooks().afterOnNodeDragOver(event, {
|
|
||||||
blockId: block.id,
|
|
||||||
dom: block.dom,
|
|
||||||
rect: block.dom?.getBoundingClientRect(),
|
|
||||||
type: block.type,
|
|
||||||
properties: block.getProperties(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return <div onDragOver={handlerDragOver}>{children}</div>;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './DragDropWrapper';
|
|
||||||
@@ -12,6 +12,7 @@ enum DragType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DRAG_STATE_CHANGE_EVENT_KEY = 'dragStateChange';
|
const DRAG_STATE_CHANGE_EVENT_KEY = 'dragStateChange';
|
||||||
|
const MAX_GRID_BLOCK_FLOOR = 3;
|
||||||
export class DragDropManager {
|
export class DragDropManager {
|
||||||
private _editor: Editor;
|
private _editor: Editor;
|
||||||
private _enabled: boolean;
|
private _enabled: boolean;
|
||||||
@@ -231,6 +232,17 @@ export class DragDropManager {
|
|||||||
if (!(await this._canBeDrop(event))) {
|
if (!(await this._canBeDrop(event))) {
|
||||||
direction = BlockDropPlacement.none;
|
direction = BlockDropPlacement.none;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
direction === BlockDropPlacement.left ||
|
||||||
|
direction === BlockDropPlacement.right
|
||||||
|
) {
|
||||||
|
const path = await this._editor.getBlockPath(blockId);
|
||||||
|
const gridBlocks = path.filter(block => block.type === 'grid');
|
||||||
|
// limit grid block floor counts
|
||||||
|
if (gridBlocks.length >= MAX_GRID_BLOCK_FLOOR) {
|
||||||
|
direction = BlockDropPlacement.none;
|
||||||
|
}
|
||||||
|
}
|
||||||
this._setBlockDragDirection(direction);
|
this._setBlockDragDirection(direction);
|
||||||
return direction;
|
return direction;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,7 +340,20 @@ export class Editor implements Virgo {
|
|||||||
const rootBlockId = this.getRootBlockId();
|
const rootBlockId = this.getRootBlockId();
|
||||||
const rootBlock = await this.getBlockById(rootBlockId);
|
const rootBlock = await this.getBlockById(rootBlockId);
|
||||||
const blockList: Array<AsyncBlock> = rootBlock ? [rootBlock] : [];
|
const blockList: Array<AsyncBlock> = rootBlock ? [rootBlock] : [];
|
||||||
const children = (await rootBlock?.children()) || [];
|
return [...blockList, ...(await this.getOffspring(rootBlockId))];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* get all offspring of block
|
||||||
|
* @param {string} id
|
||||||
|
* @return {*}
|
||||||
|
* @memberof Editor
|
||||||
|
*/
|
||||||
|
async getOffspring(id: string) {
|
||||||
|
const block = await this.getBlockById(id);
|
||||||
|
const blockList: Array<AsyncBlock> = [];
|
||||||
|
const children = (await block?.children()) || [];
|
||||||
for (const block of children) {
|
for (const block of children) {
|
||||||
if (!block) {
|
if (!block) {
|
||||||
continue;
|
continue;
|
||||||
@@ -354,15 +367,6 @@ export class Editor implements Virgo {
|
|||||||
return blockList;
|
return blockList;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRootLastChildrenBlock(rootBlockId = this.getRootBlockId()) {
|
|
||||||
const rootBlock = await this.getBlockById(rootBlockId);
|
|
||||||
if (!rootBlock) {
|
|
||||||
throw new Error('root block is not found');
|
|
||||||
}
|
|
||||||
const lastChildren = await rootBlock.lastChild();
|
|
||||||
return lastChildren ?? rootBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLastBlock(rootBlockId = this.getRootBlockId()) {
|
async getLastBlock(rootBlockId = this.getRootBlockId()) {
|
||||||
const rootBlock = await this.getBlockById(rootBlockId);
|
const rootBlock = await this.getBlockById(rootBlockId);
|
||||||
if (!rootBlock) {
|
if (!rootBlock) {
|
||||||
@@ -379,6 +383,20 @@ export class Editor implements Virgo {
|
|||||||
return lastBlock;
|
return lastBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBlockPath(id: string) {
|
||||||
|
const block = await this.getBlockById(id);
|
||||||
|
if (!block) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const path = [block];
|
||||||
|
let parent = await block.parent();
|
||||||
|
while (parent) {
|
||||||
|
path.unshift(parent);
|
||||||
|
parent = await parent.parent();
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
async getBlockByPoint(point: Point) {
|
async getBlockByPoint(point: Point) {
|
||||||
const blockList = await this.getBlockList();
|
const blockList = await this.getBlockList();
|
||||||
|
|
||||||
|
|||||||
@@ -35,20 +35,6 @@ export class KeyboardManager {
|
|||||||
}
|
}
|
||||||
this.handler_map = {};
|
this.handler_map = {};
|
||||||
|
|
||||||
// WARNING: Remove the filter of hotkeys, the input event of input/select/textarea will be filtered out by default
|
|
||||||
// When there is a problem with the input of the text component, you need to pay attention to this
|
|
||||||
const old_filter = HotKeys.filter;
|
|
||||||
HotKeys.filter = event => {
|
|
||||||
let parent = (event.target as Element).parentElement;
|
|
||||||
while (parent) {
|
|
||||||
if (parent === editor.container) {
|
|
||||||
return old_filter(event);
|
|
||||||
}
|
|
||||||
parent = parent.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
HotKeys.setScope('editor');
|
HotKeys.setScope('editor');
|
||||||
|
|
||||||
// this.init_common_shortcut_cb();
|
// this.init_common_shortcut_cb();
|
||||||
|
|||||||
@@ -113,13 +113,6 @@ export class Hooks implements HooksRunner, PluginHooks {
|
|||||||
this._runHook(HookType.ON_ROOTNODE_DRAG_OVER_CAPTURE, e);
|
this._runHook(HookType.ON_ROOTNODE_DRAG_OVER_CAPTURE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public afterOnNodeDragOver(
|
|
||||||
e: React.DragEvent<Element>,
|
|
||||||
node: BlockDomInfo
|
|
||||||
): void {
|
|
||||||
this._runHook(HookType.AFTER_ON_NODE_DRAG_OVER, e, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSearch(): void {
|
public onSearch(): void {
|
||||||
this._runHook(HookType.ON_SEARCH);
|
this._runHook(HookType.ON_SEARCH);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ export class ScrollManager {
|
|||||||
|
|
||||||
constructor(editor: BlockEditor) {
|
constructor(editor: BlockEditor) {
|
||||||
this._editor = editor;
|
this._editor = editor;
|
||||||
(window as any).scrollManager = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateScrollInfo(left: number, top: number) {
|
private _updateScrollInfo(left: number, top: number) {
|
||||||
@@ -111,6 +110,7 @@ export class ScrollManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public emitScrollEvent(event: UIEvent) {
|
public emitScrollEvent(event: UIEvent) {
|
||||||
|
this.scrollContainer = event.target as HTMLElement;
|
||||||
this._scrollDirection = this._getScrollDirection();
|
this._scrollDirection = this._getScrollDirection();
|
||||||
this._scrollMoveOffset = Math.abs(
|
this._scrollMoveOffset = Math.abs(
|
||||||
this.scrollContainer.scrollTop - this._scrollRecord[0]
|
this.scrollContainer.scrollTop - this._scrollRecord[0]
|
||||||
|
|||||||
@@ -177,7 +177,6 @@ export enum HookType {
|
|||||||
ON_ROOTNODE_DRAG_END = 'onRootNodeDragEnd',
|
ON_ROOTNODE_DRAG_END = 'onRootNodeDragEnd',
|
||||||
ON_ROOTNODE_DRAG_OVER_CAPTURE = 'onRootNodeDragOverCapture',
|
ON_ROOTNODE_DRAG_OVER_CAPTURE = 'onRootNodeDragOverCapture',
|
||||||
ON_ROOTNODE_DROP = 'onRootNodeDrop',
|
ON_ROOTNODE_DROP = 'onRootNodeDrop',
|
||||||
AFTER_ON_NODE_DRAG_OVER = 'afterOnNodeDragOver',
|
|
||||||
BEFORE_COPY = 'beforeCopy',
|
BEFORE_COPY = 'beforeCopy',
|
||||||
BEFORE_CUT = 'beforeCut',
|
BEFORE_CUT = 'beforeCut',
|
||||||
ON_ROOTNODE_SCROLL = 'onRootNodeScroll',
|
ON_ROOTNODE_SCROLL = 'onRootNodeScroll',
|
||||||
@@ -219,10 +218,6 @@ export interface HooksRunner {
|
|||||||
onRootNodeDragEnd: (e: React.DragEvent<Element>) => void;
|
onRootNodeDragEnd: (e: React.DragEvent<Element>) => void;
|
||||||
onRootNodeDragLeave: (e: React.DragEvent<Element>) => void;
|
onRootNodeDragLeave: (e: React.DragEvent<Element>) => void;
|
||||||
onRootNodeDrop: (e: React.DragEvent<Element>) => void;
|
onRootNodeDrop: (e: React.DragEvent<Element>) => void;
|
||||||
afterOnNodeDragOver: (
|
|
||||||
e: React.DragEvent<Element>,
|
|
||||||
node: BlockDomInfo
|
|
||||||
) => void;
|
|
||||||
beforeCopy: (e: ClipboardEvent) => void;
|
beforeCopy: (e: ClipboardEvent) => void;
|
||||||
beforeCut: (e: ClipboardEvent) => void;
|
beforeCut: (e: ClipboardEvent) => void;
|
||||||
onRootNodeScroll: (e: React.UIEvent) => void;
|
onRootNodeScroll: (e: React.UIEvent) => void;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { noop, Point } from '@toeverything/utils';
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useEditor } from './Contexts';
|
||||||
import {
|
import {
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
BlockEditor,
|
BlockEditor,
|
||||||
@@ -5,9 +8,6 @@ import {
|
|||||||
SelectionInfo,
|
SelectionInfo,
|
||||||
SelectionSettingsMap,
|
SelectionSettingsMap,
|
||||||
} from './editor';
|
} from './editor';
|
||||||
import { noop, Point } from '@toeverything/utils';
|
|
||||||
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
||||||
import { RootContext } from './contexts';
|
|
||||||
|
|
||||||
function useRequestReRender() {
|
function useRequestReRender() {
|
||||||
const [, setUpdateCounter] = useState(0);
|
const [, setUpdateCounter] = useState(0);
|
||||||
@@ -56,7 +56,7 @@ function useRequestReRender() {
|
|||||||
export const useBlock = (blockId: string) => {
|
export const useBlock = (blockId: string) => {
|
||||||
const [block, setBlock] = useState<AsyncBlock>();
|
const [block, setBlock] = useState<AsyncBlock>();
|
||||||
const requestReRender = useRequestReRender();
|
const requestReRender = useRequestReRender();
|
||||||
const { editor } = useContext(RootContext);
|
const { editor } = useEditor();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!blockId) {
|
if (!blockId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -95,7 +95,7 @@ export const useOnSelect = (
|
|||||||
blockId: string,
|
blockId: string,
|
||||||
cb: (isSelect: boolean) => void
|
cb: (isSelect: boolean) => void
|
||||||
) => {
|
) => {
|
||||||
const { editor } = useContext(RootContext);
|
const { editor } = useEditor();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editor.selectionManager.observe(blockId, SelectEventTypes.onSelect, cb);
|
editor.selectionManager.observe(blockId, SelectEventTypes.onSelect, cb);
|
||||||
return () => {
|
return () => {
|
||||||
@@ -117,7 +117,7 @@ export const useOnSelectActive = (
|
|||||||
blockId: string,
|
blockId: string,
|
||||||
cb: (position: Point | undefined) => void
|
cb: (position: Point | undefined) => void
|
||||||
) => {
|
) => {
|
||||||
const { editor } = useContext(RootContext);
|
const { editor } = useEditor();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editor.selectionManager.observe(blockId, SelectEventTypes.active, cb);
|
editor.selectionManager.observe(blockId, SelectEventTypes.active, cb);
|
||||||
return () => {
|
return () => {
|
||||||
@@ -139,7 +139,7 @@ export const useOnSelectSetSelection = <T extends keyof SelectionSettingsMap>(
|
|||||||
blockId: string,
|
blockId: string,
|
||||||
cb: (args: SelectionSettingsMap[T]) => void
|
cb: (args: SelectionSettingsMap[T]) => void
|
||||||
) => {
|
) => {
|
||||||
const { editor } = useContext(RootContext);
|
const { editor } = useEditor();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editor.selectionManager.observe(
|
editor.selectionManager.observe(
|
||||||
blockId,
|
blockId,
|
||||||
@@ -162,7 +162,7 @@ export const useOnSelectSetSelection = <T extends keyof SelectionSettingsMap>(
|
|||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const useOnSelectChange = (cb: (info: SelectionInfo) => void) => {
|
export const useOnSelectChange = (cb: (info: SelectionInfo) => void) => {
|
||||||
const { editor } = useContext(RootContext);
|
const { editor } = useEditor();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editor.selectionManager.onSelectionChange(cb);
|
editor.selectionManager.onSelectionChange(cb);
|
||||||
return () => {
|
return () => {
|
||||||
@@ -177,7 +177,7 @@ export const useOnSelectChange = (cb: (info: SelectionInfo) => void) => {
|
|||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const useOnSelectEnd = (cb: (info: SelectionInfo) => void) => {
|
export const useOnSelectEnd = (cb: (info: SelectionInfo) => void) => {
|
||||||
const { editor } = useContext(RootContext);
|
const { editor } = useEditor();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editor.selectionManager.onSelectEnd(cb);
|
editor.selectionManager.onSelectEnd(cb);
|
||||||
return () => {
|
return () => {
|
||||||
@@ -195,7 +195,7 @@ export const useOnSelectStartWith = (
|
|||||||
blockId: string,
|
blockId: string,
|
||||||
cb: (args: MouseEvent) => void
|
cb: (args: MouseEvent) => void
|
||||||
) => {
|
) => {
|
||||||
const { editor } = useContext(RootContext);
|
const { editor } = useEditor();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editor.mouseManager.onSelectStartWith(blockId, cb);
|
editor.mouseManager.onSelectStartWith(blockId, cb);
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export { ColumnsContext, RootContext } from './contexts';
|
|
||||||
export { RenderRoot, MIN_PAGE_WIDTH } from './RenderRoot';
|
export { RenderRoot, MIN_PAGE_WIDTH } from './RenderRoot';
|
||||||
export * from './render-block';
|
export * from './render-block';
|
||||||
export * from './hooks';
|
export * from './hooks';
|
||||||
@@ -15,7 +14,6 @@ export * from './kanban/types';
|
|||||||
|
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
|
||||||
export * from './drag-drop-wrapper';
|
|
||||||
export * from './block-content-wrapper';
|
|
||||||
|
|
||||||
export * from './editor';
|
export * from './editor';
|
||||||
|
|
||||||
|
export { RefPageProvider, useRefPage } from './ref-page';
|
||||||
|
|||||||
@@ -6,10 +6,15 @@ import {
|
|||||||
PropertyType,
|
PropertyType,
|
||||||
RecastBlockValue,
|
RecastBlockValue,
|
||||||
RecastMetaProperty,
|
RecastMetaProperty,
|
||||||
RecastPropertyId,
|
|
||||||
} from '../recast-block/types';
|
} from '../recast-block/types';
|
||||||
import type { DefaultGroup, KanbanGroup } from './types';
|
import type { DefaultGroup, KanbanGroup } from './types';
|
||||||
import { DEFAULT_GROUP_ID } from './types';
|
import { DEFAULT_GROUP_ID } from './types';
|
||||||
|
import {
|
||||||
|
generateInitialOptions,
|
||||||
|
generateRandomFieldName,
|
||||||
|
getPendantIconsConfigByName,
|
||||||
|
} from '../block-pendant/utils';
|
||||||
|
import { SelectOption } from '../recast-block';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - If the `groupBy` is `SelectProperty` or `MultiSelectProperty`, return `(Multi)SelectProperty.options`.
|
* - If the `groupBy` is `SelectProperty` or `MultiSelectProperty`, return `(Multi)SelectProperty.options`.
|
||||||
@@ -23,6 +28,7 @@ export const getGroupOptions = async (
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
switch (groupBy.type) {
|
switch (groupBy.type) {
|
||||||
|
case PropertyType.Status:
|
||||||
case PropertyType.Select:
|
case PropertyType.Select:
|
||||||
case PropertyType.MultiSelect: {
|
case PropertyType.MultiSelect: {
|
||||||
return groupBy.options.map(option => ({
|
return groupBy.options.map(option => ({
|
||||||
@@ -51,15 +57,13 @@ const isValueBelongOption = (
|
|||||||
option: KanbanGroup
|
option: KanbanGroup
|
||||||
) => {
|
) => {
|
||||||
switch (propertyValue.type) {
|
switch (propertyValue.type) {
|
||||||
case PropertyType.Select: {
|
case PropertyType.Select:
|
||||||
|
case PropertyType.Status: {
|
||||||
return propertyValue.value === option.id;
|
return propertyValue.value === option.id;
|
||||||
}
|
}
|
||||||
case PropertyType.MultiSelect: {
|
case PropertyType.MultiSelect: {
|
||||||
return propertyValue.value.some(i => i === option.id);
|
return propertyValue.value.some(i => i === option.id);
|
||||||
}
|
}
|
||||||
// case PropertyType.Text: {
|
|
||||||
// TOTODO:DO support this type
|
|
||||||
// }
|
|
||||||
default: {
|
default: {
|
||||||
console.error(propertyValue, option);
|
console.error(propertyValue, option);
|
||||||
throw new Error('Not support group by type');
|
throw new Error('Not support group by type');
|
||||||
@@ -96,40 +100,67 @@ export const calcCardGroup = (
|
|||||||
/**
|
/**
|
||||||
* Set group value for the card block
|
* Set group value for the card block
|
||||||
*/
|
*/
|
||||||
export const moveCardToGroup = async (
|
export const moveCardToGroup = async ({
|
||||||
groupById: RecastPropertyId,
|
groupBy,
|
||||||
cardBlock: RecastItem,
|
cardBlock,
|
||||||
group: KanbanGroup
|
group,
|
||||||
) => {
|
recastBlock,
|
||||||
|
}: {
|
||||||
|
groupBy: RecastMetaProperty;
|
||||||
|
cardBlock: RecastItem;
|
||||||
|
group: KanbanGroup;
|
||||||
|
recastBlock: RecastBlock;
|
||||||
|
}) => {
|
||||||
const { setValue, removeValue } = getRecastItemValue(cardBlock);
|
const { setValue, removeValue } = getRecastItemValue(cardBlock);
|
||||||
let success = false;
|
let success = false;
|
||||||
if (group.id === DEFAULT_GROUP_ID) {
|
if (group.id === DEFAULT_GROUP_ID) {
|
||||||
success = await removeValue(groupById);
|
success = await removeValue(groupBy.id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (group.type) {
|
switch (group.type) {
|
||||||
case PropertyType.Select: {
|
case PropertyType.Select: {
|
||||||
success = await setValue({
|
success = await setValue(
|
||||||
id: groupById,
|
{
|
||||||
type: group.type,
|
id: groupBy.id,
|
||||||
value: group.id,
|
type: group.type,
|
||||||
});
|
value: group.id,
|
||||||
|
},
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PropertyType.Status: {
|
||||||
|
success = await setValue(
|
||||||
|
{
|
||||||
|
id: groupBy.id,
|
||||||
|
type: group.type,
|
||||||
|
value: group.id,
|
||||||
|
},
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PropertyType.MultiSelect: {
|
case PropertyType.MultiSelect: {
|
||||||
success = await setValue({
|
success = await setValue(
|
||||||
id: groupById,
|
{
|
||||||
type: group.type,
|
id: groupBy.id,
|
||||||
value: [group.id],
|
type: group.type,
|
||||||
});
|
value: [group.id],
|
||||||
|
},
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PropertyType.Text: {
|
case PropertyType.Text: {
|
||||||
success = await setValue({
|
success = await setValue(
|
||||||
id: groupById,
|
{
|
||||||
type: group.type,
|
id: groupBy.id,
|
||||||
value: group.id,
|
type: group.type,
|
||||||
});
|
value: group.id,
|
||||||
|
},
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -194,14 +225,18 @@ export const genDefaultGroup = (groupBy: RecastMetaProperty): DefaultGroup => ({
|
|||||||
items: [],
|
items: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DEFAULT_GROUP_BY_PROPERTY = {
|
export const generateDefaultGroupByProperty = (): {
|
||||||
name: 'Status',
|
name: string;
|
||||||
options: [
|
options: Omit<SelectOption, 'id'>[];
|
||||||
{ name: 'No Started', color: '#E53535', background: '#FFCECE' },
|
type: PropertyType.Status;
|
||||||
{ name: 'In Progress', color: '#A77F1A', background: '#FFF5AB' },
|
} => ({
|
||||||
{ name: 'Complete', color: '#3C8867', background: '#C5FBE0' },
|
name: generateRandomFieldName(PropertyType.Status),
|
||||||
],
|
type: PropertyType.Status,
|
||||||
};
|
options: generateInitialOptions(
|
||||||
|
PropertyType.Status,
|
||||||
|
getPendantIconsConfigByName(PropertyType.Status)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unwrap blocks from the grid recursively.
|
* Unwrap blocks from the grid recursively.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const useKanbanGroup = (groupBy: RecastMetaProperty) => {
|
|||||||
const { updateSelect } = useSelectProperty();
|
const { updateSelect } = useSelectProperty();
|
||||||
|
|
||||||
switch (groupBy.type) {
|
switch (groupBy.type) {
|
||||||
|
case PropertyType.Status:
|
||||||
case PropertyType.MultiSelect:
|
case PropertyType.MultiSelect:
|
||||||
case PropertyType.Select: {
|
case PropertyType.Select: {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { useEditor } from '../contexts';
|
import { useEditor } from '../Contexts';
|
||||||
import { AsyncBlock } from '../editor';
|
import { AsyncBlock } from '../editor';
|
||||||
import { useRecastView } from '../recast-block';
|
import { useRecastView } from '../recast-block';
|
||||||
import { useRecastBlock } from '../recast-block/Context';
|
import { useRecastBlock } from '../recast-block/Context';
|
||||||
@@ -18,8 +18,8 @@ import {
|
|||||||
import { supportChildren } from '../utils';
|
import { supportChildren } from '../utils';
|
||||||
import {
|
import {
|
||||||
calcCardGroup,
|
calcCardGroup,
|
||||||
DEFAULT_GROUP_BY_PROPERTY,
|
|
||||||
genDefaultGroup,
|
genDefaultGroup,
|
||||||
|
generateDefaultGroupByProperty,
|
||||||
getCardGroup,
|
getCardGroup,
|
||||||
getGroupOptions,
|
getGroupOptions,
|
||||||
moveCardToAfter,
|
moveCardToAfter,
|
||||||
@@ -48,6 +48,7 @@ export const useRecastKanbanGroupBy = () => {
|
|||||||
// Add other type groupBy support
|
// Add other type groupBy support
|
||||||
const supportedGroupBy = getProperties().filter(
|
const supportedGroupBy = getProperties().filter(
|
||||||
prop =>
|
prop =>
|
||||||
|
prop.type === PropertyType.Status ||
|
||||||
prop.type === PropertyType.Select ||
|
prop.type === PropertyType.Select ||
|
||||||
prop.type === PropertyType.MultiSelect
|
prop.type === PropertyType.MultiSelect
|
||||||
);
|
);
|
||||||
@@ -88,7 +89,8 @@ export const useRecastKanbanGroupBy = () => {
|
|||||||
// TODO: support other property type
|
// TODO: support other property type
|
||||||
if (
|
if (
|
||||||
groupByProperty.type !== PropertyType.Select &&
|
groupByProperty.type !== PropertyType.Select &&
|
||||||
groupByProperty.type !== PropertyType.MultiSelect
|
groupByProperty.type !== PropertyType.MultiSelect &&
|
||||||
|
groupByProperty.type !== PropertyType.Status
|
||||||
) {
|
) {
|
||||||
console.warn('Not support groupBy type', groupByProperty);
|
console.warn('Not support groupBy type', groupByProperty);
|
||||||
|
|
||||||
@@ -134,7 +136,7 @@ export const useInitKanbanEffect = ():
|
|||||||
}
|
}
|
||||||
// 3. no group by, no properties
|
// 3. no group by, no properties
|
||||||
// create a new property and set it as group by
|
// create a new property and set it as group by
|
||||||
const prop = await createSelect(DEFAULT_GROUP_BY_PROPERTY);
|
const prop = await createSelect(generateDefaultGroupByProperty());
|
||||||
await setGroupBy(prop.id);
|
await setGroupBy(prop.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -197,7 +199,12 @@ export const useRecastKanban = () => {
|
|||||||
beforeBlock: string | null,
|
beforeBlock: string | null,
|
||||||
afterBlock: string | null
|
afterBlock: string | null
|
||||||
) => {
|
) => {
|
||||||
await moveCardToGroup(groupBy.id, child, kanbanMap[id]);
|
await moveCardToGroup({
|
||||||
|
groupBy,
|
||||||
|
cardBlock: child,
|
||||||
|
group: kanbanMap[id],
|
||||||
|
recastBlock,
|
||||||
|
});
|
||||||
if (beforeBlock) {
|
if (beforeBlock) {
|
||||||
const block = await editor.getBlockById(
|
const block = await editor.getBlockById(
|
||||||
beforeBlock
|
beforeBlock
|
||||||
@@ -286,7 +293,12 @@ export const useKanban = () => {
|
|||||||
);
|
);
|
||||||
if (isChangedGroup) {
|
if (isChangedGroup) {
|
||||||
// 1.2 Move to the target group
|
// 1.2 Move to the target group
|
||||||
await moveCardToGroup(groupBy.id, targetCard, targetGroup);
|
await moveCardToGroup({
|
||||||
|
groupBy,
|
||||||
|
cardBlock: targetCard,
|
||||||
|
group: targetGroup,
|
||||||
|
recastBlock,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Reorder the card
|
// 2. Reorder the card
|
||||||
@@ -324,7 +336,12 @@ export const useKanban = () => {
|
|||||||
}
|
}
|
||||||
recastBlock.append(newBlock);
|
recastBlock.append(newBlock);
|
||||||
const newCard = newBlock as unknown as RecastItem;
|
const newCard = newBlock as unknown as RecastItem;
|
||||||
await moveCardToGroup(groupBy.id, newCard, group);
|
await moveCardToGroup({
|
||||||
|
groupBy,
|
||||||
|
cardBlock: newCard,
|
||||||
|
group,
|
||||||
|
recastBlock,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[editor, groupBy.id, recastBlock]
|
[editor, groupBy.id, recastBlock]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ export type DefaultGroup = KanbanGroupBase & {
|
|||||||
|
|
||||||
type SelectGroup = KanbanGroupBase &
|
type SelectGroup = KanbanGroupBase &
|
||||||
SelectOption & {
|
SelectOption & {
|
||||||
type: PropertyType.Select | PropertyType.MultiSelect;
|
type:
|
||||||
|
| PropertyType.Select
|
||||||
|
| PropertyType.MultiSelect
|
||||||
|
| PropertyType.Status;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TextGroup = KanbanGroupBase & {
|
type TextGroup = KanbanGroupBase & {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Protocol } from '@toeverything/datasource/db-service';
|
|||||||
import { AsyncBlock } from '../editor';
|
import { AsyncBlock } from '../editor';
|
||||||
import { ComponentType, createContext, ReactNode, useContext } from 'react';
|
import { ComponentType, createContext, ReactNode, useContext } from 'react';
|
||||||
import { RecastBlock } from './types';
|
import { RecastBlock } from './types';
|
||||||
|
import { RefPageProvider } from '../ref-page';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the block supports RecastBlock
|
* Determine whether the block supports RecastBlock
|
||||||
@@ -47,7 +48,7 @@ export const RecastBlockProvider = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RecastBlockContext.Provider value={block}>
|
<RecastBlockContext.Provider value={block}>
|
||||||
{children}
|
<RefPageProvider>{children}</RefPageProvider>
|
||||||
</RecastBlockContext.Provider>
|
</RecastBlockContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -60,7 +61,7 @@ export const useRecastBlock = () => {
|
|||||||
const recastBlock = useContext(RecastBlockContext);
|
const recastBlock = useContext(RecastBlockContext);
|
||||||
if (!recastBlock) {
|
if (!recastBlock) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Failed to find recastBlock! Please use the hook under `RecastTableProvider`.'
|
'Failed to find recastBlock! Please use the hook under `RecastBlockProvider`.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return recastBlock;
|
return recastBlock;
|
||||||
|
|||||||
@@ -49,22 +49,3 @@ const SomeBlock = () => {
|
|||||||
return <div>...</div>;
|
return <div>...</div>;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Scene
|
|
||||||
|
|
||||||
**Notice: The scene API will refactor at next version.**
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const SomeBlock = () => {
|
|
||||||
const { scene, setScene, setPage, setTable, setKanban } =
|
|
||||||
useRecastBlockScene();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div>Scene: {scene}</div>
|
|
||||||
<button onClick={setPage}>list</button>
|
|
||||||
<button onClick={setKanban}>kanban</button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const mergeGroup = async (...groups: AsyncBlock[]) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await mergeGroupProperties(...(groups as RecastBlock[]));
|
await mergeGroupProperties(...(groups as unknown as RecastBlock[]));
|
||||||
|
|
||||||
const [headGroup, ...restGroups] = groups;
|
const [headGroup, ...restGroups] = groups;
|
||||||
// Add all children to the head group
|
// Add all children to the head group
|
||||||
@@ -174,7 +174,7 @@ export const splitGroup = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
splitGroupProperties(
|
splitGroupProperties(
|
||||||
group as RecastBlock,
|
group as unknown as RecastBlock,
|
||||||
newGroupBlock as unknown as RecastBlock
|
newGroupBlock as unknown as RecastBlock
|
||||||
);
|
);
|
||||||
await group.after(newGroupBlock);
|
await group.after(newGroupBlock);
|
||||||
@@ -185,6 +185,22 @@ export const splitGroup = async (
|
|||||||
return newGroupBlock;
|
return newGroupBlock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const appendNewGroup = async (
|
||||||
|
editor: BlockEditor,
|
||||||
|
parentBlock: AsyncBlock,
|
||||||
|
active = false
|
||||||
|
) => {
|
||||||
|
const newGroupBlock = await createGroupWithEmptyText(editor);
|
||||||
|
await parentBlock.append(newGroupBlock);
|
||||||
|
if (active) {
|
||||||
|
// Active text block
|
||||||
|
await editor.selectionManager.activeNodeByNodeId(
|
||||||
|
newGroupBlock.childrenIds[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return newGroupBlock;
|
||||||
|
};
|
||||||
|
|
||||||
export const addNewGroup = async (
|
export const addNewGroup = async (
|
||||||
editor: BlockEditor,
|
editor: BlockEditor,
|
||||||
previousBlock: AsyncBlock,
|
previousBlock: AsyncBlock,
|
||||||
|
|||||||
84
libs/components/editor-core/src/recast-block/history.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { RecastPropertyId } from './types';
|
||||||
|
|
||||||
|
// TODO: The logic for keeping history should be supported by the network layer
|
||||||
|
type Props = {
|
||||||
|
recastBlockId: string;
|
||||||
|
blockId: string;
|
||||||
|
propertyId: RecastPropertyId;
|
||||||
|
};
|
||||||
|
|
||||||
|
type HistoryStorageMap = {
|
||||||
|
[recastBlockId: string]: {
|
||||||
|
[propertyId: RecastPropertyId]: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const LOCAL_STORAGE_NAME = 'TEMPORARY_HISTORY_DATA';
|
||||||
|
|
||||||
|
const ensureLocalStorage = () => {
|
||||||
|
const data = localStorage.getItem(LOCAL_STORAGE_NAME);
|
||||||
|
if (!data) {
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify({}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const ensureHistoryAtom = (
|
||||||
|
data: HistoryStorageMap,
|
||||||
|
recastBlockId: string,
|
||||||
|
propertyId: RecastPropertyId
|
||||||
|
): HistoryStorageMap => {
|
||||||
|
if (!data[recastBlockId]) {
|
||||||
|
data[recastBlockId] = {};
|
||||||
|
}
|
||||||
|
if (!data[recastBlockId][propertyId]) {
|
||||||
|
data[recastBlockId][propertyId] = [];
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setHistory = ({ recastBlockId, blockId, propertyId }: Props) => {
|
||||||
|
ensureLocalStorage();
|
||||||
|
const data: HistoryStorageMap = JSON.parse(
|
||||||
|
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
||||||
|
);
|
||||||
|
ensureHistoryAtom(data, recastBlockId, propertyId);
|
||||||
|
const propertyHistory = data[recastBlockId][propertyId];
|
||||||
|
|
||||||
|
if (propertyHistory.includes(blockId)) {
|
||||||
|
const idIndex = propertyHistory.findIndex(id => id === blockId);
|
||||||
|
propertyHistory.splice(idIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyHistory.push(blockId);
|
||||||
|
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getHistory = ({ recastBlockId }: { recastBlockId: string }) => {
|
||||||
|
ensureLocalStorage();
|
||||||
|
const data: HistoryStorageMap = JSON.parse(
|
||||||
|
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
||||||
|
);
|
||||||
|
|
||||||
|
return data[recastBlockId] ?? {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeHistory = ({
|
||||||
|
recastBlockId,
|
||||||
|
blockId,
|
||||||
|
propertyId,
|
||||||
|
}: Props) => {
|
||||||
|
ensureLocalStorage();
|
||||||
|
const data: HistoryStorageMap = JSON.parse(
|
||||||
|
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
||||||
|
);
|
||||||
|
ensureHistoryAtom(data, recastBlockId, propertyId);
|
||||||
|
|
||||||
|
const propertyHistory = data[recastBlockId][propertyId];
|
||||||
|
|
||||||
|
if (propertyHistory.includes(blockId)) {
|
||||||
|
const idIndex = propertyHistory.findIndex(id => id === blockId);
|
||||||
|
propertyHistory.splice(idIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
SelectProperty,
|
SelectProperty,
|
||||||
TABLE_VALUES_KEY,
|
TABLE_VALUES_KEY,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import { getHistory, removeHistory, setHistory } from './history';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a unique id for a property
|
* Generate a unique id for a property
|
||||||
@@ -240,7 +241,13 @@ export const getRecastItemValue = (block: RecastItem | AsyncBlock) => {
|
|||||||
return props[id];
|
return props[id];
|
||||||
};
|
};
|
||||||
|
|
||||||
const setValue = (newValue: RecastBlockValue) => {
|
const setValue = (newValue: RecastBlockValue, recastBlockId: string) => {
|
||||||
|
setHistory({
|
||||||
|
recastBlockId: recastBlockId,
|
||||||
|
blockId: block.id,
|
||||||
|
propertyId: newValue.id,
|
||||||
|
});
|
||||||
|
|
||||||
return recastItem.setProperty(TABLE_VALUES_KEY, {
|
return recastItem.setProperty(TABLE_VALUES_KEY, {
|
||||||
...props,
|
...props,
|
||||||
[newValue.id]: newValue,
|
[newValue.id]: newValue,
|
||||||
@@ -249,22 +256,30 @@ export const getRecastItemValue = (block: RecastItem | AsyncBlock) => {
|
|||||||
|
|
||||||
const removeValue = (propertyId: RecastPropertyId) => {
|
const removeValue = (propertyId: RecastPropertyId) => {
|
||||||
const { [propertyId]: omitted, ...restProps } = props;
|
const { [propertyId]: omitted, ...restProps } = props;
|
||||||
|
|
||||||
|
removeHistory({
|
||||||
|
recastBlockId: block.id,
|
||||||
|
propertyId: propertyId,
|
||||||
|
blockId: block.id,
|
||||||
|
});
|
||||||
|
|
||||||
return recastItem.setProperty(TABLE_VALUES_KEY, restProps);
|
return recastItem.setProperty(TABLE_VALUES_KEY, restProps);
|
||||||
};
|
};
|
||||||
return { getAllValue, getValue, setValue, removeValue };
|
|
||||||
|
const getValueHistory = getHistory;
|
||||||
|
|
||||||
|
return { getAllValue, getValue, setValue, removeValue, getValueHistory };
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSelectLikeProperty = (
|
const isSelectLikeProperty = (
|
||||||
metaProperty?: RecastMetaProperty
|
metaProperty?: RecastMetaProperty
|
||||||
): metaProperty is SelectProperty | MultiSelectProperty => {
|
): metaProperty is SelectProperty | MultiSelectProperty | StatusProperty => {
|
||||||
if (
|
return (
|
||||||
!metaProperty ||
|
metaProperty &&
|
||||||
(metaProperty.type !== PropertyType.Select &&
|
(metaProperty.type === PropertyType.Status ||
|
||||||
metaProperty.type !== PropertyType.MultiSelect)
|
metaProperty.type === PropertyType.Select ||
|
||||||
) {
|
metaProperty.type === PropertyType.MultiSelect)
|
||||||
return false;
|
);
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -312,7 +327,7 @@ export const useSelectProperty = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateSelect = (
|
const updateSelect = (
|
||||||
selectProperty: SelectProperty | MultiSelectProperty
|
selectProperty: StatusProperty | SelectProperty | MultiSelectProperty
|
||||||
) => {
|
) => {
|
||||||
// if (typeof selectProperty === 'string') {
|
// if (typeof selectProperty === 'string') {
|
||||||
// const maybeSelectProperty = getProperty(selectProperty);
|
// const maybeSelectProperty = getProperty(selectProperty);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { useCallback } from 'react';
|
import { MutableRefObject, useCallback, useEffect, useState } from 'react';
|
||||||
import { useRecastBlock } from './Context';
|
import { useRecastBlock } from './Context';
|
||||||
import {
|
import {
|
||||||
KanbanView,
|
KanbanView,
|
||||||
@@ -50,7 +50,33 @@ export const useCurrentView = () => {
|
|||||||
);
|
);
|
||||||
return [currentView, setCurrentView] as const;
|
return [currentView, setCurrentView] as const;
|
||||||
};
|
};
|
||||||
|
export const useLazyIframe = (
|
||||||
|
link: string,
|
||||||
|
timers: number,
|
||||||
|
container: MutableRefObject<HTMLElement>
|
||||||
|
) => {
|
||||||
|
const [iframeShow, setIframeShow] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.src = link;
|
||||||
|
iframe.onload = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Prevent iframe from scrolling parent container
|
||||||
|
// TODO W3C https://github.com/w3c/csswg-drafts/issues/7134
|
||||||
|
// https://forum.figma.com/t/prevent-figmas-embed-code-from-automatically-scrolling-to-it-on-page-load/26029/6
|
||||||
|
setIframeShow(true);
|
||||||
|
}, timers);
|
||||||
|
};
|
||||||
|
if (container?.current) {
|
||||||
|
container.current.appendChild(iframe);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
iframe.remove();
|
||||||
|
};
|
||||||
|
}, [link, container]);
|
||||||
|
|
||||||
|
return iframeShow;
|
||||||
|
};
|
||||||
export const useRecastView = () => {
|
export const useRecastView = () => {
|
||||||
const recastBlock = useRecastBlock();
|
const recastBlock = useRecastBlock();
|
||||||
const recastViews =
|
const recastViews =
|
||||||
|
|||||||