Skip to content

Commit

Permalink
prototype of smart layout
Browse files Browse the repository at this point in the history
  • Loading branch information
loveluthien committed Nov 12, 2024
1 parent 246c2c3 commit 9989f60
Show file tree
Hide file tree
Showing 14 changed files with 482 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
.#{$ns}-dialog-footer {
margin-top: 0;
margin-right: 20px;
.footer-sub {
display: flex;
}
}
}

Expand Down
38 changes: 32 additions & 6 deletions src/components/Dialogs/FileBrowser/FileBrowserDialogComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import {Alert, AnchorButton, Breadcrumb, BreadcrumbProps, Breadcrumbs, Button, ButtonGroup, Classes, DialogProps, Icon, InputGroup, Intent, Menu, MenuItem, Popover, Position, TabId, Tooltip} from "@blueprintjs/core";
import {Alert, AnchorButton, Breadcrumb, BreadcrumbProps, Breadcrumbs, Button, ButtonGroup, Classes, DialogProps, FormGroup, Icon, InputGroup, Intent, Menu, MenuItem, Popover, Position, Switch, TabId, Tooltip} from "@blueprintjs/core";
import {CARTA} from "carta-protobuf";
import classNames from "classnames";
import * as _ from "lodash";
Expand All @@ -9,8 +9,8 @@ import {observer} from "mobx-react";
import {DraggableDialogComponent, TaskProgressDialogComponent} from "components/Dialogs";
import {FileInfoComponent, FileInfoType} from "components/FileInfo/FileInfoComponent";
import {AppToaster, ErrorToast, SimpleTableComponentProps} from "components/Shared";
import {ImageType} from "models";
import {AppStore, BrowserMode, CatalogProfileStore, DialogId, FileBrowserStore, FileFilteringType, HelpType, ISelectedFile, PreferenceKeys, PreferenceStore} from "stores";
import {ImageType, PresetLayout} from "models";
import {AlertStore, AppStore, BrowserMode, CatalogProfileStore, DialogId, FileBrowserStore, FileFilteringType, HelpType, ISelectedFile, LayoutStore, PreferenceKeys, PreferenceStore} from "stores";
import {FrameStore} from "stores/Frame";

import {FileListTableComponent} from "./FileListTable/FileListTableComponent";
Expand Down Expand Up @@ -43,8 +43,8 @@ export class FileBrowserDialogComponent extends React.Component {
FileBrowserStore.Instance.setSelectedTab(newId);
};

