update newtab screen to make it match the figma mockup (still waiting on UI for name) (#52)

This commit is contained in:
sawka 2023-10-30 09:43:54 -07:00 committed by GitHub
parent 940a187d34
commit 9b5bc33125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 554 additions and 51 deletions

View File

@ -49,6 +49,32 @@ textarea {
}
}
.text-standard {
font-size: 12.5px;
font-weight: 300;
line-height: 20px;
font-family: @text-s1-font;
.icon {
width: 16px;
height: 16px;
}
}
.text-secondary {
font-family: @fixed-font;
font-size: 11px;
font-weight: 300;
line-height: 16px;
}
.text-caption {
font-family: @fixed-font;
font-size: 10px;
font-weight: 300;
line-height: 14px;
}
.text-s1.icon {
width: 16px;
height: 16px;
@ -437,3 +463,87 @@ a.a-block {
opacity: 0;
}
}
.icon.color-red {
path, circle {
fill: @tab-red;
}
}
.icon.color-green {
path, circle {
fill: @tab-green;
}
}
.icon.color-orange {
path, circle {
fill: @tab-orange;
}
}
.icon.color-blue {
path, circle {
fill: @tab-blue;
}
}
.icon.color-yellow {
path, circle {
fill: @tab-yellow;
}
}
.icon.color-pink {
path, circle {
fill: @tab-pink;
}
}
.icon.color-magenta {
path, circle {
fill: @tab-magenta;
}
}
.icon.color-cyan {
path, circle {
fill: @tab-cyan;
}
}
.icon.color-violet {
path, circle {
fill: @tab-violet;
}
}
.icon.color-white {
path, circle {
fill: @tab-white;
}
}
.status-icon.status-connected {
path, circle {
fill: @status-connected;
}
}
.status-icon.status-connecting {
path, circle {
fill: @status-connecting;
}
}
.status-icon.status-disconnected {
path, circle {
fill: @status-disconnected;
}
}
.status-icon.status-error {
path, circle {
fill: @status-error;
}
}

View File

@ -0,0 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrows up_down">
<g id="Vector">
<path d="M12.1297 9.69269C12.3999 10.0066 12.3645 10.4801 12.0506 10.7504L8.48447 13.8208C8.20296 14.0632 7.78645 14.063 7.50519 13.8203L3.94636 10.7499C3.63274 10.4793 3.59785 10.0057 3.86843 9.69211C4.13901 9.37849 4.6126 9.3436 4.92622 9.61418L7.99562 12.2624L11.0719 9.61368C11.3858 9.34342 11.8594 9.37879 12.1297 9.69269Z" fill="#C3C8C2"/>
<path d="M12.1297 6.31224C12.3999 5.99834 12.3645 5.52479 12.0506 5.25453L8.48447 2.18408C8.20296 1.94171 7.78645 1.94192 7.50519 2.18458L3.94636 5.25502C3.63274 5.52561 3.59785 5.9992 3.86843 6.31282C4.13901 6.62644 4.6126 6.66133 4.92622 6.39075L7.99562 3.74257L11.0719 6.39125C11.3858 6.66151 11.8594 6.62614 12.1297 6.31224Z" fill="#C3C8C2"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 846 B

View File

@ -0,0 +1,5 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="check">
<path id="Vector" d="M10.4215 3.07563C10.6558 3.30994 10.6558 3.68984 10.4215 3.92416L4.922 9.4238C4.80947 9.53632 4.65685 9.59954 4.49772 9.59954C4.33858 9.59954 4.18597 9.53631 4.07345 9.42378L1.57086 6.92099C1.33655 6.68666 1.33657 6.30676 1.5709 6.07246C1.80522 5.83815 2.18512 5.83817 2.41942 6.07249L4.49774 8.15099L9.57293 3.07564C9.80725 2.84133 10.1871 2.84132 10.4215 3.07563Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 525 B

View File

@ -0,0 +1,3 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8.375" cy="8" r="8" fill="#E54D2E"/>
</svg>

After

