import type { RuntimeContext } from '../../runtime/types'; import { print } from '../../utils/print'; import type { ShortcutKey } from '../../utils/shortcut'; import { Shortcut } from '../../utils/shortcut'; import type { BasePluginOptions } from '../base-plugin'; import { BasePlugin } from '../base-plugin'; /** * 全屏配置项 * * Full screen options */ export interface FullscreenOptions extends BasePluginOptions { /** * 触发全屏的方式 * - `request` : 请求全屏 * - `exit` : 退出全屏 * * The way to trigger full screen * - `request`: request full screen * - `exit`: exit full screen */ trigger?: { request?: ShortcutKey; exit?: ShortcutKey; }; /** * 是否自适应画布尺寸,全屏后画布尺寸会自动适应屏幕尺寸 * * Whether to adapt the canvas size * @defaultValue true */ autoFit?: boolean; /** * 进入全屏后的回调 * * Callback after entering full screen */ onEnter?: () => void; /** * 退出全屏后的回调 * * Callback after exiting full screen */ onExit?: () => void; } /** * 全屏 * * Full screen */ export class Fullscreen extends BasePlugin { static defaultOptions: Partial = { trigger: {}, autoFit: true, }; private shortcut: Shortcut; private style: HTMLStyleElement; private $el = this.context.canvas.getContainer()!; private graphSize: [number, number] = [0, 0]; constructor(context: RuntimeContext, options: FullscreenOptions) { super(context, Object.assign({}, Fullscreen.defaultOptions, options)); this.shortcut = new Shortcut(context.graph); this.bindEvents(); this.style = document.createElement('style'); document.head.appendChild(this.style); this.style.innerHTML = ` :not(:root):fullscreen::backdrop { background: transparent; } `; } private bindEvents() { this.unbindEvents(); this.shortcut.unbindAll(); const { request = [], exit = [] } = this.options.trigger; this.shortcut.bind(request, this.request); this.shortcut.bind(exit, this.exit); const events = ['webkitfullscreenchange', 'mozfullscreenchange', 'fullscreenchange', 'MSFullscreenChange']; events.forEach((eventName) => { document.addEventListener(eventName, this.onFullscreenChange, false); }); } private unbindEvents() { this.shortcut.unbindAll(); const events = ['webkitfullscreenchange', 'mozfullscreenchange', 'fullscreenchange', 'MSFullscreenChange']; events.forEach((eventName) => { document.removeEventListener(eventName, this.onFullscreenChange, false); }); } private setGraphSize(fullScreen = true) { let width, height; if (fullScreen) { width = globalThis.screen?.width || 0; height = globalThis.screen?.height || 0; this.graphSize = this.context.graph.getSize(); } else { [width, height] = this.graphSize; } this.context.graph.setSize(width, height); this.context.graph.render(); } private onFullscreenChange = () => { const isFull = !!document.fullscreenElement; if (this.options.autoFit) this.setGraphSize(isFull); if (isFull) { this.options.onEnter?.(); } else { this.options.onExit?.(); } }; /** * 请求全屏 * * Request full screen */ public request() { if (document.fullscreenElement || !isFullscreenEnabled()) return; this.$el.requestFullscreen().catch((err: Error) => { print.warn(`Error attempting to enable full-screen: ${err.message} (${err.name})`); }); } /** * 退出全屏 * * Exit full screen */ public exit() { if (!document.fullscreenElement) return; document.exitFullscreen(); } /** * 更新配置 * * Update options * @param options - 配置项 | Options * @internal */ public update(options: Partial): void { this.unbindEvents(); super.update(options); this.bindEvents(); } public destroy(): void { this.exit(); this.style.remove(); super.destroy(); } } /** * 判断是否支持全屏 * * Determine whether full screen is enabled * @returns 是否支持全屏 | Whether full screen is enabled */ function isFullscreenEnabled() { return ( document.fullscreenEnabled || // 使用 Reflect 语法规避 ts 检查 | use Reflect to avoid ts checking Reflect.get(document, 'webkitFullscreenEnabled') || Reflect.get(document, 'mozFullscreenEnabled') || Reflect.get(document, 'msFullscreenEnabled') ); }