@action private handleFileClicked = (file: ISelectedFile) => {
FileBrowserStore.Instance.selectFile(file);
@action private handleFileClicked = async (file: ISelectedFile) => {
await FileBrowserStore.Instance.selectFile(file);
if (this.enableImageArithmetic) {
// Check if the existing string has a trailing quote or not
const quoteRegex = /(["'])+/gm;
Expand All @@ -66,6 +66,8 @@ export class FileBrowserDialogComponent extends React.Component {
}
this.imageArithmeticInputRef.current?.focus();
}

LayoutStore.Instance.matchLayoutMap();
};

private loadWithColorBlending = async () => {
Expand Down Expand Up @@ -140,6 +142,9 @@ export class FileBrowserDialogComponent extends React.Component {
const fileBrowserStore = appStore.fileBrowserStore;
let frame: FrameStore;

const layoutStore = LayoutStore.Instance;
const preferenceStore = PreferenceStore.Instance;

// Ignore load
switch (fileBrowserStore.browserMode) {
case BrowserMode.RegionExport:
Expand All @@ -152,6 +157,17 @@ export class FileBrowserDialogComponent extends React.Component {
if (fileBrowserStore.browserMode === BrowserMode.File) {
const frames = appStore.frames;
if (!(forceAppend || fileBrowserStore.appendingFrame) || !frames.length) {
if (layoutStore.isSmartLayout && layoutStore.currentLayoutMapIndex !== null && layoutStore.layoutExists(layoutStore.smartLayoutName)) {
// should show some warning if the smart layout is not applied
layoutStore.applyLayout(layoutStore.smartLayoutName);
} else {
if (!layoutStore.applyLayout(preferenceStore.layout)) {
AlertStore.Instance.showAlert(`Applying preference layout "${preferenceStore.layout}" failed! Resetting preference layout to default.`);
layoutStore.applyLayout(PresetLayout.DEFAULT);
preferenceStore.setPreference(PreferenceKeys.GLOBAL_LAYOUT, PresetLayout.DEFAULT);
}
}

frame = yield appStore.openFile(fileBrowserStore.fileList.directory, file.fileInfo.name, file.hdu);
} else {
frame = yield appStore.appendFile(fileBrowserStore.fileList.directory, file.fileInfo.name, file.hdu);
Expand Down Expand Up @@ -272,6 +288,11 @@ export class FileBrowserDialogComponent extends React.Component {
fileBrowserStore.resetLoadingStates();
};

private handleSmartLayout = () => {
LayoutStore.Instance.toogleSmartLayout();
PreferenceStore.Instance.setPreference(PreferenceKeys.GLOBAL_IS_SMART_LAYOUT, LayoutStore.Instance.isSmartLayout);
};

@action handleFilterStringInputChanged = (ev: React.ChangeEvent<HTMLInputElement>) => {
this.fileFilterString = ev.target.value;
this.setFilterString(this.fileFilterString);
Expand Down Expand Up @@ -396,7 +417,12 @@ export class FileBrowserDialogComponent extends React.Component {
);
} else {
return (
<div>
<div className="footer-sub">
<FormGroup inline={true} label="Smart Layout">
<Tooltip content={"Apply data type associated layout"}>
<Switch checked={LayoutStore.Instance.isSmartLayout} onChange={() => this.handleSmartLayout()} />
</Tooltip>
</FormGroup>
<Tooltip content={"Close any existing images and load this image"}>
<AnchorButton intent={Intent.PRIMARY} disabled={actionDisabled} onClick={actionFunction} text={actionText} />
</Tooltip>
Expand Down
127 changes: 117 additions & 10 deletions src/components/Dialogs/LayoutDialog/SaveLayoutDialogComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as React from "react";
import {AnchorButton, Classes, DialogProps, FormGroup, InputGroup, Intent, Position, Tooltip} from "@blueprintjs/core";
import {AnchorButton, Classes, DialogProps, FormGroup, HTMLSelect, InputGroup, Intent, Label, Position, Tooltip} from "@blueprintjs/core";
import classNames from "classnames";
import {computed, makeObservable, observable} from "mobx";
import {observer} from "mobx-react";

import {DraggableDialogComponent} from "components/Dialogs";
import {PresetLayout} from "models";
import {AppStore, DialogId, HelpType} from "stores";
import {AppStore, DialogId, HelpType, LayoutDialogMode} from "stores";

import "./SaveLayoutDialogComponent.scss";

Expand All @@ -26,6 +26,12 @@ export class SaveLayoutDialogComponent extends React.Component {
makeObservable(this);
}

private titleMap = new Map<LayoutDialogMode, string>([
[LayoutDialogMode.Save, "Save Layout"],
[LayoutDialogMode.Rename, "Rename Layout"],
[LayoutDialogMode.SmartLayout, "Smart Layout"]
]);

private handleInput = (ev: React.FormEvent<HTMLInputElement>) => {
this.layoutName = ev.currentTarget.value;
};
Expand Down Expand Up @@ -66,6 +72,17 @@ export class SaveLayoutDialogComponent extends React.Component {
this.clearInput();
};

private saveLayoutMap = async () => {
// keep layoutName before it is cleared in saveLayout()
const layoutName = this.layoutName.trim();

await this.saveLayout();

const appStore = AppStore.Instance;
appStore.dialogStore.hideDialog(DialogId.Layout);
await appStore.layoutStore.saveLayoutMap(layoutName);
};

@computed get isEmpty(): boolean {
return !this.layoutName?.trim();
}
Expand All @@ -74,10 +91,92 @@ export class SaveLayoutDialogComponent extends React.Component {
return this.layoutName.match(/^[^~`!*()\-+=[.'?<>/|\\:;&]+$/)?.length > 0;
}

private renderLayoutDialogBody = (mode: LayoutDialogMode) => {
const layoutStore = AppStore.Instance.layoutStore;

switch (mode) {
case LayoutDialogMode.Save:
return (
<div className={Classes.DIALOG_BODY}>
<FormGroup inline={true} label="Save current layout as:">
<Tooltip isOpen={!this.isEmpty && !this.validName} position={Position.BOTTOM_LEFT} content={"Layout name should not contain ~, `, !, *, (, ), -, +, =, [, ., ', ?, <, >, /, |, \\, :, ; or &"}>
<InputGroup className="layout-name-input" placeholder="Enter layout name" value={this.layoutName} autoFocus={true} onChange={this.handleInput} onKeyDown={this.handleKeyDown} />
</Tooltip>
</FormGroup>
</div>
);
case LayoutDialogMode.Rename:
return (
<div className={Classes.DIALOG_BODY}>
<FormGroup inline={true} label={`Rename ${layoutStore.oldLayoutName} to:`}>
<Tooltip isOpen={!this.isEmpty && !this.validName} position={Position.BOTTOM_LEFT} content={"Layout name should not contain ~, `, !, *, (, ), -, +, =, [, ., ', ?, <, >, /, |, \\, :, ; or &"}>
<InputGroup className="layout-name-input" placeholder="Enter layout name" value={this.layoutName} autoFocus={true} onChange={this.handleInput} onKeyDown={this.handleKeyDown} />
</Tooltip>
</FormGroup>
</div>
);
case LayoutDialogMode.SmartLayout:
return (
<div className={Classes.DIALOG_BODY}>
<Label>{`Associate data type (${layoutStore.currentLayoutMapCtype})`}</Label>
</div>
);
default:
return "";
}
};

private renderLayoutDialogFooter = (mode: LayoutDialogMode) => {
const layoutStore = AppStore.Instance.layoutStore;

switch (mode) {
case LayoutDialogMode.Save:
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Tooltip content="Save as smart layout" disabled={!this.isEmpty}>
<AnchorButton intent={Intent.PRIMARY} onClick={this.saveLayoutMap} text={"Smart Layout"} disabled={this.isEmpty || !this.validName || layoutStore.currentLayoutMapCtype.length === 0} />
</Tooltip>
<Tooltip content="Layout name cannot be empty!" disabled={!this.isEmpty}>
<AnchorButton intent={Intent.PRIMARY} onClick={this.saveLayout} text={"Save"} disabled={this.isEmpty || !this.validName} />
</Tooltip>
</div>
</div>
);
case LayoutDialogMode.Rename:
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Tooltip content="Layout name cannot be empty!" disabled={!this.isEmpty}>
<AnchorButton intent={Intent.PRIMARY} onClick={this.renameLayout} text={"Rename"} disabled={this.isEmpty || !this.validName} />
</Tooltip>
</div>
</div>
);
case LayoutDialogMode.SmartLayout:
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<FormGroup inline={true} label="to Layout:">
<HTMLSelect value={layoutStore.smartLayoutName} onChange={ev => layoutStore.saveLayoutMap(ev.currentTarget.value)}>
{layoutStore.orderedLayoutNames.map(layout => (
<option key={layout} value={layout}>
{layout}
</option>
))}
</HTMLSelect>
</FormGroup>
</div>
</div>
);
default:
return "";
}
};

render() {
const appStore = AppStore.Instance;
const className = classNames("preference-dialog", {[Classes.DARK]: appStore.darkTheme});
const isSave = appStore.layoutStore.isSave;

const dialogProps: DialogProps = {
icon: "layout-grid",
Expand All @@ -86,9 +185,12 @@ export class SaveLayoutDialogComponent extends React.Component {
canOutsideClickClose: false,
lazy: true,
isOpen: appStore.dialogStore.dialogVisible.get(DialogId.Layout),
title: isSave ? "Save Layout" : `Rename Layout`
title: this.titleMap.get(appStore.layoutStore.layoutDialogMode)
};

const dialogBody = this.renderLayoutDialogBody(appStore.layoutStore.layoutDialogMode);
const dialogFooter = this.renderLayoutDialogFooter(appStore.layoutStore.layoutDialogMode);

return (
<DraggableDialogComponent
dialogProps={dialogProps}
Expand All @@ -100,20 +202,25 @@ export class SaveLayoutDialogComponent extends React.Component {
enableResizing={true}
dialogId={DialogId.Layout}
>
<div className={Classes.DIALOG_BODY}>
<FormGroup inline={true} label={isSave ? "Save current layout as:" : `Rename ${appStore.layoutStore.oldLayoutName} to:`}>
{/* <div className={Classes.DIALOG_BODY}> */}
{/* <FormGroup inline={true} label={isSave ? "Save current layout as:" : `Rename ${appStore.layoutStore.oldLayoutName} to:`}>
<Tooltip isOpen={!this.isEmpty && !this.validName} position={Position.BOTTOM_LEFT} content={"Layout name should not contain ~, `, !, *, (, ), -, +, =, [, ., ', ?, <, >, /, |, \\, :, ; or &"}>
<InputGroup className="layout-name-input" placeholder="Enter layout name" value={this.layoutName} autoFocus={true} onChange={this.handleInput} onKeyDown={this.handleKeyDown} />
</Tooltip>
</FormGroup>
</div>
<div className={Classes.DIALOG_FOOTER}>
</FormGroup> */}
{/* </div> */}
{dialogBody}
{/* <div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Tooltip content="Save as smart layout" disabled={!this.isEmpty}>
<AnchorButton intent={Intent.PRIMARY} onClick={this.saveLayoutMap} text={"Smart Layout"} disabled={this.isEmpty || !this.validName || appStore.layoutStore.currentLayoutMapCtype.length === 0} />
</Tooltip>
<Tooltip content="Layout name cannot be empty!" disabled={!this.isEmpty}>
<AnchorButton intent={Intent.PRIMARY} onClick={isSave ? this.saveLayout : this.renameLayout} text={isSave ? "Save" : "Rename"} disabled={this.isEmpty || !this.validName} />
</Tooltip>
</div>
</div>
</div> */}
{dialogFooter}
</DraggableDialogComponent>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ export class PreferenceDialogComponent extends React.Component {
<option value={FileFilterMode.All}>All files</option>
</HTMLSelect>
</FormGroup>
{/* <FormGroup inline={true} label="Smart Layout">
<Tooltip content={"Apply data type associated layout"}>
<Switch checked={preference.isSmartLayout} onChange={ev => preference.setPreference(PreferenceKeys.GLOBAL_IS_SMART_LAYOUT, ev.currentTarget.value)} />
</Tooltip>
</FormGroup> */}
<FormGroup inline={true} label="Initial layout">
<HTMLSelect value={preference.layout} onChange={ev => preference.setPreference(PreferenceKeys.GLOBAL_LAYOUT, ev.currentTarget.value)}>
{layoutStore.orderedLayoutNames.map(layout => (
Expand Down
25 changes: 22 additions & 3 deletions src/components/Menu/RootMenuComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {AppToaster, ExportImageMenuComponent, SuccessToast} from "components/Sha
import {CustomIcon, CustomIconName} from "icons/CustomIcons";
import {CARTA_INFO, ImageType, ImageViewItem, PresetLayout, Snippet} from "models";
import {ApiService, ConnectionStatus} from "services";
import {AppStore, BrowserMode, DialogId, PreferenceKeys, SnippetStore, WidgetsStore, WidgetType} from "stores";
import {AppStore, BrowserMode, DialogId, LayoutDialogMode, PreferenceKeys, SnippetStore, WidgetsStore, WidgetType} from "stores";
import {copyToClipboard, toFixed} from "utilities";

import {WorkspaceDialogMode} from "../Dialogs/WorkspaceDialog/WorkspaceDialogComponent";
Expand Down Expand Up @@ -314,11 +314,18 @@ export class RootMenuComponent extends React.Component {
</React.Fragment>
)}
</MenuItem>
<MenuItem text="Save Layout" onClick={() => appStore.dialogStore.showDialog(DialogId.Layout)} />
<MenuItem text="Save Layout" onClick={() => appStore.dialogStore.showDialog(DialogId.Layout, {layoutDialogMode: LayoutDialogMode.Save})} />
<MenuItem text="Rename Layout" disabled={!userLayouts || userLayouts.length <= 0}>
{userLayouts &&
userLayouts.length > 0 &&
userLayouts.map(value => <MenuItem key={value} text={value} active={value === appStore.layoutStore.currentLayoutName} onClick={() => appStore.dialogStore.showDialog(DialogId.Layout, {oldLayoutName: value})} />)}
userLayouts.map(value => (
<MenuItem
key={value}
text={value}
active={value === appStore.layoutStore.currentLayoutName}
onClick={() => appStore.dialogStore.showDialog(DialogId.Layout, {oldLayoutName: value, layoutDialogMode: LayoutDialogMode.Rename})}
/>
))}
</MenuItem>
<MenuItem text="Delete Layout" disabled={!userLayouts || userLayouts.length <= 0}>
{userLayouts &&
Expand All @@ -337,6 +344,18 @@ export class RootMenuComponent extends React.Component {
/>
))}
</MenuItem>
{/* <Tooltip content="Associate data type to the exist layout" position={Position.TOP}>
<MenuItem text="Smart Layout" disabled={!userLayouts || userLayouts.length <= 0 || layoutStore.currentLayoutMapCtype.length === 0}>
{userLayouts &&
userLayouts.length > 0 &&
userLayouts.map(value => <MenuItem key={value} text={value} active={value === appStore.layoutStore.smartLayoutName} onClick={() => appStore.layoutStore.saveLayoutMap(value)} />)}
</MenuItem>
</Tooltip> */}
<MenuItem
text="Smart Layout"
disabled={!userLayouts || userLayouts.length <= 0 || layoutStore.currentLayoutMapCtype.length === 0}
onClick={() => appStore.dialogStore.showDialog(DialogId.Layout, {layoutDialogMode: LayoutDialogMode.SmartLayout})}
/>
</MenuItem>
{imageItems.length > 0 && (
<MenuItem text="Images" icon={"multi-select"}>
Expand Down
Loading

0 comments on commit 9989f60

Please sign in to comment.