Width:  |  Height:  |  Size: 151 B

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="globe">
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M5.51561 7.24988C5.58926 5.49464 5.91878 3.94637 6.3934 2.81642C6.66453 2.17096 6.96822 1.69729 7.26714 1.39629C7.56327 1.0981 7.81035 1.00977 8.00003 1.00977C8.1897 1.00977 8.43678 1.0981 8.73291 1.39629C9.03183 1.69729 9.33553 2.17096 9.60665 2.81642C10.0813 3.94637 10.4108 5.49464 10.4844 7.24988H5.51561ZM4.01441 7.24988H1.03973C1.31693 4.64752 3.02019 2.47128 5.35297 1.51768C5.22994 1.74443 5.1158 1.98471 5.01045 2.23552C4.44737 3.57606 4.0887 5.32893 4.01441 7.24988ZM10.6471 1.51768C10.7701 1.74443 10.8842 1.98471 10.9896 2.23552C11.5527 3.57606 11.9114 5.32893 11.9856 7.24988H14.9603C14.6831 4.64752 12.9799 2.47128 10.6471 1.51768ZM10.4842 8.74988C10.4101 10.5028 10.0808 12.049 9.60665 13.1777C9.33553 13.8232 9.03183 14.2968 8.73291 14.5978C8.43678 14.896 8.1897 14.9844 8.00003 14.9844C7.81035 14.9844 7.56327 14.896 7.26714 14.5978C6.96822 14.2968 6.66453 13.8232 6.3934 13.1777C5.91928 12.049 5.58997 10.5028 5.51585 8.74988H10.4842ZM11.9854 8.74988H14.9603C14.683 11.3537 12.978 13.5309 10.6432 14.4837C10.7677 14.2548 10.8831 14.0121 10.9896 13.7586C11.5521 12.4194 11.9107 10.6686 11.9854 8.74988ZM5.35689 14.4837C3.0221 13.5309 1.31708 11.3537 1.03973 8.74988H4.01463C4.0894 10.6686 4.44792 12.4194 5.01045 13.7586C5.11692 14.0121 5.23237 14.2548 5.35689 14.4837Z" fill="#C3C8C2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,3 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle id="Ellipse 1" cx="5" cy="5" r="4" fill="#46A758" stroke="#151715" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 197 B

View File

