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