mirror of
https://github.com/logseq/logseq.git
synced 2026-05-28 06:34:34 +00:00
Merge pull request #6647 from logseq/feat/whiteboards-iframe
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
shapes,
|
||||
TextTool,
|
||||
YouTubeTool,
|
||||
IFrameTool,
|
||||
type Shape,
|
||||
} from './lib'
|
||||
import { LogseqContext, type LogseqContextValue } from './lib/logseq-context'
|
||||
@@ -47,6 +48,7 @@ const tools: TLReactToolConstructor<Shape>[] = [
|
||||
PencilTool,
|
||||
TextTool,
|
||||
YouTubeTool,
|
||||
IFrameTool,
|
||||
HTMLTool,
|
||||
LogseqPortalTool,
|
||||
]
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
LogseqPortalShape,
|
||||
TextShape,
|
||||
HTMLShape,
|
||||
IFrameShape,
|
||||
YouTubeShape,
|
||||
BoxShape,
|
||||
PolygonShape,
|
||||
@@ -36,13 +37,19 @@ export const contextBarActionTypes = [
|
||||
'ScaleLevel',
|
||||
'TextStyle',
|
||||
'YoutubeLink',
|
||||
'IFrameSource',
|
||||
'LogseqPortalViewMode',
|
||||
'ArrowMode',
|
||||
'OpenPage',
|
||||
] as const
|
||||
|
||||
type ContextBarActionType = typeof contextBarActionTypes[number]
|
||||
const singleShapeActions: ContextBarActionType[] = ['Edit', 'YoutubeLink', 'OpenPage']
|
||||
const singleShapeActions: ContextBarActionType[] = [
|
||||
'Edit',
|
||||
'YoutubeLink',
|
||||
'IFrameSource',
|
||||
'OpenPage',
|
||||
]
|
||||
|
||||
const contextBarActionMapping = new Map<ContextBarActionType, React.FC>()
|
||||
|
||||
@@ -51,6 +58,7 @@ type ShapeType = Shape['props']['type']
|
||||
export const shapeMapping: Partial<Record<ShapeType, ContextBarActionType[]>> = {
|
||||
'logseq-portal': ['Edit', 'LogseqPortalViewMode', 'ScaleLevel', 'OpenPage', 'AutoResizing'],
|
||||
youtube: ['YoutubeLink'],
|
||||
iframe: ['IFrameSource'],
|
||||
box: ['Swatch', 'NoFill', 'StrokeType'],
|
||||
ellipse: ['Swatch', 'NoFill', 'StrokeType'],
|
||||
polygon: ['Swatch', 'NoFill', 'StrokeType'],
|
||||
@@ -239,6 +247,42 @@ const OpenPageAction = observer(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const IFrameSourceAction = observer(() => {
|
||||
const app = useApp<Shape>()
|
||||
const shape = filterShapeByAction<IFrameShape>(app.selectedShapesArray, 'IFrameSource')[0]
|
||||
|
||||
const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
shape.onIFrameSourceChange(e.target.value.trim().toLowerCase())
|
||||
app.persist()
|
||||
}, [])
|
||||
|
||||
const handleReload = React.useCallback(() => {
|
||||
shape.reload()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<span className="flex gap-3">
|
||||
<button title="Reload" className="tl-contextbar-button" type="button" onClick={handleReload}>
|
||||
<TablerIcon name="refresh" />
|
||||
</button>
|
||||
<TextInput
|
||||
title="Website Url"
|
||||
className="tl-iframe-src"
|
||||
value={`${shape.props.url}`}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<button
|
||||
title="Open website url"
|
||||
className="tl-contextbar-button"
|
||||
type="button"
|
||||
onClick={() => window.open(shape.props.url)}
|
||||
>
|
||||
<TablerIcon name="external-link" />
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
|
||||
const YoutubeLinkAction = observer(() => {
|
||||
const app = useApp<Shape>()
|
||||
const shape = filterShapeByAction<YouTubeShape>(app.selectedShapesArray, 'YoutubeLink')[0]
|
||||
@@ -450,6 +494,7 @@ contextBarActionMapping.set('LogseqPortalViewMode', LogseqPortalViewModeAction)
|
||||
contextBarActionMapping.set('ScaleLevel', ScaleLevelAction)
|
||||
contextBarActionMapping.set('OpenPage', OpenPageAction)
|
||||
contextBarActionMapping.set('YoutubeLink', YoutubeLinkAction)
|
||||
contextBarActionMapping.set('IFrameSource', IFrameSourceAction)
|
||||
contextBarActionMapping.set('NoFill', NoFillAction)
|
||||
contextBarActionMapping.set('Swatch', SwatchAction)
|
||||
contextBarActionMapping.set('StrokeType', StrokeTypeAction)
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
LogseqPortalShape,
|
||||
VideoShape,
|
||||
ImageShape,
|
||||
IFrameShape,
|
||||
} from '../lib'
|
||||
import type { LogseqContextValue } from '../lib/logseq-context'
|
||||
|
||||
@@ -243,7 +244,13 @@ export function usePaste(context: LogseqContextValue) {
|
||||
) {
|
||||
return true
|
||||
}
|
||||
// ??? deal with normal URLs?
|
||||
|
||||
shapesToCreate.push({
|
||||
...IFrameShape.defaultProps,
|
||||
url: rawText,
|
||||
point: [point[0], point[1]],
|
||||
})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
87
tldraw/apps/tldraw-logseq/src/lib/shapes/IFrameShape.tsx
Normal file
87
tldraw/apps/tldraw-logseq/src/lib/shapes/IFrameShape.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import * as React from 'react'
|
||||
import { TLBoxShape, TLBoxShapeProps } from '@tldraw/core'
|
||||
import { HTMLContainer, TLComponentProps } from '@tldraw/react'
|
||||
import { action, computed } from 'mobx'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
export interface IFrameShapeProps extends TLBoxShapeProps {
|
||||
type: 'iframe'
|
||||
url: string
|
||||
}
|
||||
|
||||
export class IFrameShape extends TLBoxShape<IFrameShapeProps> {
|
||||
static id = 'iframe'
|
||||
frameRef = React.createRef<HTMLIFrameElement>()
|
||||
|
||||
static defaultProps: IFrameShapeProps = {
|
||||
id: 'iframe',
|
||||
type: 'iframe',
|
||||
parentId: 'page',
|
||||
point: [0, 0],
|
||||
size: [853, 480],
|
||||
url: '',
|
||||
}
|
||||
|
||||
canEdit = true
|
||||
|
||||
@action onIFrameSourceChange = (url: string) => {
|
||||
this.update({ url })
|
||||
}
|
||||
|
||||
@action reload = () => {
|
||||
this.frameRef.current.src = this.frameRef?.current?.src
|
||||
}
|
||||
|
||||
ReactComponent = observer(({ events, isErasing, isEditing }: TLComponentProps) => {
|
||||
const ref = React.useRef<HTMLIFrameElement>(null)
|
||||
|
||||
return (
|
||||
<HTMLContainer
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
pointerEvents: 'all',
|
||||
opacity: isErasing ? 0.2 : 1,
|
||||
}}
|
||||
{...events}
|
||||
>
|
||||
<div
|
||||
className="rounded-lg w-full h-full relative overflow-hidden shadow-xl"
|
||||
style={{
|
||||
pointerEvents: isEditing ? 'all' : 'none',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
{this.props.url && (
|
||||
<div
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
ref={this.frameRef}
|
||||
className="absolute inset-0 w-full h-full m-0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
src={`${this.props.url}`}
|
||||
frameBorder="0"
|
||||
sandbox="allow-scripts"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</HTMLContainer>
|
||||
)
|
||||
})
|
||||
|
||||
ReactIndicator = observer(() => {
|
||||
const {
|
||||
props: {
|
||||
size: [w, h],
|
||||
},
|
||||
} = this
|
||||
return <rect width={w} height={h} fill="transparent" rx={8} ry={8} />
|
||||
})
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { HighlighterShape } from './HighlighterShape'
|
||||
import { HTMLShape } from './HTMLShape'
|
||||
import { ImageShape } from './ImageShape'
|
||||
import { VideoShape } from './VideoShape'
|
||||
import { IFrameShape } from './IFrameShape'
|
||||
import { LineShape } from './LineShape'
|
||||
import { LogseqPortalShape } from './LogseqPortalShape'
|
||||
import { PencilShape } from './PencilShape'
|
||||
@@ -27,6 +28,7 @@ export type Shape =
|
||||
| PolygonShape
|
||||
| TextShape
|
||||
| YouTubeShape
|
||||
| IFrameShape
|
||||
| HTMLShape
|
||||
| LogseqPortalShape
|
||||
|
||||
@@ -37,6 +39,7 @@ export * from './HighlighterShape'
|
||||
export * from './HTMLShape'
|
||||
export * from './ImageShape'
|
||||
export * from './VideoShape'
|
||||
export * from './IFrameShape'
|
||||
export * from './LineShape'
|
||||
export * from './LogseqPortalShape'
|
||||
export * from './PencilShape'
|
||||
@@ -56,6 +59,7 @@ export const shapes: TLReactShapeConstructor<Shape>[] = [
|
||||
PolygonShape,
|
||||
TextShape,
|
||||
YouTubeShape,
|
||||
IFrameShape,
|
||||
HTMLShape,
|
||||
LogseqPortalShape,
|
||||
]
|
||||
|
||||
10
tldraw/apps/tldraw-logseq/src/lib/tools/IFrameTool.tsx
Normal file
10
tldraw/apps/tldraw-logseq/src/lib/tools/IFrameTool.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { TLBoxTool } from '@tldraw/core'
|
||||
import type { TLReactEventMap } from '@tldraw/react'
|
||||
import { IFrameShape, type Shape } from '../shapes'
|
||||
|
||||
export class IFrameTool extends TLBoxTool<IFrameShape, Shape, TLReactEventMap> {
|
||||
static id = 'iframe'
|
||||
Shape = IFrameShape
|
||||
}
|
||||
|
||||
export {}
|
||||
@@ -10,3 +10,4 @@ export * from './TextTool'
|
||||
export * from './YouTubeTool'
|
||||
export * from './LogseqPortalTool'
|
||||
export * from './HTMLTool'
|
||||
export * from './IFrameTool'
|
||||
|
||||
@@ -818,11 +818,11 @@ html[data-theme='dark'] {
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.tl-youtube-link {
|
||||
border-radius: 8px;
|
||||
.tl-youtube-link,
|
||||
.tl-iframe-src {
|
||||
@apply rounded-lg px-2 py-1;
|
||||
color: var(--ls-primary-text-color);
|
||||
box-shadow: 0 0 0 1px var(--ls-secondary-border-color);
|
||||
padding: 4px 14px;
|
||||
}
|
||||
|
||||
.tl-hitarea-stroke {
|
||||
|
||||
@@ -719,7 +719,6 @@ export class TLApp<
|
||||
inputs: { ctrlKey },
|
||||
} = this
|
||||
return (
|
||||
!ctrlKey &&
|
||||
this.isInAny('select.idle', 'select.hoveringSelectionHandle') &&
|
||||
!this.isIn('select.contextMenu') &&
|
||||
selectedShapesArray.length > 0 &&
|
||||
|
||||
@@ -84,7 +84,7 @@ export class TLViewport {
|
||||
|
||||
pinchCamera = (point: number[], delta: number[], zoom: number): this => {
|
||||
const { camera } = this
|
||||
zoom = Math.max(TLViewport.minZoom, Math.min(TLViewport.maxZoom, zoom));
|
||||
zoom = Math.max(TLViewport.minZoom, Math.min(TLViewport.maxZoom, zoom))
|
||||
const nextPoint = Vec.sub(camera.point, Vec.div(delta, camera.zoom))
|
||||
const p0 = Vec.sub(Vec.div(point, camera.zoom), nextPoint)
|
||||
const p1 = Vec.sub(Vec.div(point, zoom), nextPoint)
|
||||
@@ -105,7 +105,6 @@ export class TLViewport {
|
||||
zoomOut = () => {
|
||||
const { camera, bounds } = this
|
||||
this.setZoom(camera.zoom * ZOOM_UPDATE_FACTOR)
|
||||
|
||||
}
|
||||
|
||||
resetZoom = (): this => {
|
||||
|
||||
@@ -26,8 +26,8 @@ export class PinchingState<
|
||||
|
||||
// Normalize the value of deltaZ from raw WheelEvent
|
||||
const deltaZ = normalizeWheel(event)[2] * 0.01
|
||||
if (deltaZ === 0) return;
|
||||
const zoom = camera.zoom - deltaZ * camera.zoom;
|
||||
if (deltaZ === 0) return
|
||||
const zoom = camera.zoom - deltaZ * camera.zoom
|
||||
this.app.viewport.pinchCamera(info.point, [0, 0], zoom)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user