@ -13,6 +13,7 @@ import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "..
import { RemotesSelector } from "../../connections/connections";
import { PluginModel } from "../../../plugins/plugins";
import * as util from "../../../util/util";
import { commandRtnHandler } from "../../../util/util";
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
import { ReactComponent as XmarkIcon } from "../../assets/icons/line/xmark.svg";
import { ReactComponent as AngleDownIcon } from "../../assets/icons/history/angle-down.svg";
@ -48,17 +49,6 @@ const WebStopShareConfirmMarkdown = `
Are you sure you want to stop web-sharing this screen?
`.trim();
function commandRtnHandler(prtn: Promise<CommandRtnType>, errorMessage: OV<string>) {
prtn.then((crtn) => {
if (crtn.success) {
return;
}
mobx.action(() => {
errorMessage.set(crtn.error);
})();
});
}
@mobxReact.observer
class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string; inline?: boolean }, {}> {
shareCopied: OV<boolean> = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" });
@ -271,12 +261,6 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
</div>
</div>
</div>
<div className="settings-field">
<div className="settings-label">Connection</div>
<div className="settings-input">
<RemotesSelector model={GlobalModel.remotesModalModel} isChangeRemoteOnSelect={true} />
</div>
</div>
{!inline && (
<div className="settings-field">
<div className="settings-label">

View File

@ -9,7 +9,7 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, LineContainerModel } from "../../../model/model";
import type { LineType, RemoteType, RemotePtrType, LineHeightChangeCallbackType } from "../../../types/types";
import cn from "classnames";
import { isBlank } from "../../../util/util";
import { isBlank, getRemoteStr } from "../../../util/util";
import { ReactComponent as FolderIcon } from "../../assets/icons/folder.svg";
import "./prompt.less";
@ -44,29 +44,6 @@ function getShortVEnv(venvDir: string): string {
return venvDir.substr(lastSlash + 1);
}
function makeFullRemoteRef(ownerName: string, remoteRef: string, name: string): string {
if (isBlank(ownerName) && isBlank(name)) {
return remoteRef;
}
if (!isBlank(ownerName) && isBlank(name)) {
return ownerName + ":" + remoteRef;
}
if (isBlank(ownerName) && !isBlank(name)) {
return remoteRef + ":" + name;
}
return ownerName + ":" + remoteRef + ":" + name;
}
function getRemoteStr(rptr: RemotePtrType): string {
if (rptr == null || isBlank(rptr.remoteid)) {
return "(invalid remote)";
}
let username = isBlank(rptr.ownerid) ? null : GlobalModel.resolveUserIdToName(rptr.ownerid);
let remoteRef = GlobalModel.resolveRemoteIdToRef(rptr.remoteid);
let fullRef = makeFullRemoteRef(username, remoteRef, rptr.name);
return fullRef;
}
function replaceHomePath(path: string, homeDir: string): string {
if (path == homeDir) {
return "~";

View File

@ -18,6 +18,18 @@
@warning-yellow: #ffa500;
@textarea-background: #2a2a2a;
@text-primary: #fff;
@text-secondary: #C3C8C2;
@text-caption: #8b918a;
@status-outline: #151715;
@dropdown-menu: rgba(21, 23, 21, 1);
@status-connected: #46A758;
@status-connecting: #f5d90a;
@status-error: #e54d2e;
@status-disconnected: #c3c8c2;
@term-black: #000000;
@term-red: #cc0000;
@term-green: #4e9a06;

View File

@ -94,3 +94,194 @@
}
}
}
.newtab-container {
margin: 16px;
.newtab-section {
padding: 16px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
&.conn-section {
gap: 8px;
}
}
.newtab-spacer {
height: 1px;
background: rgba(241, 246, 243, 0.15);
}
.control-iconlist {
display: flex;
padding: 8px 0;
align-items: flex-start;
gap: 27px;
.icondiv {
width: 16px;
height: 16px;
cursor: pointer;
position: relative;
.icon {
width: 16px;
height: 16px;
}
.check-icon {
width: 12px;
height: 12px;
position: absolute;
top: 2px;
left: 2px;
}
.icon.color-white + .check-icon {
path {
fill: black;
}
}
}
}
.dropdown.conn-dropdown {
padding-left: 0;
border-radius: 8px;
background-color: rgba(241, 246, 243, 0.08);
.conn-dd-trigger {
display: flex;
flex-direction: row;
width: 413px;
padding: 6px 8px 6px 12px;
align-items: center;
height: 42px;
.lefticon {
margin-right: 8px;
margin-top: 4px;
position: relative;
.status-icon {
width: 10px;
height: 10px;
stroke-width: 2px;
stroke: @status-outline;
position: absolute;
bottom: 3px;
right: -2px;
}
}
.dd-control {
display: flex;
padding: 4px;
align-items: center;
.icon {
height: 16px;
width: 16px;
}
}
.globe-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.conntext {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
flex: 1 0 0;
.conntext-solo {
color: @text-primary;
text-overflow: ellipsis;
}
.conntext-1 {
color: @text-primary;
text-overflow: ellipsis;
}
.conntext-2 {
color: @text-secondary;
text-overflow: ellipsis;
}
}
}
.conn-dd-menu {
display: flex;
width: 413px;
padding: 6px;
flex-direction: column;
align-items: flex-start;
border-radius: 8px;
background-color: @dropdown-menu;
.dropdown-item {
display: flex;
padding: 5px 12px 5px 8px;
align-items: center;
gap: 8px;
align-self: stretch;
border-radius: 6px;
.status-div {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
padding: 3px;
svg.status-icon {
width: 10px;
height: 10px;
}
}
.add-div {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
svg.add-icon {
width: 16px;
height: 16px;
path {
fill: @text-primary;
}
}
}
.text-standard {
color: @text-secondary;
}
.text-caption {
color: @text-caption;
}
.ellipsis {
text-overflow: ellipsis;
}
&:hover {
background-color: rgba(241, 246, 243, 0.08);
}
}
}
}
}

View File

@ -10,14 +10,22 @@ import { If } from "tsx-control-statements/components";
import cn from "classnames";
import { debounce } from "throttle-debounce";
import dayjs from "dayjs";
import { GlobalCommandRunner } from "../../../model/model";
import { GlobalCommandRunner, TabColors } from "../../../model/model";
import type { LineType, RenderModeType, LineFactoryProps, CommandRtnType } from "../../../types/types";
import * as T from "../../../types/types";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { InlineSettingsTextEdit } from "../../common/common";
import { InlineSettingsTextEdit, RemoteStatusLight } from "../../common/common";
import { GlobalModel, ScreenLines, Screen } from "../../../model/model";
import { Line } from "../../line/linecomps";
import { LinesView } from "../../line/linesview";
import { ScreenSettingsModal } from "../../common/modals/settings";
import * as util from "../../../util/util";
import { ReactComponent as EllipseIcon } from "../../assets/icons/ellipse.svg";
import { ReactComponent as Check12Icon } from "../../assets/icons/check12.svg";
import { ReactComponent as GlobeIcon } from "../../assets/icons/globe.svg";
import { ReactComponent as StatusCircleIcon } from "../../assets/icons/statuscircle.svg";
import { ReactComponent as ArrowsUpDownIcon } from "../../assets/icons/arrowsupdown.svg";
import { ReactComponent as CircleIcon } from "../../assets/icons/circle.svg";
import { ReactComponent as AddIcon } from "../../assets/icons/add.svg";
import "./screenview.less";
import "./tabs.less";
@ -42,6 +50,172 @@ class ScreenView extends React.Component<{ screen: Screen }, {}> {
}
}
@mobxReact.observer
class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
connDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "NewTabSettings-connDropdownActive" });
errorMessage: OV<string> = mobx.observable.box(null, { name: "NewTabSettings-errorMessage" });
@boundMethod
selectTabColor(color: string): void {
let { screen } = this.props;
if (screen.getTabColor() == color) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, { tabcolor: color }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
inlineUpdateName(val: string): void {
let { screen } = this.props;
if (util.isStrEq(val, screen.name.get())) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, { name: val }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
toggleConnDropdown(): void {
mobx.action(() => {
this.connDropdownActive.set(!this.connDropdownActive.get());
})();
}
@boundMethod
selectRemote(cname: string): void {
mobx.action(() => {
this.connDropdownActive.set(false);
})();
let prtn = GlobalCommandRunner.screenSetRemote(cname, true, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
clickNewConnection(): void {
mobx.action(() => {
this.connDropdownActive.set(false);
})();
GlobalModel.remotesModalModel.openModalForEdit({remoteedit: true}, true);
}
renderConnDropdown(): any {
let { screen } = this.props;
let allRemotes = util.sortAndFilterRemotes(GlobalModel.remotes.slice());
let remote: RemoteType = null;
let curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
// TODO no remote?
return (
<div className={cn("dropdown", "conn-dropdown", { "is-active": this.connDropdownActive.get() })}>
<div className="dropdown-trigger" onClick={this.toggleConnDropdown}>
<div className="conn-dd-trigger">
<div className="lefticon">
<GlobeIcon className="globe-icon"/>
<StatusCircleIcon className={cn("status-icon", "status-" + curRemote.status)}/>
</div>
<div className="conntext">
<If condition={util.isBlank(curRemote.remotealias)}>
<div className="text-standard conntext-solo">
{curRemote.remotecanonicalname}
</div>
</If>
<If condition={!util.isBlank(curRemote.remotealias)}>
<div className="text-secondary conntext-1">
{curRemote.remotealias}
</div>
<div className="text-caption conntext-2">
{curRemote.remotecanonicalname}
</div>
</If>
</div>
<div className="dd-control">
<ArrowsUpDownIcon className="icon"/>
</div>
</div>
</div>
<div className="dropdown-menu" role="menu">
<div className="dropdown-content conn-dd-menu">
<For each="remote" of={allRemotes}>
<div className="dropdown-item" key={remote.remoteid} onClick={() => this.selectRemote(remote.remotecanonicalname)}>
<div className="status-div">
<CircleIcon className={cn("status-icon", "status-" + remote.status)}/>
</div>
<If condition={util.isBlank(remote.remotealias)}>
<div className="text-standard">{remote.remotecanonicalname}</div>
</If>
<If condition={!util.isBlank(remote.remotealias)}>
<div className="text-standard">{remote.remotealias}</div>
<div className="text-caption">{remote.remotecanonicalname}</div>
</If>
</div>
</For>
<div className="dropdown-item" onClick={this.clickNewConnection}>
<div className="add-div">
<AddIcon className="add-icon"/>
</div>
<div className="text-standard">New Connection</div>
</div>
</div>
</div>
</div>
);
}
render() {
let { screen } = this.props;
let rptr = screen.curRemote.get();
let curColor = screen.getTabColor();
if (util.isBlank(curColor) || curColor == "default") {
curColor = "green";
}
let color: string = null;
return (
<div className="newtab-container">
<div className="newtab-section conn-section">
<div className="text-s1">
You're connected to [{util.getRemoteStr(rptr)}]. Do you want to change it?
</div>
<div>
{this.renderConnDropdown()}
</div>
</div>
<div className="newtab-spacer"/>
<div className="newtab-section settings-field">
<div className="text-s1">
Name
</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder="name"
text={screen.name.get() ?? "(none)"}
value={screen.name.get() ?? ""}
onChange={this.inlineUpdateName}
maxLength={50}
showIcon={true}
/>
</div>
</div>
<div className="newtab-spacer"/>
<div className="newtab-section">
<div className="text-s1">
Select the color
</div>
<div className="control-iconlist">
<For each="color" of={TabColors}>
<div className="icondiv" key={color} title={color} onClick={() => this.selectTabColor(color)}>
<EllipseIcon className={cn("icon", "color-" + color)}/>
<If condition={color == curColor}>
<Check12Icon className="check-icon"/>
</If>
</div>
</For>
</div>
</div>
</div>
);
}
}
// screen is not null
@mobxReact.observer
class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
@ -204,12 +378,7 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
</div>
</div>
<If condition={lines.length == 0}>
<ScreenSettingsModal
key={screen.sessionId + ":" + screen.screenId}
sessionId={screen.sessionId}
screenId={screen.screenId}
inline={true}
/>
<NewTabSettings screen={screen}/>
</If>
<If condition={screen.isWebShared()}>
<div key="share-tag" className="share-tag">

View File

@ -94,7 +94,7 @@ const MinFontSize = 8;
const MaxFontSize = 15;
const InputChunkSize = 500;
const RemoteColors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"];
const TabColors = ["yellow", "blue", "pink", "magenta", "cyan", "violet", "orange", "green", "red", "white"];
const TabColors = ["green", "blue", "yellow", "pink", "magenta", "cyan", "violet", "orange", "red", "white"];
// @ts-ignore
const VERSION = __PROMPT_VERSION__;

View File

@ -401,6 +401,40 @@ function generateBackgroundWithGradient(colorName = "white", decay = 3) {
return `linear-gradient(180deg, rgba(${r}, ${g}, ${b}, ${opacities[0]}) ${percentages[0]}%, rgba(${r}, ${g}, ${b}, ${opacities[1]}) ${percentages[1]}%, rgba(${r}, ${g}, ${b}, 0) ${percentages[2]}%)`;
}
function makeFullRemoteRef(ownerName: string, remoteRef: string, name: string): string {
if (isBlank(ownerName) && isBlank(name)) {
return remoteRef;
}
if (!isBlank(ownerName) && isBlank(name)) {
return ownerName + ":" + remoteRef;
}
if (isBlank(ownerName) && !isBlank(name)) {
return remoteRef + ":" + name;
}
return ownerName + ":" + remoteRef + ":" + name;
}
function getRemoteStr(rptr: RemotePtrType): string {
if (rptr == null || isBlank(rptr.remoteid)) {
return "(invalid remote)";
}
let username = isBlank(rptr.ownerid) ? null : GlobalModel.resolveUserIdToName(rptr.ownerid);
let remoteRef = GlobalModel.resolveRemoteIdToRef(rptr.remoteid);
let fullRef = makeFullRemoteRef(username, remoteRef, rptr.name);
return fullRef;
}
function commandRtnHandler(prtn: Promise<CommandRtnType>, errorMessage: OV<string>) {
prtn.then((crtn) => {
if (crtn.success) {
return;
}
mobx.action(() => {
errorMessage.set(crtn.error);
})();
});
}
export {
handleJsonFetchResponse,
base64ToArray,
@ -424,4 +458,6 @@ export {
openLink,
generateBackgroundWithGradient,
getColorRGB,
getRemoteStr,
commandRtnHandler,
};