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')
);
}