Browse Source

feat: open page actions

Peng Xiao 3 years ago
parent
commit
6ba0d5aa56

+ 21 - 11
src/main/frontend/extensions/tldraw.cljs

@@ -4,6 +4,7 @@
             [frontend.components.page :as page]
             [frontend.db.model :as model]
             [frontend.handler.editor :as editor-handler]
+            [frontend.handler.route :as route-handler]
             [frontend.handler.search :as search]
             [frontend.handler.whiteboard :as whiteboard-handler]
             [frontend.rum :as r]
@@ -54,6 +55,24 @@
          (when-let [[asset-file-name _ full-file-path] (and (seq res) (first res))]
            (editor-handler/resolve-relative-path (or full-file-path asset-file-name)))))))
 
+(def tldraw-renderers {:Page page-cp
+                       :Block block-cp
+                       :Breadcrumb breadcrumb
+                       :PageNameLink page-name-link})
+
+(def tldraw-handlers {:search search-handler
+                      :queryBlockByUUID #(clj->js (model/query-block-by-uuid (parse-uuid %)))
+                      :isWhiteboardPage model/whiteboard-page?
+                      :saveAsset save-asset-handler
+                      :makeAssetUrl editor-handler/make-asset-url
+                      :addNewBlock (fn [content]
+                                     (str (whiteboard-handler/add-new-block! name content)))
+                      :sidebarAddBlock (fn [uuid type]
+                                         (state/sidebar-add-block! (state/get-current-repo)
+                                                                   (:db/id (model/get-page uuid))
+                                                                   (keyword type)))
+                      :redirectToPage route-handler/redirect-to-page!})
+
 (rum/defc tldraw-app
   [name block-id]
   (let [data (whiteboard-handler/page-name->tldr! name block-id)
@@ -77,17 +96,8 @@
         ;; wheel -> overscroll may cause browser navigation
         :on-wheel util/stop-propagation}
 
-       (tldraw {:renderers {:Page page-cp
-                            :Block block-cp
-                            :Breadcrumb breadcrumb
-                            :PageNameLink page-name-link}
-                :handlers (clj->js {:search search-handler
-                                    :queryBlockByUUID #(clj->js (model/query-block-by-uuid (parse-uuid %)))
-                                    :isWhiteboardPage model/whiteboard-page?
-                                    :saveAsset save-asset-handler
-                                    :makeAssetUrl editor-handler/make-asset-url
-                                    :addNewBlock (fn [content]
-                                                   (str (whiteboard-handler/add-new-block! name content)))})
+       (tldraw {:renderers tldraw-renderers
+                :handlers tldraw-handlers
                 :onMount (fn [app] (set-tln ^js app))
                 :onPersist (fn [app]
                              (let [document (gobj/get app "serialized")]

+ 2 - 1
tldraw/apps/tldraw-logseq/package.json

@@ -33,7 +33,8 @@
     "@radix-ui/react-dropdown-menu": "^1.0.0",
     "@radix-ui/react-select": "^1.0.0",
     "@radix-ui/react-switch": "^1.0.0",
-    "@radix-ui/react-toggle-group": "^1.0.0"
+    "@radix-ui/react-toggle-group": "^1.0.0",
+    "@radix-ui/react-separator": "^1.0.0"
   },
   "peerDependencies": {
     "react": "^16.8.0 || ^17.0.0 || ^18.0.0",

+ 10 - 3
tldraw/apps/tldraw-logseq/src/components/ContextBar/ContextBar.tsx

@@ -5,9 +5,11 @@ import {
   useApp,
 } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'
+import * as Separator from '@radix-ui/react-separator'
+
 import * as React from 'react'
 import type { Shape } from '~lib/shapes'
-import { getContextBarActionsForTypes } from './contextBarActionFactory'
+import { getContextBarActionsForTypes as getContextBarActionsForShapes } from './contextBarActionFactory'
 
 const _ContextBar: TLContextBarComponent<Shape> = ({ shapes, offsets, hidden }) => {
   const app = useApp()
@@ -33,7 +35,7 @@ const _ContextBar: TLContextBarComponent<Shape> = ({ shapes, offsets, hidden })
 
   if (!app) return null
 
-  const Actions = getContextBarActionsForTypes(shapes.map(s => s.props.type))
+  const Actions = getContextBarActionsForShapes(shapes)
 
   return (
     <HTMLContainer centered>
@@ -44,7 +46,12 @@ const _ContextBar: TLContextBarComponent<Shape> = ({ shapes, offsets, hidden })
           style={{ pointerEvents: hidden ? 'none' : 'all' }}
         >
           {Actions.map((Action, idx) => (
-            <Action key={idx} />
+            <React.Fragment key={idx}>
+              <Action />
+              {idx < Actions.length - 1 && (
+                <Separator.Root className="tl-contextbar-separator" orientation="vertical" />
+              )}
+            </React.Fragment>
           ))}
         </div>
       )}

+ 59 - 16
tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx

@@ -1,22 +1,26 @@
 import { isNonNullable } from '@tldraw/core'
 import { useApp } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'
+import React from 'react'
+import { TablerIcon } from '~components/icons'
 import { SelectInput, SelectOption } from '~components/inputs/SelectInput'
 import { ToggleGroupInput, ToggleGroupInputOption } from '~components/inputs/ToggleGroupInput'
 import { LogseqPortalShape, Shape } from '~lib'
+import { LogseqContext } from '~lib/logseq-context'
 
 export const contextBarActionTypes = [
+  // Order matters
   'NoFill',
-  'LogseqPortalViewMode',
-  'ScaleLevel',
   'ColorAccent',
   'StrokeColor',
   'NoStroke',
+  'ScaleLevel',
+  'LogseqPortalViewMode',
   'OpenPage',
-  'OpenInRightSidebar',
 ] as const
 
 type ContextBarActionType = typeof contextBarActionTypes[number]
+const singleShapeActions: ContextBarActionType[] = ['OpenPage']
 
 const contextBarActionMapping = new Map<ContextBarActionType, React.FC>()
 
@@ -98,30 +102,69 @@ const ScaleLevelAction = observer(() => {
   )
 })
 
+const OpenPageAction = observer(() => {
+  const { handlers } = React.useContext(LogseqContext)
+  const app = useApp<Shape>()
+  const shapes = app.selectedShapesArray.filter(
+    s => s.props.type === LogseqPortalShape.defaultProps.type
+  ) as LogseqPortalShape[]
+  const shape = shapes[0]
+  const { pageId, blockType } = shape.props
+
+  return (
+    <span className="flex gap-1">
+      <button
+        className="tl-contextbar-button"
+        type="button"
+        onClick={() => handlers?.sidebarAddBlock(pageId, blockType === 'B' ? 'block' : 'page')}
+      >
+        <TablerIcon name="layout-sidebar-right" />
+      </button>
+      <button
+        className="tl-contextbar-button"
+        type="button"
+        onClick={() => handlers?.redirectToPage(pageId)}
+      >
+        <TablerIcon name="external-link" />
+      </button>
+    </span>
+  )
+})
+
 contextBarActionMapping.set('LogseqPortalViewMode', LogseqPortalViewModeAction)
 contextBarActionMapping.set('ScaleLevel', ScaleLevelAction)
+contextBarActionMapping.set('OpenPage', OpenPageAction)
 
 type ShapeType = Shape['props']['type']
 
 const shapeMapping: Partial<Record<ShapeType, ContextBarActionType[]>> = {
-  'logseq-portal': ['LogseqPortalViewMode', 'ScaleLevel'],
+  'logseq-portal': ['LogseqPortalViewMode', 'ScaleLevel', 'OpenPage'],
 }
 
-export const getContextBarActionsForType = (type: ShapeType) => {
-  return (shapeMapping[type] ?? [])
-    .map(actionType => contextBarActionMapping.get(actionType))
-    .filter(isNonNullable)
+const getContextBarActionTypes = (type: ShapeType) => {
+  return (shapeMapping[type] ?? []).filter(isNonNullable)
 }
 
-export const getContextBarActionsForTypes = (types: ShapeType[]) => {
-  const actions = new Set(types.length > 0 ? getContextBarActionsForType(types[0]) : [])
-  for (let i = 1; i < types.length && actions.size > 0; i++) {
-    const actionsForType = getContextBarActionsForType(types[i])
-    actions.forEach(action => {
-      if (!actionsForType.includes(action)) {
-        actions.delete(action)
+export const getContextBarActionsForTypes = (shapes: Shape[]) => {
+  const types = shapes.map(s => s.props.type)
+  const actionTypes = new Set(shapes.length > 0 ? getContextBarActionTypes(types[0]) : [])
+  for (let i = 1; i < types.length && actionTypes.size > 0; i++) {
+    const otherActionTypes = getContextBarActionTypes(types[i])
+    actionTypes.forEach(action => {
+      if (!otherActionTypes.includes(action)) {
+        actionTypes.delete(action)
       }
     })
   }
-  return Array.from(actions)
+  if (shapes.length > 1) {
+    singleShapeActions.forEach(action => {
+      if (actionTypes.has(action)) {
+        actionTypes.delete(action)
+      }
+    })
+  }
+
+  return Array.from(actionTypes)
+    .sort((a, b) => contextBarActionTypes.indexOf(a) - contextBarActionTypes.indexOf(b))
+    .map(action => contextBarActionMapping.get(action)!)
 }

+ 20 - 22
tldraw/apps/tldraw-logseq/src/components/inputs/ToggleGroupInput.tsx

@@ -12,28 +12,26 @@ interface SelectInputProps extends React.HTMLAttributes<HTMLElement> {
   onValueChange: (value: string) => void
 }
 
-export function ToggleGroupInput({ options, value, onValueChange, ...rest }: SelectInputProps) {
+export function ToggleGroupInput({ options, value, onValueChange }: SelectInputProps) {
   return (
-    <div {...rest}>
-      <ToggleGroup.Root
-        className="tl-toggle-group-input"
-        type="single"
-        value={value}
-        onValueChange={onValueChange}
-      >
-        {options.map(option => {
-          return (
-            <ToggleGroup.Item
-              className="tl-toggle-group-input-button"
-              key={option.value}
-              value={option.value}
-              disabled={option.value === value}
-            >
-              <TablerIcon name={option.icon} />
-            </ToggleGroup.Item>
-          )
-        })}
-      </ToggleGroup.Root>
-    </div>
+    <ToggleGroup.Root
+      className="tl-toggle-group-input"
+      type="single"
+      value={value}
+      onValueChange={onValueChange}
+    >
+      {options.map(option => {
+        return (
+          <ToggleGroup.Item
+            className="tl-toggle-group-input-button"
+            key={option.value}
+            value={option.value}
+            disabled={option.value === value}
+          >
+            <TablerIcon name={option.icon} />
+          </ToggleGroup.Item>
+        )
+      })}
+    </ToggleGroup.Root>
   )
 }

+ 2 - 0
tldraw/apps/tldraw-logseq/src/lib/logseq-context.ts

@@ -29,6 +29,8 @@ export interface LogseqContextValue {
     isWhiteboardPage: (pageName: string) => boolean
     saveAsset: (file: File) => Promise<string>
     makeAssetUrl: (relativeUrl: string) => string
+    sidebarAddBlock: (uuid: string, type: 'block' | 'page') => void
+    redirectToPage: (uuidOrPageName: string) => void
   }
 }
 

+ 21 - 2
tldraw/apps/tldraw-logseq/src/styles.css

@@ -249,7 +249,7 @@
 
 button.tl-select-input-trigger {
   @apply flex items-center py-1 px-3;
-  border: 1px solid var(--ls-secondary-border-color);
+  box-shadow: 0 0 0 1px var(--ls-secondary-border-color);
   background-color: var(--ls-quaternary-background-color);
   min-width: 160px;
   border-radius: 8px;
@@ -698,7 +698,7 @@ html[data-theme='dark'] {
 
 .tl-toggle-group-input {
   @apply rounded overflow-hidden;
-  border: 1px solid var(--ls-secondary-border-color);
+  box-shadow: 0 0 0 1px var(--ls-secondary-border-color);
 }
 
 .tl-toggle-group-input-button {
@@ -713,9 +713,28 @@ html[data-theme='dark'] {
     border-right: none;
   }
 
+  &:hover {
+    background-color: var(--ls-tertiary-background-color);
+  }
+
   &[data-state='on'] {
     background-color: var(--ls-tertiary-background-color);
     color: var(--ls-primary-text-color);
     opacity: 1;
   }
 }
+
+.tl-contextbar-button {
+  @apply rounded inline-flex items-center justify-center;
+  height: 32px;
+  width: 32px;
+
+  &:hover {
+    background-color: var(--ls-tertiary-background-color);
+  }
+}
+
+.tl-contextbar-separator {
+  background-color: var(--ls-border-color);
+  width: 1px;
+}

+ 8 - 0
tldraw/yarn.lock

@@ -2008,6 +2008,14 @@
     aria-hidden "^1.1.1"
     react-remove-scroll "2.5.4"
 
+"@radix-ui/react-separator@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.0.0.tgz#ae318b5e7e1436b68d77cf08b7cd25a37893021f"
+  integrity sha512-narSsDP+CKJWEOV9Yx9aOpw4bLaNRNIvnqmfx30mAJAJ9wPKoyVmcLzFj0Hyx3yQNZx7EokMwW6KtMyxN5jDUw==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@radix-ui/react-primitive" "1.0.0"
+
 "@radix-ui/[email protected]":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.0.tgz#7fa805b99891dea1e862d8f8fbe07f4d6d0fd698"