mirror of https://github.com/msgbyte/tailchat
feat: 增加功能,在新窗口打开会话
parent
11d0411537
commit
ce1f9af030
@ -0,0 +1,28 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
interface UIState {
|
||||
panelWinUrls: string[];
|
||||
}
|
||||
|
||||
const initialState: UIState = {
|
||||
panelWinUrls: [],
|
||||
};
|
||||
|
||||
const uiSlice = createSlice({
|
||||
name: 'ui',
|
||||
initialState,
|
||||
reducers: {
|
||||
addPanelWindowUrl(state, action: PayloadAction<{ url: string }>) {
|
||||
const panelUrl = action.payload.url;
|
||||
state.panelWinUrls.push(panelUrl);
|
||||
},
|
||||
deletePanelWindowUrl(state, action: PayloadAction<{ url: string }>) {
|
||||
const panelUrl = action.payload.url;
|
||||
const index = state.panelWinUrls.indexOf(panelUrl);
|
||||
state.panelWinUrls.splice(index, 1);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const uiActions = uiSlice.actions;
|
||||
export const uiReducer = uiSlice.reducer;
|
@ -0,0 +1,39 @@
|
||||
import { panelWindowManager } from '@/utils/window-helper';
|
||||
import { useCallback } from 'react';
|
||||
import { uiActions, useAppDispatch, useAppSelector } from 'tailchat-shared';
|
||||
|
||||
/**
|
||||
* 面板窗口上下文信息
|
||||
* 使用hooks来将 redux 数据与 panelWindowManager 数据联合
|
||||
*/
|
||||
export function usePanelWindow(panelUrl: string) {
|
||||
const hasOpenedPanel = useAppSelector(
|
||||
(state) =>
|
||||
/**
|
||||
* 两处维护地址均存在才视为在控制下
|
||||
*/
|
||||
state.ui.panelWinUrls.includes(panelUrl) &&
|
||||
panelWindowManager.has(panelUrl)
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const openPanelWindow = useCallback(() => {
|
||||
panelWindowManager.open(panelUrl, {
|
||||
onClose() {
|
||||
dispatch(uiActions.deletePanelWindowUrl({ url: panelUrl }));
|
||||
},
|
||||
});
|
||||
dispatch(uiActions.addPanelWindowUrl({ url: panelUrl }));
|
||||
}, [panelUrl]);
|
||||
|
||||
const closePanelWindow = useCallback(() => {
|
||||
panelWindowManager.close(panelUrl);
|
||||
dispatch(uiActions.deletePanelWindowUrl({ url: panelUrl }));
|
||||
}, [panelUrl]);
|
||||
|
||||
return {
|
||||
hasOpenedPanel,
|
||||
openPanelWindow,
|
||||
closePanelWindow,
|
||||
};
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { buildWindowFeatures } from '../window-helper';
|
||||
|
||||
describe('window-helper', () => {
|
||||
describe('buildWindowFeatures', () => {
|
||||
test.each([
|
||||
[{ foo: 'bar' }, 'foo=bar'],
|
||||
[{ foo: 'bar', baz: 'qux' }, 'foo=bar,baz=qux'],
|
||||
[{ foo: 'bar', baz: 1 }, 'foo=bar,baz=1'],
|
||||
[{ foo: 'bar', baz: true }, 'foo=bar,baz=true'],
|
||||
])('%p => %s', (input, output) => {
|
||||
expect(buildWindowFeatures(input)).toBe(output);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 多窗口管理工具
|
||||
*/
|
||||
|
||||
/**
|
||||
* 窗口序列号
|
||||
* 一个自增数
|
||||
*/
|
||||
let windowIndex = 0;
|
||||
|
||||
export function buildWindowFeatures(
|
||||
options: Record<string, string | number | boolean>
|
||||
): string {
|
||||
return Object.entries(options)
|
||||
.map(([key, val]) => `${key}=${val}`)
|
||||
.join(',');
|
||||
}
|
||||
|
||||
/**
|
||||
* 在新窗口打开
|
||||
* @param url 新窗口地址
|
||||
*/
|
||||
export function openInNewWindow(url: string) {
|
||||
const width = 414;
|
||||
const height = 736;
|
||||
const top = (window.screen.height - height) / 2;
|
||||
const left = (window.screen.width - width) / 2;
|
||||
|
||||
// 打开窗口
|
||||
const win = window.open(
|
||||
url,
|
||||
`tailwindow#${windowIndex++}`,
|
||||
buildWindowFeatures({
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height,
|
||||
menubar: false,
|
||||
toolbar: false,
|
||||
location: false,
|
||||
status: false,
|
||||
resizable: true,
|
||||
})
|
||||
);
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
class PanelWindowManager {
|
||||
openedPanelWindows: Record<string, Window> = {};
|
||||
|
||||
/**
|
||||
* 打开一个独立窗口
|
||||
* @param url 窗口Url
|
||||
*/
|
||||
open(url: string, options?: { onClose: () => void }): void {
|
||||
if (this.openedPanelWindows[url]) {
|
||||
this.openedPanelWindows[url].focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const win = openInNewWindow(url);
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = setInterval(() => {
|
||||
// 目前没有好的办法可以检测打开的窗口是否被直接关闭
|
||||
// 轮询检测是否被关闭
|
||||
if (win.closed) {
|
||||
// 窗口已经被用户关闭
|
||||
delete this.openedPanelWindows[url];
|
||||
clearInterval(timer);
|
||||
|
||||
if (typeof options?.onClose === 'function') {
|
||||
// 触发回调
|
||||
options?.onClose();
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
this.openedPanelWindows[url] = win;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭窗口
|
||||
* @param url 窗口url
|
||||
*/
|
||||
close(url: string): void {
|
||||
if (!this.openedPanelWindows[url]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openedPanelWindows[url].close();
|
||||
delete this.openedPanelWindows[url];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查窗口是否被打开
|
||||
* @param url 窗口Url
|
||||
*/
|
||||
has(url: string): boolean {
|
||||
return !!this.openedPanelWindows[url];
|
||||
}
|
||||
}
|
||||
|
||||
export const panelWindowManager = new PanelWindowManager();
|
Loading…
Reference in New Issue