feat: publish HoloLake model-native living system source
This commit is contained in:
parent
6ad10edde1
commit
c395dd3a99
2467 changed files with 615073 additions and 0 deletions
|
|
@ -0,0 +1,187 @@
|
|||
import { useLayoutEffect } from 'react'
|
||||
import {
|
||||
blockElementById,
|
||||
editorBlockElement,
|
||||
type TolariaBlockNoteEditor,
|
||||
} from './tolariaBlockNoteDom'
|
||||
import type { SideMenuBlock } from './tolariaSideMenuBlocks'
|
||||
|
||||
type SideMenuAlignmentState = {
|
||||
attemptsRemaining: number
|
||||
frame: number | null
|
||||
hasObservedTargets: boolean
|
||||
}
|
||||
type SideMenuAlignmentContext = {
|
||||
blockId: string
|
||||
editorElement: HTMLElement
|
||||
observeTargets: () => void
|
||||
ownerWindow: Window
|
||||
retry: () => void
|
||||
state: SideMenuAlignmentState
|
||||
}
|
||||
|
||||
const SIDE_MENU_ALIGNMENT_ATTEMPTS = 8
|
||||
|
||||
function sideMenuElementForEditor(editorElement: HTMLElement): HTMLElement | null {
|
||||
const container = editorElement.closest('.editor__blocknote-container') ?? editorElement
|
||||
const sideMenu = container.querySelector('.bn-side-menu')
|
||||
return sideMenu instanceof HTMLElement ? sideMenu : null
|
||||
}
|
||||
|
||||
function blockTextAnchorRect(blockElement: HTMLElement): DOMRect | null {
|
||||
const content = blockElement.querySelector('.bn-block-content')
|
||||
const inlineContent = content?.querySelector('.bn-inline-content') ?? content
|
||||
if (!(inlineContent instanceof HTMLElement)) return null
|
||||
|
||||
const ownerDocument = inlineContent.ownerDocument
|
||||
const range = ownerDocument.createRange()
|
||||
range.selectNodeContents(inlineContent)
|
||||
const firstLineRect = Array.from(range.getClientRects())
|
||||
.find((rect) => rect.width > 0 && rect.height > 0)
|
||||
const textRect = firstLineRect ?? range.getBoundingClientRect()
|
||||
range.detach()
|
||||
|
||||
if (textRect.height > 0) return textRect
|
||||
|
||||
const fallbackRect = inlineContent.getBoundingClientRect()
|
||||
return fallbackRect.height > 0 ? fallbackRect : null
|
||||
}
|
||||
|
||||
function alignSideMenuWithBlockText(editorElement: HTMLElement, blockId: string): boolean {
|
||||
const blockElement = blockElementById(editorElement, blockId)
|
||||
const sideMenu = sideMenuElementForEditor(editorElement)
|
||||
if (!blockElement || !sideMenu) return false
|
||||
|
||||
const anchorRect = blockTextAnchorRect(blockElement)
|
||||
if (!anchorRect) return false
|
||||
|
||||
sideMenu.style.removeProperty('translate')
|
||||
const sideMenuRect = sideMenu.getBoundingClientRect()
|
||||
if (sideMenuRect.height <= 0) return false
|
||||
|
||||
const anchorCenter = anchorRect.top + anchorRect.height / 2
|
||||
const sideMenuCenter = sideMenuRect.top + sideMenuRect.height / 2
|
||||
sideMenu.style.setProperty('translate', `0 ${anchorCenter - sideMenuCenter}px`)
|
||||
return true
|
||||
}
|
||||
|
||||
function createSideMenuAlignmentState(): SideMenuAlignmentState {
|
||||
return {
|
||||
attemptsRemaining: SIDE_MENU_ALIGNMENT_ATTEMPTS,
|
||||
frame: null,
|
||||
hasObservedTargets: false,
|
||||
}
|
||||
}
|
||||
|
||||
function createSideMenuResizeObserver(onResize: () => void): ResizeObserver | null {
|
||||
return typeof ResizeObserver === 'undefined'
|
||||
? null
|
||||
: new ResizeObserver(onResize)
|
||||
}
|
||||
|
||||
function observeSideMenuAlignmentTargets({
|
||||
blockId,
|
||||
editorElement,
|
||||
resizeObserver,
|
||||
state,
|
||||
}: {
|
||||
blockId: string
|
||||
editorElement: HTMLElement
|
||||
resizeObserver: ResizeObserver | null
|
||||
state: SideMenuAlignmentState
|
||||
}) {
|
||||
if (state.hasObservedTargets) return
|
||||
|
||||
const blockElement = blockElementById(editorElement, blockId)
|
||||
const sideMenu = sideMenuElementForEditor(editorElement)
|
||||
if (!resizeObserver || !blockElement || !sideMenu) return
|
||||
|
||||
resizeObserver.observe(blockElement)
|
||||
resizeObserver.observe(sideMenu)
|
||||
state.hasObservedTargets = true
|
||||
}
|
||||
|
||||
function scheduleSideMenuTextAlignment(context: SideMenuAlignmentContext) {
|
||||
const { blockId, editorElement, observeTargets, ownerWindow, retry, state } = context
|
||||
if (state.frame !== null) return
|
||||
|
||||
state.frame = ownerWindow.requestAnimationFrame(() => {
|
||||
state.frame = null
|
||||
const aligned = alignSideMenuWithBlockText(editorElement, blockId)
|
||||
observeTargets()
|
||||
if (!aligned && state.attemptsRemaining > 0) {
|
||||
state.attemptsRemaining -= 1
|
||||
retry()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createSideMenuAlignmentCleanup({
|
||||
editorElement,
|
||||
ownerWindow,
|
||||
resizeObserver,
|
||||
scheduleAlignment,
|
||||
state,
|
||||
}: {
|
||||
editorElement: HTMLElement
|
||||
ownerWindow: Window
|
||||
resizeObserver: ResizeObserver | null
|
||||
scheduleAlignment: () => void
|
||||
state: SideMenuAlignmentState
|
||||
}) {
|
||||
return () => {
|
||||
if (state.frame !== null) ownerWindow.cancelAnimationFrame(state.frame)
|
||||
resizeObserver?.disconnect()
|
||||
ownerWindow.removeEventListener('resize', scheduleAlignment)
|
||||
sideMenuElementForEditor(editorElement)?.style.removeProperty('translate')
|
||||
}
|
||||
}
|
||||
|
||||
function createSideMenuAlignmentController(editor: TolariaBlockNoteEditor, blockId: string) {
|
||||
const editorElement = editorBlockElement(editor)
|
||||
const ownerWindow = editorElement?.ownerDocument.defaultView
|
||||
if (!editorElement || !ownerWindow) return undefined
|
||||
|
||||
const state = createSideMenuAlignmentState()
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
const observeTargets = () => observeSideMenuAlignmentTargets({
|
||||
blockId,
|
||||
editorElement,
|
||||
resizeObserver,
|
||||
state,
|
||||
})
|
||||
const scheduleAlignment = () => scheduleSideMenuTextAlignment({
|
||||
blockId,
|
||||
editorElement,
|
||||
observeTargets,
|
||||
ownerWindow,
|
||||
retry: scheduleAlignment,
|
||||
state,
|
||||
})
|
||||
|
||||
resizeObserver = createSideMenuResizeObserver(scheduleAlignment)
|
||||
scheduleAlignment()
|
||||
observeTargets()
|
||||
ownerWindow.addEventListener('resize', scheduleAlignment)
|
||||
|
||||
return createSideMenuAlignmentCleanup({
|
||||
editorElement,
|
||||
ownerWindow,
|
||||
resizeObserver,
|
||||
scheduleAlignment,
|
||||
state,
|
||||
})
|
||||
}
|
||||
|
||||
export function useSideMenuTextAlignment(
|
||||
editor: TolariaBlockNoteEditor,
|
||||
block: SideMenuBlock | undefined,
|
||||
) {
|
||||
const blockId = block?.id
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!blockId) return
|
||||
|
||||
return createSideMenuAlignmentController(editor, blockId)
|
||||
}, [blockId, editor])
|
||||
}
|
||||
Loading…
Reference in a new issue