Add tab renderer panel & tooltips cancle outer tab & switch tab #11
| @ -1,6 +1,7 @@ | |||||||
| import { BackgroundLevel, Theme } from "@Component/Theme/Theme"; | import { BackgroundLevel, Theme } from "@Component/Theme/Theme"; | ||||||
| import { DirectionalHint, IconButton } from "@fluentui/react"; | import { DirectionalHint, IconButton } from "@fluentui/react"; | ||||||
| import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost"; | import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost"; | ||||||
|  | import { useSetting, IMixinSettingProps } from "@Context/Setting"; | ||||||
| import { AllI18nKeys } from "../Localization/Localization"; | import { AllI18nKeys } from "../Localization/Localization"; | ||||||
| import { Component, ReactNode } from "react"; | import { Component, ReactNode } from "react"; | ||||||
| import "./CommandBar.scss"; | import "./CommandBar.scss"; | ||||||
| @ -9,13 +10,19 @@ interface ICommandBarProps { | |||||||
|     width: number; |     width: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class CommandBar extends Component<ICommandBarProps> { | @useSetting | ||||||
|  | class CommandBar extends Component<ICommandBarProps & IMixinSettingProps> { | ||||||
| 
 | 
 | ||||||
|     render(): ReactNode { |     render(): ReactNode { | ||||||
|         return <Theme |         return <Theme | ||||||
|             className="command-bar" |             className="command-bar" | ||||||
|             backgroundLevel={BackgroundLevel.Level2} |             backgroundLevel={BackgroundLevel.Level2} | ||||||
|             style={{ width: this.props.width }} |             style={{ width: this.props.width }} | ||||||
|  |             onClick={() => { | ||||||
|  |                 if (this.props.setting) { | ||||||
|  |                     this.props.setting.layout.focus(""); | ||||||
|  |                 } | ||||||
|  |             }} | ||||||
|         > |         > | ||||||
|             <div> |             <div> | ||||||
|                 {this.getRenderButton({ iconName: "Save", i18NKey: "Command.Bar.Save.Info" })} |                 {this.getRenderButton({ iconName: "Save", i18NKey: "Command.Bar.Save.Info" })} | ||||||
|  | |||||||
| @ -43,6 +43,7 @@ div.app-container { | |||||||
| 			box-sizing: border-box; | 			box-sizing: border-box; | ||||||
| 			display: flex; | 			display: flex; | ||||||
| 			border: .8px solid rgba($color: #000000, $alpha: 0); | 			border: .8px solid rgba($color: #000000, $alpha: 0); | ||||||
|  | 			// transition: all 300ms ease-in-out; | ||||||
| 			justify-content: space-between; | 			justify-content: space-between; | ||||||
| 			align-items: stretch; | 			align-items: stretch; | ||||||
| 			flex-direction: column; | 			flex-direction: column; | ||||||
| @ -63,7 +64,7 @@ div.app-container { | |||||||
| 				box-sizing: border-box; | 				box-sizing: border-box; | ||||||
| 				height: 32.8px; | 				height: 32.8px; | ||||||
| 				border: .8px solid rgba($color: #000000, $alpha: 0); | 				border: .8px solid rgba($color: #000000, $alpha: 0); | ||||||
| 				transition: all 300ms ease-in-out; | 				// transition: all 300ms ease-in-out; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			div.title-view { | 			div.title-view { | ||||||
| @ -82,6 +83,7 @@ div.app-container { | |||||||
| 
 | 
 | ||||||
| 		div.app-tab-header-item.active { | 		div.app-tab-header-item.active { | ||||||
| 			border: .8px solid blue; | 			border: .8px solid blue; | ||||||
|  | 			transition: none; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		div.app-tab-header-item::after { | 		div.app-tab-header-item::after { | ||||||
| @ -92,32 +94,52 @@ div.app-container { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	div.app-panel.has-padding { | ||||||
|  | 		padding: 10px; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	div.app-panel { | 	div.app-panel { | ||||||
| 		width: 100%; | 		width: 100%; | ||||||
| 		height: 100%; | 		height: 100%; | ||||||
| 		box-sizing: border-box; | 		box-sizing: border-box; | ||||||
|  | 		overflow: scroll; | ||||||
|  | 		-ms-overflow-style: none; | ||||||
| 		border: .8px solid rgba($color: #000000, $alpha: 0); | 		border: .8px solid rgba($color: #000000, $alpha: 0); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	div.app-panel.hide-scrollbar::-webkit-scrollbar { | ||||||
|  | 		width : 0;  /*高宽分别对应横竖滚动条的尺寸*/ | ||||||
|  | 		height: 0; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	div.app-panel::-webkit-scrollbar { | ||||||
|  | 		width : 8px;  /*高宽分别对应横竖滚动条的尺寸*/ | ||||||
|  |   		height: 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	div.app-panel::-webkit-scrollbar-thumb { | ||||||
|  | 		/*滚动条里面小方块*/ | ||||||
|  | 		border-radius: 8px; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	div.app-panel::-webkit-scrollbar-track { | ||||||
|  | 		/*滚动条里面轨道*/ | ||||||
|  | 		border-radius: 8px; | ||||||
|  | 		background-color: rgba($color: #000000, $alpha: 0); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	div.app-panel.active { | 	div.app-panel.active { | ||||||
| 		border: .8px solid blue !important; | 		border: .8px solid blue !important; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.app-panel { |  | ||||||
| 	overflow: scroll; |  | ||||||
| 	-ms-overflow-style: none; |  | ||||||
| } |  | ||||||
| div.app-panel::-webkit-scrollbar { |  | ||||||
| 	width: 0 !important; |  | ||||||
| 	height: 0 !important; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| div.dark.app-container.end-containe { | div.dark.app-container.end-containe { | ||||||
| 	border: .8px solid $lt-bg-color-lvl3-dark; | 	border: .8px solid $lt-bg-color-lvl3-dark; | ||||||
| 
 | 
 | ||||||
|  | 	div.app-tab-header-item.tab, | ||||||
| 	div.app-tab-header-item.active, | 	div.app-tab-header-item.active, | ||||||
| 	div.app-tab-header-item:hover { | 	div.app-tab-header-item:hover { | ||||||
|  | 		transition: none; | ||||||
| 		background-color: $lt-bg-color-lvl4-dark; | 		background-color: $lt-bg-color-lvl4-dark; | ||||||
| 		color: rgba($color: #FFFFFF, $alpha: .85); | 		color: rgba($color: #FFFFFF, $alpha: .85); | ||||||
| 	} | 	} | ||||||
| @ -125,21 +147,33 @@ div.dark.app-container.end-containe { | |||||||
| 	div.app-tab-header-item.active { | 	div.app-tab-header-item.active { | ||||||
| 		div.border-view::after { | 		div.border-view::after { | ||||||
| 			background-color: $lt-bg-color-lvl4-dark; | 			background-color: $lt-bg-color-lvl4-dark; | ||||||
|  | 			transition: none; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	div.app-panel::-webkit-scrollbar-thumb { | ||||||
|  | 		background-color: $lt-bg-color-lvl1-dark; | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.light.app-container.end-containe { | div.light.app-container.end-containe { | ||||||
| 	border: .8px solid $lt-bg-color-lvl3-light; | 	border: .8px solid $lt-bg-color-lvl3-light; | ||||||
| 
 | 
 | ||||||
|  | 	div.app-tab-header-item.tab, | ||||||
| 	div.app-tab-header-item.active, | 	div.app-tab-header-item.active, | ||||||
| 	div.app-tab-header-item:hover { | 	div.app-tab-header-item:hover { | ||||||
|  | 		transition: none; | ||||||
| 		color: rgba($color: #000000, $alpha: .85); | 		color: rgba($color: #000000, $alpha: .85); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	div.app-tab-header-item.active { | 	div.app-tab-header-item.active { | ||||||
| 		div.border-view::after { | 		div.border-view::after { | ||||||
| 			background-color: $lt-bg-color-lvl4-light; | 			background-color: $lt-bg-color-lvl4-light; | ||||||
|  | 			transition: none; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	div.app-panel::-webkit-scrollbar-thumb { | ||||||
|  | 		background-color: $lt-bg-color-lvl1-light; | ||||||
|  | 	} | ||||||
| } | } | ||||||
| @ -1,7 +1,11 @@ | |||||||
|  | import { Localization } from "@Component/Localization/Localization"; | ||||||
| import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme"; | import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme"; | ||||||
| import { Themes } from "@Context/Setting"; | import { Themes } from "@Context/Setting"; | ||||||
|  | import { DirectionalHint } from "@fluentui/react"; | ||||||
| import { ILayout, LayoutDirection } from "@Model/Layout"; | import { ILayout, LayoutDirection } from "@Model/Layout"; | ||||||
| import { Component, ReactNode } from "react"; | import { Component, ReactNode, MouseEvent } from "react"; | ||||||
|  | import { getPanelById, getPanelInfoById } from "../../Panel/Panel"; | ||||||
|  | import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost"; | ||||||
| import "./Container.scss"; | import "./Container.scss"; | ||||||
| 
 | 
 | ||||||
| interface IContainerProps extends ILayout { | interface IContainerProps extends ILayout { | ||||||
| @ -10,17 +14,13 @@ interface IContainerProps extends ILayout { | |||||||
| 	theme?: Themes; | 	theme?: Themes; | ||||||
| 	focusId?: string; | 	focusId?: string; | ||||||
| 	onScaleChange?: (id: number, scale: number) => any; | 	onScaleChange?: (id: number, scale: number) => any; | ||||||
| } | 	onFocusTab?: (id: string) => any; | ||||||
| 
 |  | ||||||
| function getPanelById(id: string) { |  | ||||||
| 	return <Theme |  | ||||||
| 		className="app-panel" draggable={false} |  | ||||||
| 	>{id}</Theme> |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class Container extends Component<IContainerProps> { | class Container extends Component<IContainerProps> { | ||||||
| 
 | 
 | ||||||
| 	private focusEdgeId: number | undefined; | 	private focusEdgeId: number | undefined; | ||||||
|  | 
 | ||||||
| 	private readonly edgeInfo = { | 	private readonly edgeInfo = { | ||||||
| 		direction: LayoutDirection.Y, | 		direction: LayoutDirection.Y, | ||||||
| 		rootWidth: 0, | 		rootWidth: 0, | ||||||
| @ -31,62 +31,83 @@ class Container extends Component<IContainerProps> { | |||||||
| 		mouseY: 0 | 		mouseY: 0 | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	private renderPanel(panles: string[], showBar: boolean) { | 	/** | ||||||
|  | 	 * 渲染此 Tab 下的 ELE | ||||||
|  | 	 */ | ||||||
|  | 	private renderPanel(panles: string[], showBar: boolean, focus?: string) { | ||||||
| 
 | 
 | ||||||
| 		const classList: string[] = []; | 		const classList: string[] = []; | ||||||
| 		const theme: Themes = this.props.theme ?? Themes.dark; | 		const theme: Themes = this.props.theme ?? Themes.dark; | ||||||
|  | 		const showPanelId = focus ?? panles[0]; | ||||||
|  | 		const showPanelInfo = getPanelInfoById(showPanelId as any); | ||||||
| 
 | 
 | ||||||
| 		classList.push(theme === Themes.light ? "light" : "dark"); | 		classList.push(theme === Themes.light ? "light" : "dark"); | ||||||
| 		classList.push(`background-${BackgroundLevel.Level3}`); | 		classList.push(`background-${BackgroundLevel.Level3}`); | ||||||
| 		classList.push(`font-${FontLevel.Level3}`); | 		classList.push(`font-${FontLevel.Level3}`); | ||||||
| 		classList.push("app-tab-header"); | 		classList.push("app-tab-header"); | ||||||
| 
 | 
 | ||||||
|  | 		const hasActivePanel = panles.some((id) => id === this.props.focusId); | ||||||
|  | 
 | ||||||
| 		return <> | 		return <> | ||||||
| 			{showBar ?  | 			{showBar ?  | ||||||
| 				<div className={classList.join(" ")} >{ | 				<div className={classList.join(" ")} onClick={() => { | ||||||
|  | 					this.props.onFocusTab ? this.props.onFocusTab("") : undefined | ||||||
|  | 				}}>{ | ||||||
| 					panles.map((panelId: string) => { | 					panles.map((panelId: string) => { | ||||||
| 						return <div key={panelId} className="app-tab-header-item"> | 
 | ||||||
|  | 						const classList: string[] = ["app-tab-header-item"]; | ||||||
|  | 						if (panelId === this.props.focusId) classList.push("active"); | ||||||
|  | 						if (panelId === showPanelId) classList.push("tab"); | ||||||
|  | 						const panelInfo = getPanelInfoById(panelId as any); | ||||||
|  | 
 | ||||||
|  | 						return <LocalizationTooltipHost | ||||||
|  | 							i18nKey={panelInfo ? panelInfo.introKay as any : "Panel.Info.Notfound"} | ||||||
|  | 							options={{id: panelId}} | ||||||
|  | 							directionalHint={DirectionalHint.topAutoEdge} | ||||||
|  | 							delay={2} | ||||||
|  | 							key={panelId} | ||||||
|  | 						> | ||||||
|  | 							<div	 | ||||||
|  | 								className={classList.join(" ")} | ||||||
|  | 								onClick={(e) => { | ||||||
|  | 									e.stopPropagation(); | ||||||
|  | 									this.props.onFocusTab ? this.props.onFocusTab(panelId) : undefined; | ||||||
|  | 								}} | ||||||
|  | 							> | ||||||
| 								<div className="border-view"></div> | 								<div className="border-view"></div> | ||||||
| 							<div className="title-view" >{panelId}</div> | 								<div className="title-view"> | ||||||
|  | 									{ | ||||||
|  | 										panelInfo ? | ||||||
|  | 											<Localization i18nKey={panelInfo.nameKey as any}/>: | ||||||
|  | 											<Localization i18nKey="Panel.Title.Notfound" options={{id: panelId}}/> | ||||||
|  | 									} | ||||||
|  | 									 | ||||||
| 								</div> | 								</div> | ||||||
|  | 							</div> | ||||||
|  | 						</LocalizationTooltipHost> | ||||||
| 					}) | 					}) | ||||||
| 				}</div> : null | 				}</div> : null | ||||||
| 			} | 			} | ||||||
| 			{getPanelById(panles[0])} | 			<div | ||||||
|  | 				onClick={() => this.props.onFocusTab ? this.props.onFocusTab(showPanelId) : undefined} | ||||||
|  | 				className={[ | ||||||
|  | 					"app-panel", | ||||||
|  | 					hasActivePanel ? "active" : "", | ||||||
|  | 					showPanelInfo?.hidePadding ? "" : "has-padding", | ||||||
|  | 					showPanelInfo?.hideScrollBar ? "hide-scrollbar" : "" | ||||||
|  | 				].filter(x => !!x).join(" ")} | ||||||
|  | 				draggable={false} | ||||||
|  | 			> | ||||||
|  | 				{getPanelById(showPanelId as any)} | ||||||
|  | 			</div> | ||||||
| 		</> | 		</> | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	private renderContainer( | 	/** | ||||||
| 		props: IContainerProps,  | 	 * 处理鼠标移动数据 | ||||||
| 		selfScale: number = 50,  | 	 */ | ||||||
| 		selfLayout: LayoutDirection = LayoutDirection.Y | 	private handelMouseMove = (e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>) => { | ||||||
| 	) { |  | ||||||
| 
 | 
 | ||||||
| 		const items: [IContainerProps, IContainerProps] | undefined = props.items; |  | ||||||
| 		const showBar: boolean = props.showBar ?? true; |  | ||||||
| 		const panles: string[] = props.panles ?? []; |  | ||||||
| 		const layout: LayoutDirection = props.layout ?? LayoutDirection.Y; |  | ||||||
| 		const scale: number = props.scale ?? 50; |  | ||||||
| 		const isRoot: boolean = !!props.isRoot; |  | ||||||
| 		const classList: string[] = []; |  | ||||||
| 		const theme: Themes = this.props.theme ?? Themes.dark; |  | ||||||
| 
 |  | ||||||
| 		classList.push(theme === Themes.light ? "light" : "dark"); |  | ||||||
| 		classList.push(`background-${BackgroundLevel.Level4}`); |  | ||||||
| 		classList.push(`font-${FontLevel.normal}`); |  | ||||||
| 		classList.push("app-container"); |  | ||||||
| 		if (panles.length > 0 && !items) classList.push("end-containe"); |  | ||||||
| 
 |  | ||||||
| 		return <div |  | ||||||
| 			className={classList.join(" ")} |  | ||||||
| 			draggable={false} |  | ||||||
| 			style={{ |  | ||||||
| 				transition: "none", |  | ||||||
| 				flexDirection: layout === LayoutDirection.Y ? "column" : undefined, |  | ||||||
| 				width: isRoot ? "100%" : selfLayout === LayoutDirection.X ? `${selfScale}%` : undefined, |  | ||||||
| 				height: isRoot ? "100%" : selfLayout === LayoutDirection.Y ? `${selfScale}%` : undefined |  | ||||||
| 			}} |  | ||||||
| 			onMouseMove={isRoot ? (e) => { |  | ||||||
| 		if (this.props.onScaleChange && this.focusEdgeId !== undefined) { | 		if (this.props.onScaleChange && this.focusEdgeId !== undefined) { | ||||||
| 			e.preventDefault(); | 			e.preventDefault(); | ||||||
| 			let mouveDist: number = 0; | 			let mouveDist: number = 0; | ||||||
| @ -114,21 +135,15 @@ class Container extends Component<IContainerProps> { | |||||||
| 			let newScale = newSize / rootSize; | 			let newScale = newSize / rootSize; | ||||||
| 			this.props.onScaleChange(this.focusEdgeId, newScale * 100); | 			this.props.onScaleChange(this.focusEdgeId, newScale * 100); | ||||||
| 		} | 		} | ||||||
| 			} : undefined} | 	} | ||||||
| 			onMouseUp={isRoot ? () => { | 
 | ||||||
| 				this.focusEdgeId = undefined; | 	/** | ||||||
| 			} : undefined} | 	 * 处理鼠标按下事件 | ||||||
| 		> | 	 * 记录鼠标数据 | ||||||
| 			{panles.length > 0 && !items ? this.renderPanel(panles, showBar) : null} | 	 */ | ||||||
| 			{items && items[0] ? this.renderContainer(items[0], scale, layout) : null} | 	private handelMouseDown = (props: ILayout, e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>) => { | ||||||
| 			{items && items[1] ? <div className="drag-bar" style={{ |  | ||||||
| 				width: layout === LayoutDirection.Y ? "100%" : 0, |  | ||||||
| 				height: layout === LayoutDirection.X ? "100%" : 0 |  | ||||||
| 			}}> |  | ||||||
| 				<div |  | ||||||
| 					style={{ cursor: layout === LayoutDirection.Y ? "n-resize" : "e-resize" }} |  | ||||||
| 					onMouseDown={(e) => { |  | ||||||
| 		const targetNode = e.target; | 		const targetNode = e.target; | ||||||
|  | 
 | ||||||
| 		if (targetNode instanceof HTMLDivElement) { | 		if (targetNode instanceof HTMLDivElement) { | ||||||
| 			let root = targetNode.parentNode?.parentNode; | 			let root = targetNode.parentNode?.parentNode; | ||||||
| 			let firstDiv = targetNode.parentNode?.parentNode?.childNodes[0]; | 			let firstDiv = targetNode.parentNode?.parentNode?.childNodes[0]; | ||||||
| @ -142,13 +157,68 @@ class Container extends Component<IContainerProps> { | |||||||
| 		} | 		} | ||||||
| 		this.edgeInfo.mouseX = e.clientX; | 		this.edgeInfo.mouseX = e.clientX; | ||||||
| 		this.edgeInfo.mouseY = e.clientY; | 		this.edgeInfo.mouseY = e.clientY; | ||||||
|  | 
 | ||||||
| 		this.edgeInfo.direction = props.layout ?? LayoutDirection.Y; | 		this.edgeInfo.direction = props.layout ?? LayoutDirection.Y; | ||||||
| 		this.focusEdgeId = props.id ?? 0; | 		this.focusEdgeId = props.id ?? 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * 递归渲染全部容器 | ||||||
|  | 	 */ | ||||||
|  | 	private renderContainer ( | ||||||
|  | 		props: IContainerProps, selfScale: number = 50, | ||||||
|  | 		selfLayout: LayoutDirection = LayoutDirection.Y | ||||||
|  | 	) { | ||||||
|  | 
 | ||||||
|  | 		const items: [IContainerProps, IContainerProps] | undefined = props.items; | ||||||
|  | 		const showBar: boolean = props.showBar ?? true; | ||||||
|  | 		const panles: string[] = props.panles ?? []; | ||||||
|  | 		const layout: LayoutDirection = props.layout ?? LayoutDirection.Y; | ||||||
|  | 		const scale: number = props.scale ?? 50; | ||||||
|  | 		const isRoot: boolean = !!props.isRoot; | ||||||
|  | 		const classList: string[] = []; | ||||||
|  | 		const theme: Themes = this.props.theme ?? Themes.dark; | ||||||
|  | 		const focusPanel: string | undefined = props.focusPanel; | ||||||
|  | 
 | ||||||
|  | 		classList.push(theme === Themes.light ? "light" : "dark"); | ||||||
|  | 		classList.push(`background-${BackgroundLevel.Level4}`); | ||||||
|  | 		classList.push(`font-${FontLevel.normal}`); | ||||||
|  | 		classList.push("app-container"); | ||||||
|  | 		if (panles.length > 0 && !items) classList.push("end-containe"); | ||||||
|  | 
 | ||||||
|  | 		return <div | ||||||
|  | 			className={classList.join(" ")} | ||||||
|  | 			draggable={false} | ||||||
|  | 			style={{ | ||||||
|  | 				transition: "none", | ||||||
|  | 				flexDirection: layout === LayoutDirection.Y ? "column" : undefined, | ||||||
|  | 				width: isRoot ? "100%" : selfLayout === LayoutDirection.X ? `${selfScale}%` : undefined, | ||||||
|  | 				height: isRoot ? "100%" : selfLayout === LayoutDirection.Y ? `${selfScale}%` : undefined | ||||||
| 			}} | 			}} | ||||||
| 					onMouseUp={() => { this.focusEdgeId = undefined }} | 			onMouseMove={isRoot ? this.handelMouseMove : undefined} | ||||||
|  | 			onMouseUp={isRoot ? () => this.focusEdgeId = undefined : undefined} | ||||||
| 		> | 		> | ||||||
| 				</div> | 			{/* 渲染 Panel */} | ||||||
| 			</div> : null} | 			{panles.length > 0 && !items ? this.renderPanel(panles, showBar, focusPanel) : null} | ||||||
|  | 
 | ||||||
|  | 			{/* 渲染第一部分 */} | ||||||
|  | 			{items && items[0] ? this.renderContainer(items[0], scale, layout) : null} | ||||||
|  | 
 | ||||||
|  | 			{/* 渲染拖拽条 */} | ||||||
|  | 			{items && items[1] ?  | ||||||
|  | 				<div className="drag-bar" style={{ | ||||||
|  | 					width: layout === LayoutDirection.Y ? "100%" : 0, | ||||||
|  | 					height: layout === LayoutDirection.X ? "100%" : 0 | ||||||
|  | 				}}> | ||||||
|  | 					<div | ||||||
|  | 						style={{ cursor: layout === LayoutDirection.Y ? "n-resize" : "e-resize" }} | ||||||
|  | 						onMouseDown={ this.handelMouseDown.bind(this, props) } | ||||||
|  | 						onMouseUp={() => this.focusEdgeId = undefined } | ||||||
|  | 					/> | ||||||
|  | 				</div> : null | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			{/* 渲染第二部分 */} | ||||||
| 			{items && items[1] ? this.renderContainer(items[1], 100 - scale, layout) : null} | 			{items && items[1] ? this.renderContainer(items[1], 100 - scale, layout) : null} | ||||||
| 		</div> | 		</div> | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ class RootContainer extends Component<IMixinSettingProps> { | |||||||
| 		if (this.props.setting) { | 		if (this.props.setting) { | ||||||
| 			this.props.setting.layout.on("layoutChange", this.handelChange); | 			this.props.setting.layout.on("layoutChange", this.handelChange); | ||||||
| 			this.props.setting.layout.on("scaleChange", this.handelChange); | 			this.props.setting.layout.on("scaleChange", this.handelChange); | ||||||
|  | 			this.props.setting.layout.on("switchTab", this.handelChange); | ||||||
| 			this.props.setting.on("themes", this.handelChange); | 			this.props.setting.on("themes", this.handelChange); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @ -21,6 +22,7 @@ class RootContainer extends Component<IMixinSettingProps> { | |||||||
| 		if (this.props.setting) { | 		if (this.props.setting) { | ||||||
| 			this.props.setting.layout.off("layoutChange", this.handelChange); | 			this.props.setting.layout.off("layoutChange", this.handelChange); | ||||||
| 			this.props.setting.layout.off("scaleChange", this.handelChange); | 			this.props.setting.layout.off("scaleChange", this.handelChange); | ||||||
|  | 			this.props.setting.layout.off("switchTab", this.handelChange); | ||||||
| 			this.props.setting.off("themes", this.handelChange); | 			this.props.setting.off("themes", this.handelChange); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @ -28,6 +30,7 @@ class RootContainer extends Component<IMixinSettingProps> { | |||||||
| 	public render(): ReactNode { | 	public render(): ReactNode { | ||||||
| 		const layoutData = this.props.setting ? this.props.setting.layout.getData() : {}; | 		const layoutData = this.props.setting ? this.props.setting.layout.getData() : {}; | ||||||
| 		const theme = this.props.setting?.themes ?? Themes.dark; | 		const theme = this.props.setting?.themes ?? Themes.dark; | ||||||
|  | 		const focusId = this.props.setting?.layout.focusId ?? ""; | ||||||
| 		return <Container | 		return <Container | ||||||
| 			scale={layoutData.scale} | 			scale={layoutData.scale} | ||||||
| 			items={layoutData.items} | 			items={layoutData.items} | ||||||
| @ -35,7 +38,9 @@ class RootContainer extends Component<IMixinSettingProps> { | |||||||
| 			theme={theme} | 			theme={theme} | ||||||
| 			isRoot={true} | 			isRoot={true} | ||||||
| 			onScaleChange={this.props.setting?.layout.setScale} | 			onScaleChange={this.props.setting?.layout.setScale} | ||||||
|  | 			onFocusTab={this.props.setting?.layout.focus} | ||||||
| 			id={layoutData.id} | 			id={layoutData.id} | ||||||
|  | 			focusId={focusId} | ||||||
| 		/> | 		/> | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -100,6 +100,11 @@ class HeaderBar extends Component< | |||||||
|             backgroundLevel={BackgroundLevel.Level1} |             backgroundLevel={BackgroundLevel.Level1} | ||||||
|             fontLevel={FontLevel.Level3} |             fontLevel={FontLevel.Level3} | ||||||
|             style={{ height: this.props.height }} |             style={{ height: this.props.height }} | ||||||
|  |             onClick={() => { | ||||||
|  |                 if (this.props.setting) { | ||||||
|  |                     this.props.setting.layout.focus(""); | ||||||
|  |                 } | ||||||
|  |             }} | ||||||
|         > |         > | ||||||
|             <LocalizationTooltipHost i18nKey="Header.Bar.Title.Info"> |             <LocalizationTooltipHost i18nKey="Header.Bar.Title.Info"> | ||||||
|                 <div className="title"> |                 <div className="title"> | ||||||
|  | |||||||
| @ -20,6 +20,10 @@ abstract class BasicRenderer< | |||||||
|     E extends Record<EventType, any> = {} |     E extends Record<EventType, any> = {} | ||||||
| > extends AbstractRenderer<P, M & IRendererParams, E & {loop: number}> { | > extends AbstractRenderer<P, M & IRendererParams, E & {loop: number}> { | ||||||
| 
 | 
 | ||||||
|  |     public get dom() { | ||||||
|  |         return this.canvas.dom | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
| 	 * 渲染器参数 | 	 * 渲染器参数 | ||||||
| 	 */ | 	 */ | ||||||
|  | |||||||
| @ -19,5 +19,10 @@ const EN_US = { | |||||||
|     "Command.Bar.Add.Tag.Info": "Add label object", |     "Command.Bar.Add.Tag.Info": "Add label object", | ||||||
|     "Command.Bar.Camera.Info": "Renderer settings", |     "Command.Bar.Camera.Info": "Renderer settings", | ||||||
|     "Command.Bar.Setting.Info": "Global Settings", |     "Command.Bar.Setting.Info": "Global Settings", | ||||||
|  |     "Panel.Title.Notfound": "{id}", | ||||||
|  |     "Panel.Info.Notfound": "This panel with id {id} can not found!", | ||||||
|  |     "Panel.Title.Render.View": "Live preview", | ||||||
|  |     "Panel.Info.Render.View": "Live simulation results preview", | ||||||
|  | 
 | ||||||
| } | } | ||||||
| export default EN_US; | export default EN_US; | ||||||
| @ -19,5 +19,9 @@ const ZH_CN = { | |||||||
|     "Command.Bar.Add.Tag.Info": "添加标签对象", |     "Command.Bar.Add.Tag.Info": "添加标签对象", | ||||||
|     "Command.Bar.Camera.Info": "渲染器设置", |     "Command.Bar.Camera.Info": "渲染器设置", | ||||||
|     "Command.Bar.Setting.Info": "全局设置", |     "Command.Bar.Setting.Info": "全局设置", | ||||||
|  |     "Panel.Title.Notfound": "找不到面板: {id}", | ||||||
|  |     "Panel.Info.Notfound": "这个编号为 {id} 的面板无法找到!", | ||||||
|  |     "Panel.Title.Render.View": "实时预览", | ||||||
|  |     "Panel.Info.Render.View": "实时仿真结果预览", | ||||||
| } | } | ||||||
| export default ZH_CN; | export default ZH_CN; | ||||||
| @ -8,6 +8,7 @@ enum LayoutDirection { | |||||||
| class ILayout { | class ILayout { | ||||||
| 	items?: [ILayout, ILayout]; | 	items?: [ILayout, ILayout]; | ||||||
| 	panles?: string[]; | 	panles?: string[]; | ||||||
|  | 	focusPanel?: string; | ||||||
| 	layout?: LayoutDirection; | 	layout?: LayoutDirection; | ||||||
| 	scale?: number; | 	scale?: number; | ||||||
| 	id?: number; | 	id?: number; | ||||||
| @ -16,6 +17,7 @@ class ILayout { | |||||||
| interface ILayoutEvent { | interface ILayoutEvent { | ||||||
| 	layoutChange: Layout; | 	layoutChange: Layout; | ||||||
| 	scaleChange: Layout; | 	scaleChange: Layout; | ||||||
|  | 	switchTab: Layout; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class Layout extends Emitter<ILayoutEvent> { | class Layout extends Emitter<ILayoutEvent> { | ||||||
| @ -24,6 +26,11 @@ class Layout extends Emitter<ILayoutEvent> { | |||||||
| 
 | 
 | ||||||
| 	private data: ILayout = {}; | 	private data: ILayout = {}; | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  |      * 焦点面板 ID | ||||||
|  |      */ | ||||||
|  | 	public focusId: string = ""; | ||||||
|  | 
 | ||||||
| 	private map(fn: (layout: ILayout) => boolean | void, layout?: ILayout) { | 	private map(fn: (layout: ILayout) => boolean | void, layout?: ILayout) { | ||||||
| 		const currentLayout = layout ? layout : this.data; | 		const currentLayout = layout ? layout : this.data; | ||||||
| 		if( fn(currentLayout) ) return; | 		if( fn(currentLayout) ) return; | ||||||
| @ -44,6 +51,9 @@ class Layout extends Emitter<ILayoutEvent> { | |||||||
| 		this.id = 0; | 		this.id = 0; | ||||||
| 		this.map((layout) => { | 		this.map((layout) => { | ||||||
| 			layout.id = this.id; | 			layout.id = this.id; | ||||||
|  | 			if (!layout.focusPanel && layout.panles && layout.panles.length > 0) { | ||||||
|  | 				layout.focusPanel = layout.panles[0] | ||||||
|  | 			} | ||||||
| 			this.id ++; | 			this.id ++; | ||||||
| 		}); | 		}); | ||||||
| 		this.emit("layoutChange", this); | 		this.emit("layoutChange", this); | ||||||
| @ -62,6 +72,31 @@ class Layout extends Emitter<ILayoutEvent> { | |||||||
| 			this.emit("scaleChange", this); | 			this.emit("scaleChange", this); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	public focus = (panelId: string) => { | ||||||
|  | 		if (panelId === "") { | ||||||
|  | 			this.focusId = panelId; | ||||||
|  | 			this.emit("switchTab", this); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		this.map((layout) => { | ||||||
|  | 			if (layout.panles && layout.panles.length > 0) { | ||||||
|  | 				let index = -1; | ||||||
|  | 				for (let i = 0; i < layout.panles.length; i++) { | ||||||
|  | 					if (layout.panles[i] === panelId) { | ||||||
|  | 						index = i; | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				if (index >= 0) { | ||||||
|  | 					layout.focusPanel = panelId; | ||||||
|  | 					this.focusId = panelId; | ||||||
|  | 					this.emit("switchTab", this); | ||||||
|  | 					return true; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export { Layout, ILayout, LayoutDirection }; | export { Layout, ILayout, LayoutDirection }; | ||||||
| @ -67,6 +67,8 @@ abstract class AbstractRenderer< | |||||||
| 	E extends AbstractRendererEvent = {loop: number} | 	E extends AbstractRendererEvent = {loop: number} | ||||||
| > extends Emitter<E> { | > extends Emitter<E> { | ||||||
| 
 | 
 | ||||||
|  | 	abstract dom: HTMLDivElement | HTMLCanvasElement; | ||||||
|  | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * 渲染器参数 | 	 * 渲染器参数 | ||||||
| 	 */ | 	 */ | ||||||
|  | |||||||
| @ -60,7 +60,7 @@ class SimulatorWeb extends Component { | |||||||
|             items: [ |             items: [ | ||||||
|                 { |                 { | ||||||
|                     items: [ |                     items: [ | ||||||
|                         {panles: ["Label A", "Label Aa Bb", "Label aaa"]}, |                         {panles: ["RenderView", "Label Aa Bb", "Label aaa"]}, | ||||||
|                         { |                         { | ||||||
|                             items: [{panles: ["Label b", "Label bbb"]}, {panles: ["C"]}], |                             items: [{panles: ["Label b", "Label bbb"]}, {panles: ["C"]}], | ||||||
|                             scale: 80, |                             scale: 80, | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								source/Panel/Panel.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								source/Panel/Panel.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | import { ReactNode, Component, FunctionComponent } from "react"; | ||||||
|  | import { Theme } from "@Component/Theme/Theme"; | ||||||
|  | import { Localization } from "@Component/Localization/Localization"; | ||||||
|  | import { RenderView } from "./RenderView/RenderView"; | ||||||
|  | 
 | ||||||
|  | interface IPanelInfo { | ||||||
|  | 	nameKey: string; | ||||||
|  | 	introKay: string; | ||||||
|  | 	class: (new (...p: any) => Component) | FunctionComponent; | ||||||
|  | 	hidePadding?: boolean; | ||||||
|  | 	hideScrollBar?: boolean; | ||||||
|  | 	option?: Record<string, string>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type PanelId = "" | ||||||
|  | | "RenderView" // 主渲染器
 | ||||||
|  | ; | ||||||
|  | 
 | ||||||
|  | const PanelInfoMap = new Map<PanelId, IPanelInfo>(); | ||||||
|  | PanelInfoMap.set("RenderView", {  | ||||||
|  | 	nameKey: "Panel.Title.Render.View", introKay: "Panel.Info.Render.View", class: RenderView, hidePadding: true, hideScrollBar: true | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function getPanelById(panelId: PanelId): ReactNode { | ||||||
|  | 	switch (panelId) { | ||||||
|  | 		default:  | ||||||
|  | 			let info = PanelInfoMap.get(panelId); | ||||||
|  | 			if (info) { | ||||||
|  | 				const C = info.class; | ||||||
|  | 				return <C></C> | ||||||
|  | 			} else return <Theme> | ||||||
|  | 				<Localization i18nKey={"Panel.Info.Notfound"} options={{ | ||||||
|  | 					id: panelId | ||||||
|  | 				}}/> | ||||||
|  | 			</Theme> | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getPanelInfoById(panelId: PanelId): IPanelInfo | undefined { | ||||||
|  | 	return PanelInfoMap.get(panelId); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { PanelId, getPanelById, getPanelInfoById} | ||||||
							
								
								
									
										9
									
								
								source/Panel/RenderView/RenderView.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								source/Panel/RenderView/RenderView.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | div.render-view { | ||||||
|  | 	width: 100%; | ||||||
|  | 	height: 100%; | ||||||
|  | 
 | ||||||
|  | 	div.canvas { | ||||||
|  | 		width: 100%; | ||||||
|  | 		height: 100%; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								source/Panel/RenderView/RenderView.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								source/Panel/RenderView/RenderView.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | import { Component, ReactNode, createRef } from "react"; | ||||||
|  | import { useStatus, IMixinStatusProps } from "@Context/Status"; | ||||||
|  | import { useSetting, IMixinSettingProps, Themes } from "@Context/Setting"; | ||||||
|  | import { ClassicRenderer } from "@GLRender/ClassicRenderer"; | ||||||
|  | import "./RenderView.scss"; | ||||||
|  | 
 | ||||||
|  | @useSetting | ||||||
|  | @useStatus | ||||||
|  | class RenderView extends Component<IMixinStatusProps & IMixinSettingProps> { | ||||||
|  | 
 | ||||||
|  | 	private rootEle = createRef<HTMLDivElement>(); | ||||||
|  | 
 | ||||||
|  | 	public render(): ReactNode { | ||||||
|  | 		const theme = this.props.setting?.themes ?? Themes.dark; | ||||||
|  | 		const classList: string[] = ["render-view", "background-lvl5"]; | ||||||
|  | 		if (theme === Themes.light) classList.push("light"); | ||||||
|  | 		if (theme === Themes.dark) classList.push("dark"); | ||||||
|  | 
 | ||||||
|  | 		if (this.props.status) { | ||||||
|  | 			(this.props.status.renderer as ClassicRenderer).cleanColor =  | ||||||
|  | 			(theme === Themes.dark) ? | ||||||
|  | 				[27 / 255, 26 / 255, 25 / 255, 1] : | ||||||
|  | 				[190 / 255, 187 / 255, 184 / 255, 1] | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return <div ref={this.rootEle} className={classList.join(" ")}/>; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public componentDidMount() { | ||||||
|  | 		let div = this.rootEle.current; | ||||||
|  | 		console.log(div, div?.childNodes, this.props.status, this.props.status?.renderer.dom) | ||||||
|  | 		if (div && (!div.childNodes || div.childNodes.length <= 0) && this.props.status) {	 | ||||||
|  | 			div.appendChild(this.props.status.renderer.dom); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { RenderView } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user