mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
drag and drop support for tabs (#146)
* back-end implementation. initial ui implementation * rough implementation of drag and drop * wrap with AnimatePresence * persist screen order * cleanup * return all screens when updating indices * remove y axis anitation * remove debugging code * chain filtering and sorting logic * remove unused var * fix issue where tabs shift to left on hover * fix tabElem scroll into view regression * clear scrollIntoViewTimeout when component unmounts * remove borken style prop * completely remove animation related props * scroll into view only when there's a new tab * minor comment fix * resolvePosInt() to resolve index * move tab width const to magiclayout.ts * move back scroll into view code as it was before * clear timout * refactor setTimeout * remov debugging codes * format cmdrunner.go * move clearTimeout * remove wheel event listener
This commit is contained in:
parent
8200a312b9
commit
ea9cb37de1
@ -16,6 +16,7 @@
|
|||||||
"dayjs": "^1.11.3",
|
"dayjs": "^1.11.3",
|
||||||
"dompurify": "^3.0.2",
|
"dompurify": "^3.0.2",
|
||||||
"electron-squirrel-startup": "^1.0.0",
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
|
"framer-motion": "^10.16.16",
|
||||||
"mobx": "6.12",
|
"mobx": "6.12",
|
||||||
"mobx-react": "^7.5.0",
|
"mobx-react": "^7.5.0",
|
||||||
"monaco-editor": "^0.44.0",
|
"monaco-editor": "^0.44.0",
|
||||||
|
@ -7,11 +7,11 @@ let MagicLayout = {
|
|||||||
CmdInputHeight: 101, // height of full cmd-input div
|
CmdInputHeight: 101, // height of full cmd-input div
|
||||||
CmdInputBottom: 12, // .cmd-input
|
CmdInputBottom: 12, // .cmd-input
|
||||||
|
|
||||||
LineHeaderHeight: 46, // .line-header
|
LineHeaderHeight: 46, // .line-header
|
||||||
LinePadding: 24, // .line-header (12px * 2)
|
LinePadding: 24, // .line-header (12px * 2)
|
||||||
WindowHeightOffset: 6, // .window-view, height is calc(100%-0.5rem)
|
WindowHeightOffset: 6, // .window-view, height is calc(100%-0.5rem)
|
||||||
LinesBottomPadding: 10, // .lines, padding
|
LinesBottomPadding: 10, // .lines, padding
|
||||||
LineMarginTop: 12, // .line, margin
|
LineMarginTop: 12, // .line, margin
|
||||||
|
|
||||||
ScreenMaxContentWidthBuffer: 50,
|
ScreenMaxContentWidthBuffer: 50,
|
||||||
ScreenMaxContentHeightBuffer: 0, // calc below
|
ScreenMaxContentHeightBuffer: 0, // calc below
|
||||||
@ -21,12 +21,15 @@ let MagicLayout = {
|
|||||||
// the 3 is for descenders, which get cut off in the terminal without this
|
// the 3 is for descenders, which get cut off in the terminal without this
|
||||||
TermDescendersHeight: 3,
|
TermDescendersHeight: 3,
|
||||||
TermWidthBuffer: 15,
|
TermWidthBuffer: 15,
|
||||||
|
|
||||||
|
TabWidth: 175,
|
||||||
};
|
};
|
||||||
|
|
||||||
let m = MagicLayout;
|
let m = MagicLayout;
|
||||||
|
|
||||||
// add up all the line overhead + padding. subtract 2 so we don't see the border of neighboring line
|
// add up all the line overhead + padding. subtract 2 so we don't see the border of neighboring line
|
||||||
m.ScreenMaxContentHeightBuffer = m.LineHeaderHeight + m.LinePadding + m.WindowHeightOffset + m.LinesBottomPadding + m.LineMarginTop - 2;
|
m.ScreenMaxContentHeightBuffer =
|
||||||
|
m.LineHeaderHeight + m.LinePadding + m.WindowHeightOffset + m.LinesBottomPadding + m.LineMarginTop - 2;
|
||||||
|
|
||||||
(window as any).MagicLayout = MagicLayout;
|
(window as any).MagicLayout = MagicLayout;
|
||||||
|
|
||||||
|
@ -220,96 +220,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.screen-tabs {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
overflow-x: hidden;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.screen-tab {
|
|
||||||
display: flex;
|
|
||||||
height: 3em;
|
|
||||||
min-width: 14em;
|
|
||||||
max-width: 14em;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.icon.svg-icon {
|
|
||||||
margin: 0 12px;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.fa-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-name {
|
|
||||||
width: 8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
border-top: none;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-archived {
|
|
||||||
.fa.fa-archive {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-index,
|
|
||||||
.tab-gear {
|
|
||||||
display: none;
|
|
||||||
.icon {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.tab-gear {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .screen-tab .tab-index {
|
|
||||||
display: block;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .screen-tab:hover .tab-index {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-screen {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: 1em;
|
|
||||||
margin-right: 1em;
|
|
||||||
cursor: pointer;
|
|
||||||
.icon {
|
|
||||||
height: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
padding: 0.4em;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.screen-tabs-container {
|
.screen-tabs-container {
|
||||||
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
@ -325,4 +239,95 @@
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.screen-tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
overflow-x: overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-tab {
|
||||||
|
display: flex;
|
||||||
|
height: 3em;
|
||||||
|
min-width: 14em;
|
||||||
|
max-width: 14em;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.icon.svg-icon {
|
||||||
|
margin: 0 12px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.fa-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-name {
|
||||||
|
width: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
border-top: none;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-archived {
|
||||||
|
.fa.fa-archive {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-index,
|
||||||
|
.tab-gear {
|
||||||
|
display: none;
|
||||||
|
.icon {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.tab-gear {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .screen-tab .tab-index {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .screen-tab:hover .tab-index {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-screen {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0.4em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import { sprintf } from "sprintf-js";
|
|||||||
import { boundMethod } from "autobind-decorator";
|
import { boundMethod } from "autobind-decorator";
|
||||||
import { For } from "tsx-control-statements/components";
|
import { For } from "tsx-control-statements/components";
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import { debounce } from "throttle-debounce";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||||
import { GlobalModel, GlobalCommandRunner, Session, Screen } from "../../../model/model";
|
import { GlobalModel, GlobalCommandRunner, Session, Screen } from "../../../model/model";
|
||||||
@ -17,30 +16,50 @@ import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg"
|
|||||||
import { ReactComponent as ActionsIcon } from "../../assets/icons/tab/actions.svg";
|
import { ReactComponent as ActionsIcon } from "../../assets/icons/tab/actions.svg";
|
||||||
import { ReactComponent as AddIcon } from "../../assets/icons/add.svg";
|
import { ReactComponent as AddIcon } from "../../assets/icons/add.svg";
|
||||||
import * as constants from "../../appconst";
|
import * as constants from "../../appconst";
|
||||||
|
import { Reorder } from "framer-motion";
|
||||||
|
import { MagicLayout } from "../../magiclayout";
|
||||||
|
|
||||||
import "../workspace.less";
|
import "../workspace.less";
|
||||||
import "./tabs.less";
|
import "./tabs.less";
|
||||||
|
|
||||||
dayjs.extend(localizedFormat);
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
type OV<V> = mobx.IObservableValue<V>;
|
|
||||||
|
|
||||||
@mobxReact.observer
|
@mobxReact.observer
|
||||||
class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
class ScreenTabs extends React.Component<{ session: Session }, { showingScreens: Screen[] }> {
|
||||||
tabsRef: React.RefObject<any> = React.createRef();
|
tabsRef: React.RefObject<any> = React.createRef();
|
||||||
|
tabRefs: { [screenId: string]: React.RefObject<any> } = {};
|
||||||
lastActiveScreenId: string = null;
|
lastActiveScreenId: string = null;
|
||||||
scrolling: OV<boolean> = mobx.observable.box(false, { name: "screentabs-scrolling" });
|
dragEndTimeout = null;
|
||||||
|
scrollIntoViewTimeout = null;
|
||||||
stopScrolling_debounced: () => void;
|
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
this.stopScrolling_debounced = debounce(1500, this.stopScrolling.bind(this));
|
this.state = {
|
||||||
|
showingScreens: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@mobx.computed
|
||||||
|
get activeScreenId(): string {
|
||||||
|
let { session } = this.props;
|
||||||
|
if (session) {
|
||||||
|
return session.activeScreenId.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mobx.computed
|
||||||
|
get screens(): Screen[] {
|
||||||
|
if (this.activeScreenId) {
|
||||||
|
let screens = GlobalModel.getSessionScreens(this.props.session.sessionId);
|
||||||
|
let showingScreens = screens
|
||||||
|
.filter((screen) => !screen.archived.get() || this.activeScreenId === screen.screenId)
|
||||||
|
.sort((a, b) => a.screenIdx.get() - b.screenIdx.get());
|
||||||
|
return showingScreens;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
handleNewScreen() {
|
handleNewScreen() {
|
||||||
let { session } = this.props;
|
|
||||||
GlobalCommandRunner.createNewScreen();
|
GlobalCommandRunner.createNewScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,38 +79,77 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
|||||||
GlobalCommandRunner.switchScreen(screenId);
|
GlobalCommandRunner.switchScreen(screenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @boundMethod
|
||||||
|
// handleWheel(event: WheelEvent) {
|
||||||
|
// if (!this.tabsRef.current) return;
|
||||||
|
|
||||||
|
// // Prevent the default vertical scrolling
|
||||||
|
// event.preventDefault();
|
||||||
|
|
||||||
|
// // Scroll horizontally instead
|
||||||
|
// this.tabsRef.current.scrollLeft += event.deltaY;
|
||||||
|
// }
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
this.componentDidUpdate();
|
this.componentDidUpdate();
|
||||||
|
|
||||||
|
// // Add the wheel event listener to the tabsRef
|
||||||
|
// if (this.tabsRef.current) {
|
||||||
|
// this.tabsRef.current.addEventListener("wheel", this.handleWheel, { passive: false });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.dragEndTimeout) {
|
||||||
|
clearTimeout(this.dragEndTimeout);
|
||||||
|
clearTimeout(this.scrollIntoViewTimeout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(): void {
|
componentDidUpdate(): void {
|
||||||
|
// Scroll the active screen into view
|
||||||
let { session } = this.props;
|
let { session } = this.props;
|
||||||
let activeScreenId = session.activeScreenId.get();
|
let activeScreenId = session.activeScreenId.get();
|
||||||
if (activeScreenId != this.lastActiveScreenId && this.tabsRef.current) {
|
if (activeScreenId !== this.lastActiveScreenId) {
|
||||||
let tabElem = this.tabsRef.current.querySelector(
|
if (this.scrollIntoViewTimeout) {
|
||||||
sprintf('.screen-tab[data-screenid="%s"]', activeScreenId)
|
clearTimeout(this.scrollIntoViewTimeout);
|
||||||
);
|
|
||||||
if (tabElem != null) {
|
|
||||||
tabElem.scrollIntoView();
|
|
||||||
}
|
}
|
||||||
|
this.scrollIntoViewTimeout = setTimeout(() => {
|
||||||
|
if (this.tabsRef.current) {
|
||||||
|
let tabElem = this.tabsRef.current.querySelector(
|
||||||
|
sprintf('.screen-tab[data-screenid="%s"]', activeScreenId)
|
||||||
|
);
|
||||||
|
if (tabElem) {
|
||||||
|
tabElem.scrollIntoView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.lastActiveScreenId = activeScreenId;
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
this.lastActiveScreenId = activeScreenId;
|
|
||||||
}
|
|
||||||
|
|
||||||
stopScrolling(): void {
|
// Set the showingScreens state if it's not set or if the number of screens has changed.
|
||||||
mobx.action(() => {
|
// Individual screen update are handled automatically by mobx.
|
||||||
this.scrolling.set(false);
|
if (this.screens && this.state.showingScreens.length !== this.screens.length) {
|
||||||
})();
|
this.setState({ showingScreens: this.screens });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
handleScroll() {
|
handleDragEnd(screenId) {
|
||||||
if (!this.scrolling.get()) {
|
if (this.dragEndTimeout) {
|
||||||
mobx.action(() => {
|
clearTimeout(this.dragEndTimeout);
|
||||||
this.scrolling.set(true);
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
this.stopScrolling_debounced();
|
|
||||||
|
// Wait for the animation to complete
|
||||||
|
this.dragEndTimeout = setTimeout(() => {
|
||||||
|
const tabElement = this.tabRefs[screenId].current;
|
||||||
|
const finalTabPosition = tabElement.offsetLeft;
|
||||||
|
|
||||||
|
// Calculate the new index based on the final position
|
||||||
|
const newIndex = Math.floor(finalTabPosition / MagicLayout.TabWidth);
|
||||||
|
|
||||||
|
GlobalCommandRunner.screenReorder(screenId, `${newIndex + 1}`);
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
@ -138,17 +196,28 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
|||||||
<i title="shared to web" className="fa-sharp fa-solid fa-share-nodes web-share-icon" />
|
<i title="shared to web" className="fa-sharp fa-solid fa-share-nodes web-share-icon" />
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
// Create a ref for the tab if it doesn't exist
|
||||||
|
if (!this.tabRefs[screen.screenId]) {
|
||||||
|
this.tabRefs[screen.screenId] = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Reorder.Item
|
||||||
key={screen.screenId}
|
ref={this.tabRefs[screen.screenId]}
|
||||||
|
value={screen}
|
||||||
|
id={screen.name.get()}
|
||||||
|
whileDrag={{
|
||||||
|
backgroundColor: "rgba(13, 13, 13, 0.85)",
|
||||||
|
}}
|
||||||
data-screenid={screen.screenId}
|
data-screenid={screen.screenId}
|
||||||
className={cn(
|
className={cn(
|
||||||
"screen-tab",
|
"screen-tab",
|
||||||
{ "is-active": activeScreenId == screen.screenId, "is-archived": screen.archived.get() },
|
{ "is-active": activeScreenId == screen.screenId, "is-archived": screen.archived.get() },
|
||||||
"color-" + screen.getTabColor()
|
"color-" + screen.getTabColor()
|
||||||
)}
|
)}
|
||||||
onClick={() => this.handleSwitchScreen(screen.screenId)}
|
onPointerDown={() => this.handleSwitchScreen(screen.screenId)}
|
||||||
onContextMenu={(event) => this.openScreenSettings(event, screen)}
|
onContextMenu={(event) => this.openScreenSettings(event, screen)}
|
||||||
|
onDragEnd={() => this.handleDragEnd(screen.screenId)}
|
||||||
>
|
>
|
||||||
{this.renderTabIcon(screen)}
|
{this.renderTabIcon(screen)}
|
||||||
<div className="tab-name truncate">
|
<div className="tab-name truncate">
|
||||||
@ -158,49 +227,39 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
|||||||
</div>
|
</div>
|
||||||
{tabIndex}
|
{tabIndex}
|
||||||
{settings}
|
{settings}
|
||||||
</div>
|
</Reorder.Item>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let { showingScreens } = this.state;
|
||||||
let { session } = this.props;
|
let { session } = this.props;
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let screen: Screen | null = null;
|
let screen: Screen | null = null;
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let showingScreens = [];
|
|
||||||
let activeScreenId = session.activeScreenId.get();
|
let activeScreenId = session.activeScreenId.get();
|
||||||
let screens = GlobalModel.getSessionScreens(session.sessionId);
|
|
||||||
for (let screen of screens) {
|
|
||||||
if (!screen.archived.get() || activeScreenId == screen.screenId) {
|
|
||||||
showingScreens.push(screen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showingScreens.sort((a, b) => {
|
|
||||||
let aidx = a.screenIdx.get();
|
|
||||||
let bidx = b.screenIdx.get();
|
|
||||||
if (aidx < bidx) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (aidx > bidx) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<div className="screen-tabs-container">
|
<div className="screen-tabs-container">
|
||||||
<div
|
<Reorder.Group
|
||||||
className={cn("screen-tabs", { scrolling: this.scrolling.get() })}
|
className="screen-tabs"
|
||||||
ref={this.tabsRef}
|
ref={this.tabsRef}
|
||||||
onScroll={this.handleScroll}
|
as="ul"
|
||||||
|
axis="x"
|
||||||
|
onReorder={(tabs: Screen[]) => {
|
||||||
|
this.setState({ showingScreens: tabs });
|
||||||
|
}}
|
||||||
|
values={showingScreens}
|
||||||
>
|
>
|
||||||
<For each="screen" index="index" of={showingScreens}>
|
<For each="screen" index="index" of={showingScreens}>
|
||||||
{this.renderTab(screen, activeScreenId, index)}
|
<React.Fragment key={screen.screenId}>
|
||||||
|
{this.renderTab(screen, activeScreenId, index)}
|
||||||
|
</React.Fragment>
|
||||||
</For>
|
</For>
|
||||||
<div key="new-screen" className="new-screen" onClick={this.handleNewScreen}>
|
</Reorder.Group>
|
||||||
<AddIcon className="icon hoverEffect" />
|
<div key="new-screen" className="new-screen" onClick={this.handleNewScreen}>
|
||||||
</div>
|
<AddIcon className="icon hoverEffect" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4196,6 +4196,15 @@ class CommandRunner {
|
|||||||
GlobalModel.submitCommand("screen", "set", null, kwargs, false);
|
GlobalModel.submitCommand("screen", "set", null, kwargs, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
screenReorder(screenId: string, index: string) {
|
||||||
|
let kwargs: Record<string, string> = {
|
||||||
|
nohist: "1",
|
||||||
|
screenId: screenId,
|
||||||
|
index: index,
|
||||||
|
};
|
||||||
|
GlobalModel.submitCommand("screen", "reorder", null, kwargs, false);
|
||||||
|
}
|
||||||
|
|
||||||
setTermUsedRows(termContext: RendererContext, height: number) {
|
setTermUsedRows(termContext: RendererContext, height: number) {
|
||||||
let kwargs: Record<string, string> = {};
|
let kwargs: Record<string, string> = {};
|
||||||
kwargs["screen"] = termContext.screenId;
|
kwargs["screen"] = termContext.screenId;
|
||||||
|
@ -90,13 +90,14 @@ var SetVarNameMap map[string]string = map[string]string{
|
|||||||
"anchor": "screen.anchor",
|
"anchor": "screen.anchor",
|
||||||
"focus": "screen.focus",
|
"focus": "screen.focus",
|
||||||
"line": "screen.line",
|
"line": "screen.line",
|
||||||
|
"index": "screen.index",
|
||||||
}
|
}
|
||||||
|
|
||||||
var SetVarScopes = []SetVarScope{
|
var SetVarScopes = []SetVarScope{
|
||||||
{ScopeName: "global", VarNames: []string{}},
|
{ScopeName: "global", VarNames: []string{}},
|
||||||
{ScopeName: "client", VarNames: []string{"telemetry"}},
|
{ScopeName: "client", VarNames: []string{"telemetry"}},
|
||||||
{ScopeName: "session", VarNames: []string{"name", "pos"}},
|
{ScopeName: "session", VarNames: []string{"name", "pos"}},
|
||||||
{ScopeName: "screen", VarNames: []string{"name", "tabcolor", "tabicon", "pos", "pterm", "anchor", "focus", "line"}},
|
{ScopeName: "screen", VarNames: []string{"name", "tabcolor", "tabicon", "pos", "pterm", "anchor", "focus", "line", "index"}},
|
||||||
{ScopeName: "line", VarNames: []string{}},
|
{ScopeName: "line", VarNames: []string{}},
|
||||||
// connection = remote, remote = remoteinstance
|
// connection = remote, remote = remoteinstance
|
||||||
{ScopeName: "connection", VarNames: []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}},
|
{ScopeName: "connection", VarNames: []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}},
|
||||||
@ -167,6 +168,7 @@ func init() {
|
|||||||
registerCmdFn("screen:showall", ScreenShowAllCommand)
|
registerCmdFn("screen:showall", ScreenShowAllCommand)
|
||||||
registerCmdFn("screen:reset", ScreenResetCommand)
|
registerCmdFn("screen:reset", ScreenResetCommand)
|
||||||
registerCmdFn("screen:webshare", ScreenWebShareCommand)
|
registerCmdFn("screen:webshare", ScreenWebShareCommand)
|
||||||
|
registerCmdFn("screen:reorder", ScreenReorderCommand)
|
||||||
|
|
||||||
registerCmdAlias("remote", RemoteCommand)
|
registerCmdAlias("remote", RemoteCommand)
|
||||||
registerCmdFn("remote:show", RemoteShowCommand)
|
registerCmdFn("remote:show", RemoteShowCommand)
|
||||||
@ -723,6 +725,45 @@ func ScreenOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
|
|||||||
return update, nil
|
return update, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ScreenReorderCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||||
|
// Resolve the UI IDs for the session and screen
|
||||||
|
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the screen ID and the new index from the packet
|
||||||
|
screenId := ids.ScreenId
|
||||||
|
newScreenIdxStr := pk.Kwargs["index"]
|
||||||
|
newScreenIdx, err := resolvePosInt(newScreenIdxStr, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid new screen index: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call SetScreenIdx to update the screen's index in the database
|
||||||
|
err = sstore.SetScreenIdx(ctx, ids.SessionId, screenId, newScreenIdx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error updating screen index: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve all session screens
|
||||||
|
screens, err := sstore.GetSessionScreens(ctx, ids.SessionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error retrieving updated screen: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the update packet to send back to the client
|
||||||
|
update := &sstore.ModelUpdate{
|
||||||
|
Screens: screens,
|
||||||
|
Info: &sstore.InfoMsgType{
|
||||||
|
InfoMsg: "screen indices updated successfully",
|
||||||
|
TimeoutMs: 2000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen)
|
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
23
yarn.lock
23
yarn.lock
@ -1699,6 +1699,18 @@
|
|||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
plist "^3.0.4"
|
plist "^3.0.4"
|
||||||
|
|
||||||
|
"@emotion/is-prop-valid@^0.8.2":
|
||||||
|
version "0.8.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
|
||||||
|
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
|
||||||
|
dependencies:
|
||||||
|
"@emotion/memoize" "0.7.4"
|
||||||
|
|
||||||
|
"@emotion/memoize@0.7.4":
|
||||||
|
version "0.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
|
||||||
|
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
|
||||||
|
|
||||||
"@gar/promisify@^1.1.3":
|
"@gar/promisify@^1.1.3":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||||
@ -4225,6 +4237,15 @@ forwarded@0.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||||
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
|
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
|
||||||
|
|
||||||
|
framer-motion@^10.16.16:
|
||||||
|
version "10.16.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-10.16.16.tgz#a10a03e1190a717109163cfff212a84c8ad11b0c"
|
||||||
|
integrity sha512-je6j91rd7NmUX7L1XHouwJ4v3R+SO4umso2LUcgOct3rHZ0PajZ80ETYZTajzEXEl9DlKyzjyt4AvGQ+lrebOw==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.4.0"
|
||||||
|
optionalDependencies:
|
||||||
|
"@emotion/is-prop-valid" "^0.8.2"
|
||||||
|
|
||||||
fresh@0.5.2:
|
fresh@0.5.2:
|
||||||
version "0.5.2"
|
version "0.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||||
@ -7891,7 +7912,7 @@ trough@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876"
|
resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876"
|
||||||
integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==
|
integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==
|
||||||
|
|
||||||
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0:
|
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0:
|
||||||
version "2.6.2"
|
version "2.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
||||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||||
|
Loading…
Reference in New Issue
Block a user