Skip to content

Commit

Permalink
feat(console): sync console theme color with the vs code theme (#3805)
Browse files Browse the repository at this point in the history
This PR syncs the Console theme color with the vs-code theme.

Changing the map colors (applied in the PR):

https://github.com/winglang/wing/assets/5547636/3d5f8d0c-dc2f-457a-9b40-45b1ea0d588a

Without changing the map colors:

https://github.com/winglang/wing/assets/5547636/404a0921-9f65-4e60-9398-b67bb5a650a4

resolves: #3747
*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
  • Loading branch information
polamoros authored Aug 17, 2023
1 parent ec5f4ce commit 34f3175
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 24 deletions.
21 changes: 18 additions & 3 deletions apps/vscode-wing/src/console/console-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,24 @@ export const createConsoleManager = (
iframe { display: block; height: 100vh; width: 100vw; border: none; }
</style>
</head>
<body>
<iframe src="http://${instance.url}?layout=4&theme=${getTheme()}"/>
</body>
<script>
const instanceUrl = "${instance.url}";
const layout = 4;
const theme = "${getTheme()}";
// Used to force reload the iframe
const time = "${Date.now()}";
const color = getComputedStyle(document.documentElement).getPropertyValue('--vscode-menu-background').replace("#", "");
document.addEventListener('DOMContentLoaded', () => {
const iframe = document.querySelector('iframe');
const iframeSrc = \`http://\${instanceUrl}?layout=\${layout}&theme=\${theme}&color=\${color}\`;
iframe.src = iframeSrc;
});
</script>
<body>
<iframe src=""/>
</body>
</html>`;

resourcesExplorer.update(await instance.client.listResources());
Expand Down
1 change: 1 addition & 0 deletions apps/wing-console/console/app/web/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ReactDOM.createRoot(document.querySelector("#root")!).render(
wsUrl={`ws://${location.host}/trpc`}
layout={Number(query.get("layout")) || 1} // default to 1 = vscode (2 = playground, 3 = tutorial)
theme={query.get("theme") as any}
color={query.get("color") as any}
onTrace={(trace) => {
// Playground and Learn need to be able to listen to all traces.
window.parent.postMessage({ trace }, "*");
Expand Down
140 changes: 140 additions & 0 deletions apps/wing-console/console/design-system/src/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export type Mode = "dark" | "light" | "auto";

const localStorageThemeKey = "console-theme";

export const USE_EXTERNAL_THEME_COLOR = "use-external-theme-color";

export const DefaultTheme: Theme = {
bg1: "bg-slate-300 dark:bg-slate-800",
bg2: "bg-slate-200 dark:bg-slate-800",
Expand Down Expand Up @@ -69,6 +71,144 @@ export const DefaultTheme: Theme = {
"scrollbar hover:scrollbar-bg-slate-500/10 hover:scrollbar-thumb-slate-700/30 scrollbar-thumb-hover-slate-700/40 scrollbar-thumb-active-slate-700/60 dark:hover:scrollbar-bg-slate-400/10 dark:hover:scrollbar-thumb-slate-400/30 dark:scrollbar-thumb-hover-slate-400/40 dark:scrollbar-thumb-active-slate-400/60",
};

const adjustChannel = (value: number, factor: number): number => {
return Math.max(Math.round(value + (255 - value) * (1 - factor)), 0);
};

const computeColor = (color: string, level: number = 1): string => {
const hexColor = color.replace("#", "");

if (level === 1) {
return `#${hexColor}`;
}

const oldR = Number.parseInt(hexColor.slice(0, 2), 16);
const oldG = Number.parseInt(hexColor.slice(2, 4), 16);
const oldB = Number.parseInt(hexColor.slice(4, 6), 16);

const newR = adjustChannel(oldR, level);
const newG = adjustChannel(oldG, level);
const newB = adjustChannel(oldB, level);

const newColor = `${newR.toString(16).padStart(2, "0")}${newG
.toString(16)
.padStart(2, "0")}${newB.toString(16).padStart(2, "0")}`;

return `#${newColor}`;
};

const applyThemeStyle = (newTheme: Theme) => {
const colorRegex = /bg-|\[|]|dark:|hover:/g;
const keyRegex = /[#:[\\\]]/g;

let styles = {};
Object.keys(newTheme).map((key) => {
const colorClasses = newTheme[key as keyof Theme];
if (colorClasses === DefaultTheme[key as keyof Theme]) {
return;
}
const [lightClass, darkClass] = colorClasses.split(" ");
if (!lightClass) {
return;
}
const lightColor = lightClass.replaceAll(colorRegex, "");
const lightKey = lightClass.replaceAll(keyRegex, "\\$&");

const hover = lightClass.includes("hover:");

styles = {
...styles,
[`.${USE_EXTERNAL_THEME_COLOR} .${lightKey}${
hover ? ":hover" : ""
}`]: `{ background-color: ${lightColor} !important;}`,
};

if (!darkClass) {
return;
}
const darkColor = darkClass.replaceAll(colorRegex, "");
const darkKey = darkClass.replaceAll(keyRegex, "\\$&");
styles = {
...styles,
[`.dark .${USE_EXTERNAL_THEME_COLOR} .${darkKey}${
hover ? ":hover" : ""
}`]: `{ background-color: ${darkColor} !important;}`,
};
});

const stylesText = Object.keys(styles)
.map((property) => `${property} ${styles[property as keyof typeof styles]}`)
.join("\n");

let styleElement = document.querySelector("#style-theme");
if (styleElement) {
styleElement.remove();
}
styleElement = document.createElement("style");
styleElement.setAttribute("id", "style-theme");
styleElement.innerHTML = stylesText;
document.head.append(styleElement);
};

export const buildTheme = (color?: string): Theme => {
if (!color) {
return DefaultTheme;
}
const theme: Theme = {
...DefaultTheme,
bg1: `bg-[${computeColor(color, 1.2)}] dark:bg-[${computeColor(
color,
1.2,
)}]`,

bg2: `bg-[${computeColor(color, 1.1)}] dark:bg-[${computeColor(
color,
1.1,
)}]`,
bg3: `bg-[${computeColor(color)}] dark:bg-[${computeColor(color)}]`,
bg4: `bg-[${computeColor(color, 0.8)}] dark:bg-[${computeColor(
color,
0.8,
)}]`,
bg3Hover: `hover:bg-[${computeColor(
color,
1.02,
)}] dark:hover:bg-[${computeColor(color, 0.98)}]`,
bg2Hover: `hover:bg-[${computeColor(
color,
1.12,
)}] dark:hover:bg-[${computeColor(color, 1.08)}]`,

bg4Hover: `hover:bg-[${computeColor(
color,
0.82,
)}] dark:hover:bg-[${computeColor(color, 0.78)}]`,

bgInput: `bg-[${computeColor(color, 0.8)}] dark:bg-[${computeColor(
color,
1.13,
)}]`,
};

applyThemeStyle(theme);

let mergedTheme: Theme = DefaultTheme;
Object.keys(DefaultTheme).map((key) => {
const themeProperty = key as keyof Theme;

const changed = theme[themeProperty] !== DefaultTheme[themeProperty];
if (!changed) {
return;
}
mergedTheme = {
...mergedTheme,
[themeProperty]: `${DefaultTheme[themeProperty]} ${theme[themeProperty]}`,
};
});

return mergedTheme;
};

export interface ThemeProviderProps {
theme: Theme;
mode: Mode;
Expand Down
11 changes: 8 additions & 3 deletions apps/wing-console/console/ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {
DefaultTheme,
ThemeProvider,
NotificationsProvider,
type Mode,
Theme,
buildTheme,
} from "@wingconsole/design-system";
import type { Trace } from "@wingconsole/server";

Expand All @@ -13,10 +14,11 @@ import { TestsContextProvider } from "./tests-context.js";
export interface AppProps {
layout?: LayoutType;
theme?: Mode;
color?: string;
onTrace?: (trace: Trace) => void;
}

export const App = ({ layout, theme, onTrace }: AppProps) => {
export const App = ({ layout, theme, color, onTrace }: AppProps) => {
const trpcContext = trpc.useContext();

trpc["app.invalidateQuery"].useSubscription(undefined, {
Expand All @@ -43,7 +45,10 @@ export const App = ({ layout, theme, onTrace }: AppProps) => {
const themeMode = trpc["config.getThemeMode"].useQuery();

return (
<ThemeProvider mode={theme || themeMode?.data?.mode} theme={DefaultTheme}>
<ThemeProvider
mode={theme || themeMode?.data?.mode}
theme={buildTheme(color)}
>
<NotificationsProvider>
<TestsContextProvider>
<LayoutProvider
Expand Down
4 changes: 3 additions & 1 deletion apps/wing-console/console/ui/src/Console.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ export const Console = ({
layout = LayoutType.Default,
title,
theme,
color,
onTrace,
}: {
trpcUrl: string;
wsUrl: string;
title?: string;
layout?: LayoutType;
theme?: Mode;
color?: string;
onTrace?: (trace: Trace) => void;
}) => {
const queryClient = new QueryClient({
Expand Down Expand Up @@ -61,7 +63,7 @@ export const Console = ({
<AppContext.Provider value={{ appMode, title: windowTitle }}>
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<App layout={layout} theme={theme} onTrace={onTrace} />
<App layout={layout} theme={theme} color={color} onTrace={onTrace} />
</QueryClientProvider>
</trpc.Provider>
</AppContext.Provider>
Expand Down
12 changes: 10 additions & 2 deletions apps/wing-console/console/ui/src/features/map-view.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { useTheme, ResourceIcon } from "@wingconsole/design-system";
import {
useTheme,
ResourceIcon,
USE_EXTERNAL_THEME_COLOR,
} from "@wingconsole/design-system";
import classNames from "classnames";

import { useMap } from "../services/use-map.js";
Expand Down Expand Up @@ -30,7 +34,11 @@ export const MapView = ({
return (
<ZoomPaneProvider>
<div className="h-full flex flex-col">
{showMapControls && <MapControls />}
{showMapControls && (
<div className={classNames(USE_EXTERNAL_THEME_COLOR)}>
<MapControls />
</div>
)}
<div
className={classNames(
"grow relative border-t",
Expand Down
48 changes: 35 additions & 13 deletions apps/wing-console/console/ui/src/layout/default-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
RightResizableWidget,
ScrollableArea,
TopResizableWidget,
USE_EXTERNAL_THEME_COLOR,
} from "@wingconsole/design-system";
import type { State, LayoutConfig, LayoutComponent } from "@wingconsole/server";
import classNames from "classnames";
Expand Down Expand Up @@ -162,7 +163,10 @@ export const DefaultLayout = ({
return (
<div
key={component.type}
className={classNames("flex-1 flex flex-col min-w-[10rem]")}
className={classNames(
"flex-1 flex flex-col min-w-[10rem]",
theme.bg3,
)}
>
<div className="relative h-full flex flex-col gap-2">
{loading && (
Expand All @@ -184,7 +188,12 @@ export const DefaultLayout = ({
<ScrollableArea
ref={logsRef}
overflowY
className={classNames("pb-1.5", theme.bg3, theme.text2)}
className={classNames(
"pb-1.5",
theme.bg3,
theme.text2,
USE_EXTERNAL_THEME_COLOR,
)}
>
<ConsoleLogs
logs={logs.data ?? []}
Expand Down Expand Up @@ -238,10 +247,12 @@ export const DefaultLayout = ({
)}
>
{!layout.header?.hide && (
<Header
title={wingfile.data ?? ""}
showThemeToggle={layout.header?.showThemeToggle}
/>
<div className={classNames(USE_EXTERNAL_THEME_COLOR)}>
<Header
title={wingfile.data ?? ""}
showThemeToggle={layout.header?.showThemeToggle}
/>
</div>
)}

{cloudAppState === "error" &&
Expand Down Expand Up @@ -276,6 +287,7 @@ export const DefaultLayout = ({
layout.leftPanel?.components?.length && (
<RightResizableWidget
className={classNames(
USE_EXTERNAL_THEME_COLOR,
theme.border3,
"h-full flex flex-col w-80 min-w-[10rem] min-h-[10rem] border-r",
)}
Expand Down Expand Up @@ -305,7 +317,13 @@ export const DefaultLayout = ({

<div className="flex-1 flex flex-col">
<div className="flex-1 flex">
<div className="flex-1 flex flex-col" data-testid="map-view">
<div
className={classNames(
"flex-1 flex flex-col",
USE_EXTERNAL_THEME_COLOR,
)}
data-testid="map-view"
>
<MapView
showTests={showTests}
selectedNodeId={selectedItems[0]}
Expand All @@ -319,6 +337,7 @@ export const DefaultLayout = ({

<LeftResizableWidget
className={classNames(
USE_EXTERNAL_THEME_COLOR,
theme.border3,
"flex-shrink w-80 min-w-[10rem] border-l z-10",
theme.bg4,
Expand Down Expand Up @@ -354,6 +373,7 @@ export const DefaultLayout = ({
{!layout.bottomPanel?.hide && (
<TopResizableWidget
className={classNames(
USE_EXTERNAL_THEME_COLOR,
theme.border3,
"border-t relative flex",
theme.bg3,
Expand Down Expand Up @@ -418,12 +438,14 @@ export const DefaultLayout = ({
)}

{!layout.statusBar?.hide && (
<StatusBar
wingVersion={wingVersion}
cloudAppState={cloudAppState}
isError={cloudAppState === "error"}
showThemeToggle={layout.statusBar?.showThemeToggle}
/>
<div className={classNames(USE_EXTERNAL_THEME_COLOR)}>
<StatusBar
wingVersion={wingVersion}
cloudAppState={cloudAppState}
isError={cloudAppState === "error"}
showThemeToggle={layout.statusBar?.showThemeToggle}
/>
</div>
)}
</div>
</>
Expand Down
Loading

0 comments on commit 34f3175

Please sign in to comment.