From 0bf9382eebdb03cbe1820c03853495f30344ff2b Mon Sep 17 00:00:00 2001 From: Sakshi Gupta Date: Tue, 30 Sep 2025 16:06:12 +0530 Subject: [PATCH] updated the theme icon for now on DE to change the theme from portal/DE it self --- images/moon-blue.svg | 3 + images/sun-blue.svg | 11 ++ .../CollapsibleSectionComponent.tsx | 2 +- src/Explorer/Controls/Dialog.tsx | 24 +-- src/Explorer/Controls/Editor/EditorReact.tsx | 13 +- .../FullTextPoliciesComponent.tsx | 27 +-- .../ComputedPropertiesComponent.tsx | 17 +- .../ContainerPolicyComponent.tsx | 68 +++---- .../IndexingPolicyComponent.tsx | 60 ++----- .../ThroughputBucketsComponent.tsx | 13 +- .../ThroughputInputAutoPilotV3Component.tsx | 32 ++-- .../CostEstimateText/CostEstimateText.tsx | 2 +- .../ThroughputInput/ThroughputInput.tsx | 32 +++- .../Controls/V9Components/Button/index.tsx | 28 +-- src/Explorer/DataExplorer.tsx | 2 +- .../CommandBar/CommandBarComponentAdapter.tsx | 15 +- .../CommandBarComponentButtonFactory.tsx | 4 +- .../Menus/CommandBar/ThemeToggleButton.tsx | 29 +++ src/Explorer/OpenFullScreen.tsx | 2 +- .../AddCollectionPanel/AddCollectionPanel.tsx | 48 ++--- .../Panes/PanelContainerComponent.tsx | 33 ++-- .../Panes/SettingsPane/SettingsPane.tsx | 65 ++++--- src/Explorer/SplashScreen/SplashScreen.less | 170 ------------------ src/Explorer/SplashScreen/SplashScreen.tsx | 8 - .../Tabs/QueryTab/QueryTabComponent.tsx | 11 +- .../Tabs/UserDefinedFunctionTabContent.tsx | 4 +- src/Main.tsx | 13 +- src/hooks/useTheme.tsx | 147 ++++++++++++++- 28 files changed, 444 insertions(+), 439 deletions(-) create mode 100644 images/moon-blue.svg create mode 100644 images/sun-blue.svg create mode 100644 src/Explorer/Menus/CommandBar/ThemeToggleButton.tsx diff --git a/images/moon-blue.svg b/images/moon-blue.svg new file mode 100644 index 000000000..d664de808 --- /dev/null +++ b/images/moon-blue.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/sun-blue.svg b/images/sun-blue.svg new file mode 100644 index 000000000..40ed9a803 --- /dev/null +++ b/images/sun-blue.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx b/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx index 0f7eec239..653e4ca23 100644 --- a/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx +++ b/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx @@ -85,7 +85,7 @@ export class CollapsibleSectionComponent extends React.Component { event.stopPropagation(); diff --git a/src/Explorer/Controls/Dialog.tsx b/src/Explorer/Controls/Dialog.tsx index efa87d5e7..d02eb1120 100644 --- a/src/Explorer/Controls/Dialog.tsx +++ b/src/Explorer/Controls/Dialog.tsx @@ -179,18 +179,18 @@ export const Dialog: FC = () => { title, subText, styles: { - title: { - fontSize: DIALOG_TITLE_FONT_SIZE, + title: { + fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT, }, - subText: { + subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE, - color: 'var(--colorNeutralForeground2)' + color: "var(--colorNeutralForeground2)", }, content: { - backgroundColor: 'var(--colorNeutralBackground1)', - color: 'var(--colorNeutralForeground1)' - } + backgroundColor: "var(--colorNeutralBackground1)", + color: "var(--colorNeutralForeground1)", + }, }, showCloseButton: showCloseButton || false, onDismiss, @@ -200,12 +200,12 @@ export const Dialog: FC = () => { maxWidth: DIALOG_MAX_WIDTH, styles: { main: { - backgroundColor: 'var(--colorNeutralBackground1)', + backgroundColor: "var(--colorNeutralBackground1)", selectors: { - '.ms-Dialog-title': { color: 'var(--colorNeutralForeground1)' }, - } - } - } + ".ms-Dialog-title": { color: "var(--colorNeutralForeground1)" }, + }, + }, + }, }; const primaryButtonProps: IButtonProps = { diff --git a/src/Explorer/Controls/Editor/EditorReact.tsx b/src/Explorer/Controls/Editor/EditorReact.tsx index f98a95147..f9661c33a 100644 --- a/src/Explorer/Controls/Editor/EditorReact.tsx +++ b/src/Explorer/Controls/Editor/EditorReact.tsx @@ -1,5 +1,5 @@ import { Spinner, SpinnerSize } from "@fluentui/react"; -import { monacoTheme } from "hooks/useTheme"; +import { useThemeStore } from "hooks/useTheme"; import * as React from "react"; import { loadMonaco, monaco } from "../../LazyMonaco"; // import "./EditorReact.less"; @@ -67,6 +67,7 @@ export class EditorReact extends React.Component void; monacoApi: { default: typeof monaco; Emitter: typeof monaco.Emitter; @@ -95,6 +96,13 @@ export class EditorReact extends React.Component { + if (this.editor) { + const newTheme = state.isDarkMode ? "vs-dark" : "vs"; + this.monacoApi?.editor.setTheme(newTheme); + } + }); + setTimeout(() => { const suggestionWidget = this.editor?.getDomNode()?.querySelector(".suggest-widget") as HTMLElement; if (suggestionWidget) { @@ -129,6 +137,7 @@ export class EditorReact extends React.Component ))} - + backgroundColor: "transparent", + }, + }} + onClick={onAdd} + > Add full text path diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx index 5160a768b..cf679dc09 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx @@ -3,7 +3,7 @@ import * as DataModels from "Contracts/DataModels"; import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/Controls/Settings/SettingsRenderUtils"; import { isDirty } from "Explorer/Controls/Settings/SettingsUtils"; import { loadMonaco } from "Explorer/LazyMonaco"; -import { monacoTheme } from "hooks/useTheme"; +import { useThemeStore } from "hooks/useTheme"; import * as monaco from "monaco-editor"; import * as React from "react"; export interface ComputedPropertiesComponentProps { @@ -27,6 +27,7 @@ export class ComputedPropertiesComponent extends React.Component< private shouldCheckComponentIsDirty = true; private computedPropertiesDiv = React.createRef(); private computedPropertiesEditor: monaco.editor.IStandaloneCodeEditor; + private themeUnsubscribe: () => void; constructor(props: ComputedPropertiesComponentProps) { super(props); @@ -48,6 +49,10 @@ export class ComputedPropertiesComponent extends React.Component< this.onComponentUpdate(); } + componentWillUnmount(): void { + this.themeUnsubscribe && this.themeUnsubscribe(); + } + public resetComputedPropertiesEditor = (): void => { if (!this.computedPropertiesEditor) { this.createComputedPropertiesEditor(); @@ -86,9 +91,17 @@ export class ComputedPropertiesComponent extends React.Component< value: value, language: "json", ariaLabel: "Computed properties", - theme: monacoTheme, + theme: useThemeStore.getState().isDarkMode ? "vs-dark" : "vs", }); if (this.computedPropertiesEditor) { + // Subscribe to theme changes + this.themeUnsubscribe = useThemeStore.subscribe((state) => { + if (this.computedPropertiesEditor) { + const newTheme = state.isDarkMode ? "vs-dark" : "vs"; + monaco.editor.setTheme(newTheme); + } + }); + const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel(); computedPropertiesEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this)); this.props.logComputedPropertiesSuccessMessage(); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ContainerPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ContainerPolicyComponent.tsx index 2f7d23600..5fade0420 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ContainerPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ContainerPolicyComponent.tsx @@ -102,51 +102,51 @@ export const ContainerPolicyComponent: React.FC = return (
- {isVectorSearchEnabled && ( @@ -190,26 +190,26 @@ export const ContainerPolicyComponent: React.FC = ) : ( { checkAndSendFullTextPolicyToSettings({ diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx index 632459fa8..d002d0197 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx @@ -1,5 +1,5 @@ import { MessageBar, MessageBarType, Stack } from "@fluentui/react"; -import { monacoTheme } from "hooks/useTheme"; +import { useThemeStore } from "hooks/useTheme"; import * as monaco from "monaco-editor"; import * as React from "react"; import * as DataModels from "../../../../Contracts/DataModels"; @@ -31,6 +31,7 @@ export class IndexingPolicyComponent extends React.Component< private shouldCheckComponentIsDirty = true; private indexingPolicyDiv = React.createRef(); private indexingPolicyEditor: monaco.editor.IStandaloneCodeEditor; + private themeUnsubscribe: () => void; constructor(props: IndexingPolicyComponentProps) { super(props); @@ -52,6 +53,10 @@ export class IndexingPolicyComponent extends React.Component< this.onComponentUpdate(); } + componentWillUnmount(): void { + this.themeUnsubscribe && this.themeUnsubscribe(); + } + public resetIndexingPolicyEditor = (): void => { if (!this.indexingPolicyEditor) { this.createIndexingPolicyEditor(); @@ -98,59 +103,22 @@ export class IndexingPolicyComponent extends React.Component< language: "json", readOnly: isIndexTransforming(this.props.indexTransformationProgress), ariaLabel: "Indexing Policy", - theme: monacoTheme, + theme: useThemeStore.getState().isDarkMode ? "vs-dark" : "vs", }); if (this.indexingPolicyEditor) { + this.themeUnsubscribe = useThemeStore.subscribe((state) => { + if (this.indexingPolicyEditor) { + const newTheme = state.isDarkMode ? "vs-dark" : "vs"; + monaco.editor.setTheme(newTheme); + } + }); + const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel(); indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this)); this.props.logIndexingPolicySuccessMessage(); } } } - // private async createIndexingPolicyEditor(): Promise { - // const isDarkMode = true; - // const monacoThemeName = "fluent-theme"; - - // if (!this.indexingPolicyDiv.current) return; - - // const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4); - // const monaco = await loadMonaco(); - - // // Safely get Fluent UI theme colors - // const bodyStyles = getComputedStyle(document.body); - // const backgroundColor = bodyStyles.getPropertyValue("--colorNeutralBackground1").trim() || "#1b1a19"; - // const foregroundColor = bodyStyles.getPropertyValue("--colorNeutralForeground1").trim() || "#ffffff"; - - // // Define Monaco theme using Fluent UI colors - // monaco.editor.defineTheme(monacoThemeName, { - // base: isDarkMode ? "vs-dark" : "vs", - // inherit: true, - // rules: [], - // colors: { - // "editor.background": backgroundColor, - // "editor.foreground": foregroundColor, - // "editorCursor.foreground": "#ffcc00", - // "editorLineNumber.foreground": "#aaaaaa", - // "editor.selectionBackground": "#666666", - // "editor.lineHighlightBackground": "#333333" - // } - // }); - - // // Create the editor with the custom theme - // this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, { - // value, - // language: "json", - // readOnly: isIndexTransforming(this.props.indexTransformationProgress), - // ariaLabel: "Indexing Policy", - // theme: monacoThemeName - // }); - - // if (this.indexingPolicyEditor) { - // const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel(); - // indexingPolicyEditorModel?.onDidChangeContent(this.onEditorContentChange.bind(this)); - // this.props.logIndexingPolicySuccessMessage(); - // } - // } private onEditorContentChange = (): void => { diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx index 591e1f137..f49d60967 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx @@ -77,13 +77,14 @@ export const ThroughputBucketsComponent: FC = ( onChange={(newValue) => handleBucketChange(bucket.id, newValue)} showValue={false} label={`Bucket ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`} - styles={{ + styles={{ root: { flex: 2, maxWidth: 400 }, - titleLabel: { - color: bucket.maxThroughputPercentage === 100 ? - "var(--colorNeutralForeground4)" : - "var(--colorNeutralForeground1)" - } + titleLabel: { + color: + bucket.maxThroughputPercentage === 100 + ? "var(--colorNeutralForeground4)" + : "var(--colorNeutralForeground1)", + }, }} disabled={bucket.maxThroughputPercentage === 100} /> diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx index ddf4bc33a..a4f75d10a 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx @@ -41,7 +41,7 @@ import { noLeftPaddingCheckBoxStyle, relaxedSpacingStackProps, saveThroughputWarningMessage, - titleAndInputStackProps + titleAndInputStackProps, } from "../../SettingsRenderUtils"; import { IsComponentDirtyResult, getSanitizedInputValue, isDirty } from "../../SettingsUtils"; import { ToolTipLabelComponent } from "../ToolTipLabelComponent"; @@ -548,7 +548,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< }; private darkThemeMessageBarStyles: Partial = { - root: { + root: { marginTop: "5px", selectors: { "&.ms-MessageBar--severeWarning": { @@ -574,7 +574,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< this.currentThroughputValue() > this.props.softAllowedMaximumThroughput || this.currentThroughputValue() < this.props.minimum; return ( - @@ -590,7 +590,10 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< {/* Column 1: Minimum RU/s */} - + Minimum RU/s @@ -631,7 +634,10 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< {/* Column 3: Maximum RU/s */} - + Maximum RU/s @@ -643,25 +649,25 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< key="auto pilot throughput input" styles={{ ...getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline), - fieldGroup: { - width: 100, + fieldGroup: { + width: 100, height: 28, - backgroundColor: "var(--colorNeutralBackground4)", + backgroundColor: "var(--colorNeutralBackground4)", }, - field: { - fontSize: 14, + field: { + fontSize: 14, fontWeight: 400, - color: "var(--colorNeutralForeground1)", + color: "var(--colorNeutralForeground1)", backgroundColor: "var(--colorNeutralBackground4)", }, root: { selectors: { - "input": { + input: { backgroundColor: "var(--colorNeutralBackground4)", color: "var(--colorNeutralForeground1)", }, }, - } + }, }} disabled={this.overrideWithProvisionedThroughputSettings()} step={AutoPilotUtils.autoPilotIncrementStep} diff --git a/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx b/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx index 178f2f9b5..d5a4b9291 100644 --- a/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx +++ b/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx @@ -54,7 +54,7 @@ export const CostEstimateText: FunctionComponent = ({ if (isAutoscale) { return ( - + {estimatedMonthlyCost} ({currency}){iconWithEstimatedCostDisclaimer}:{" "} diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx index 13fddab04..e60a3c13b 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx @@ -193,7 +193,11 @@ export const ThroughputInput: FunctionComponent = ({
- + {getThroughputLabelText()} {PricingUtils.getRuToolTipText()} @@ -236,14 +240,17 @@ export const ThroughputInput: FunctionComponent = ({ {isAutoscaleSelected && ( - + Your container throughput will automatically scale up to the maximum value you select, from a minimum of 10% of that value. - + Minimum RU/s The minimum RU/s your container will scale to @@ -273,7 +280,7 @@ export const ThroughputInput: FunctionComponent = ({ fontSize: 12, fontWeight: 400, paddingBottom: 6, - color: "var(--colorNeutralForeground1)" + color: "var(--colorNeutralForeground1)", }} > x 10 = @@ -281,7 +288,10 @@ export const ThroughputInput: FunctionComponent = ({ - + Maximum RU/s {getAutoScaleTooltip()} @@ -291,7 +301,7 @@ export const ThroughputInput: FunctionComponent = ({ type="number" styles={{ fieldGroup: { width: 100, height: 27, flexShrink: 0 }, - field: { fontSize: 14, fontWeight: 400 , color: "var(--colorNeutralForeground1)"}, + field: { fontSize: 14, fontWeight: 400, color: "var(--colorNeutralForeground1)" }, }} onChange={(_event, newInput?: string) => onThroughputValueChange(newInput)} step={AutoPilotUtils.autoPilotIncrementStep} @@ -307,7 +317,7 @@ export const ThroughputInput: FunctionComponent = ({ - + Estimate your required RU/s with  = ({ {!isAutoscaleSelected && ( - + Estimate your required RU/s with  = ({ . - + {isDatabase ? "Database" : getCollectionName()} Required RU/s {getAutoScaleTooltip()} diff --git a/src/Explorer/Controls/V9Components/Button/index.tsx b/src/Explorer/Controls/V9Components/Button/index.tsx index ff46a3dd1..fe99ba028 100644 --- a/src/Explorer/Controls/V9Components/Button/index.tsx +++ b/src/Explorer/Controls/V9Components/Button/index.tsx @@ -1,8 +1,4 @@ -import { - Button as FluentButton, - makeStyles, - tokens, -} from "@fluentui/react-components"; +import { Button as FluentButton, makeStyles, tokens } from "@fluentui/react-components"; import * as React from "react"; export type CustomButtonProps = { @@ -39,21 +35,13 @@ const useStyles = makeStyles({ }, }); -export const Button = React.forwardRef( - ({ primary, ...props }, ref) => { - const baseStyles = useStyles(); - const buttonClassName = primary ? baseStyles.primary : baseStyles.button; +export const Button = React.forwardRef(({ primary, ...props }, ref) => { + const baseStyles = useStyles(); + const buttonClassName = primary ? baseStyles.primary : baseStyles.button; - return ( - - ); - } -); + return ( + + ); +}); -// Add display name Button.displayName = "Button"; diff --git a/src/Explorer/DataExplorer.tsx b/src/Explorer/DataExplorer.tsx index 0e5b66517..ac0c399da 100644 --- a/src/Explorer/DataExplorer.tsx +++ b/src/Explorer/DataExplorer.tsx @@ -15,7 +15,7 @@ const useStyles = makeStyles({ }, }); -export const DataExplorer: React.FC = ({ dataExplorer }) => { +export const DataExplorer: React.FC = () => { const styles = useStyles(); return ( diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx index cc5d89668..0c42468de 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx @@ -73,11 +73,11 @@ export const CommandBar: React.FC = ({ container }: Props) => { const { targetDocument } = useFluent(); const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR); const styles = useStyles(); - + const { connectionInfo, isPhoenixNotebooks, isPhoenixFeatures } = useNotebook((state) => ({ connectionInfo: state.connectionInfo, isPhoenixNotebooks: state.isPhoenixNotebooks, - isPhoenixFeatures: state.isPhoenixFeatures + isPhoenixFeatures: state.isPhoenixFeatures, })); if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") { @@ -126,10 +126,7 @@ export const CommandBar: React.FC = ({ container }: Props) => { uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); // Add connection status if needed (using the hook values we got at the top level) - if ( - (isPhoenixNotebooks || isPhoenixFeatures) && - connectionInfo?.status !== ConnectionStatusType.Connect - ) { + if ((isPhoenixNotebooks || isPhoenixFeatures) && connectionInfo?.status !== ConnectionStatusType.Connect) { uiFabricControlButtons.unshift( CommandBarUtil.createConnectionStatus(container, PoolIdType.DefaultPoolId, "connectionStatus"), ); @@ -152,8 +149,8 @@ export const CommandBar: React.FC = ({ container }: Props) => { ":active": { backgroundColor: "var(--colorNeutralBackground3)", color: "var(--colorNeutralForeground1)", - } - } + }, + }, }, menuIcon: { color: "var(--colorNeutralForeground1)", @@ -165,7 +162,7 @@ export const CommandBar: React.FC = ({ container }: Props) => { link: { backgroundColor: "var(--colorNeutralBackground1)", color: "var(--colorNeutralForeground1)", - } + }, }; const allButtons = staticButtons.concat(contextButtons).concat(controlButtons); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index ad21c0932..94958c029 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -1,3 +1,4 @@ +import { OpenFullScreen } from "Explorer/OpenFullScreen"; import { KeyboardAction } from "KeyboardShortcuts"; import { isDataplaneRbacSupported } from "Utils/APITypeUtils"; import * as React from "react"; @@ -25,12 +26,12 @@ import { useSidePanel } from "../../../hooks/useSidePanel"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import Explorer from "../../Explorer"; import { useNotebook } from "../../Notebook/useNotebook"; -import { OpenFullScreen } from "../../OpenFullScreen"; import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane"; import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane"; import { SettingsPane, useDataPlaneRbac } from "../../Panes/SettingsPane/SettingsPane"; import { useDatabases } from "../../useDatabases"; import { SelectedNodeState, useSelectedNode } from "../../useSelectedNode"; +import { ThemeToggleButton } from "./ThemeToggleButton"; let counter = 0; @@ -175,6 +176,7 @@ export function createContextCommandBarButtons( export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { const buttons: CommandButtonComponentProps[] = [ + ThemeToggleButton(), { iconSrc: SettingsIcon, iconAlt: "Settings", diff --git a/src/Explorer/Menus/CommandBar/ThemeToggleButton.tsx b/src/Explorer/Menus/CommandBar/ThemeToggleButton.tsx new file mode 100644 index 000000000..01ed852aa --- /dev/null +++ b/src/Explorer/Menus/CommandBar/ThemeToggleButton.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; +import MoonBlueIcon from "../../../../images/moon-blue.svg"; +import SunBlueIcon from "../../../../images/sun-blue.svg"; +import { useThemeStore } from "../../../hooks/useTheme"; +import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; + +export const ThemeToggleButton = (): CommandButtonComponentProps => { + const [darkMode, setDarkMode] = React.useState(useThemeStore.getState().isDarkMode); + + React.useEffect(() => { + const unsubscribe = useThemeStore.subscribe((state) => { + setDarkMode(state.isDarkMode); + }); + return unsubscribe; + }, []); + + const tooltipText = darkMode ? "Switch to Light Theme" : "Switch to Dark Theme"; + + return { + iconSrc: darkMode ? SunBlueIcon : MoonBlueIcon, + iconAlt: "Theme Toggle", + onCommandClick: useThemeStore.getState().toggleTheme, + commandButtonLabel: undefined, + ariaLabel: tooltipText, + tooltipText: tooltipText, + hasPopup: false, + disabled: false, + }; +}; \ No newline at end of file diff --git a/src/Explorer/OpenFullScreen.tsx b/src/Explorer/OpenFullScreen.tsx index 5474161cd..ec778e0a5 100644 --- a/src/Explorer/OpenFullScreen.tsx +++ b/src/Explorer/OpenFullScreen.tsx @@ -6,7 +6,7 @@ export const OpenFullScreen: React.FunctionComponent = () => { <>
- + Open this database account in a new browser tab with Cosmos DB Explorer. You can connect using your Microsoft account or a connection string. diff --git a/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx index c0151aae1..b70f1f1d9 100644 --- a/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx +++ b/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx @@ -653,26 +653,26 @@ export class AddCollectionPanel extends React.Component
@@ -1073,7 +1053,11 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ ariaLabel="Enable cross partition query" checked={crossPartitionQueryEnabled} onChange={() => setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)} - label="Enable cross-partition query" + onRenderLabel={() => ( + + Enable cross-partition query + + )} />
@@ -1105,7 +1089,11 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ ariaLabel="EnableQueryControl" checked={queryControlEnabled} onChange={() => setQueryControlEnabled(!queryControlEnabled)} - label="Enable query control" + onRenderLabel={() => ( + + Enable query control + + )} />
@@ -1139,6 +1127,11 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ onValidate={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) || maxDegreeOfParallelism)} ariaLabel="Max degree of parallelism" label="Max degree of parallelism" + styles={{ + label: { + color: "var(--colorNeutralForeground1)" + } + }} /> @@ -1208,7 +1201,11 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ ariaLabel="Enable sample db for query exploration" checked={copilotSampleDBEnabled} onChange={handleSampleDatabaseChange} - label="Enable sample database" + onRenderLabel={() => ( + + Enable sample database + + )} /> @@ -1248,12 +1245,12 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ selectors: { ":hover": { backgroundColor: "var(--colorBrandBackgroundHover)", - color: "var(--colorNeutralForegroundOnBrand)" + color: "var(--colorNeutralForegroundOnBrand)", }, ":active": { backgroundColor: "var(--colorBrandBackgroundPressed)", - color: "var(--colorNeutralForegroundOnBrand)" - } + color: "var(--colorNeutralForegroundOnBrand)", + }, }, }, }} diff --git a/src/Explorer/SplashScreen/SplashScreen.less b/src/Explorer/SplashScreen/SplashScreen.less index d3844f0eb..43fc82b9b 100644 --- a/src/Explorer/SplashScreen/SplashScreen.less +++ b/src/Explorer/SplashScreen/SplashScreen.less @@ -1,30 +1,5 @@ -// @import "../../../less/Common/Constants"; - -// .splashScreenContainer { -// width: 100%; -// overflow-y: scroll; -// overflow-x: hidden; - -// .splashScreen { -// .flex-display(); -// .flex-direction(); -// text-align: left; -// margin: auto; -// padding-left: 21px; -// padding-right: 16px; -// max-width: 1168px; - -// > .title { -// position: relative; // To attach FeaturePanelLauncher as absolute -// color: @BaseHigh; -// font-size: 48px; -// padding-left: 0px; -// margin: 16px auto; -// text-align: center; -// } > .subtitle { - // color: @BaseHigh; font-size: 18px; padding-left: 0px; margin: 0px auto; @@ -46,148 +21,3 @@ } } -// .mainButtonsContainer { -// .flex-display(); -// text-align: center; -// cursor: pointer; -// margin: 40px auto; -// width: 84%; - -// > .mainButton { -// min-width: 124px; -// max-width: 296px; -// padding: 32px 16px; -// background-color: @BaseLight; -// border: 1px solid #949494; -// box-sizing: border-box; -// box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); -// border-radius: 4px; - -// > .legendContainer { -// margin-left: 16px; -// text-align: left; - -// .legend { -// font-family: @SemiboldFont; -// font-size: 18px; -// } - -// .description { -// font-size: 10px; -// } - -// .newDescription { -// font-size: 13px; -// } -// } -// } - -// > :nth-child(n + 2) { -// margin-left: 32px; -// } -// } - -// .moreStuffContainer { -// .flex-display(); -// justify-content: space-between; - -// .moreStuffColumn { -// flex-grow: 1; -// flex-basis: 0; -// min-width: 124px; -// max-width: 296px; - -// > .title { -// font-size: 18px; -// font-family: @SemiboldFont; -// color: @BaseDark; -// padding: 0px; -// margin-bottom: 16px; -// } - -// > ul { -// list-style: none; -// padding-left: 0px; -// margin-bottom: 0px; - -// li { -// padding: @DefaultSpace; -// .flex-display(); -// align-items: flex-start; - -// > img { -// margin-right: @DefaultSpace; -// width: 24px; -// height: 24px; -// } - -// .oneLineContent { -// margin-top: 4px; -// } - -// .description { -// font-size: 10px; -// color: @BaseMediumHigh; -// } -// } -// } - -// .tipContainer { -// padding: 8px 16px; -// width: 100%; -// cursor: pointer; -// .flex-display(); -// .flex-direction(); - -// > .title { -// color: @BaseDark; -// padding: 0px; -// font-size: 12px; -// } -// > .description { -// color: @BaseDark; -// } - -// &:not(:hover):not(:focus) { -// background-color: @BaseLow; -// } -// } - -// &.commonTasks { -// li { -// cursor: pointer; -// } -// } - -// &.tipsContainer { -// li { -// margin: 2px 0px; -// } -// } -// } -// } - -// .focusable { -// &:hover { -// .hover(); -// } - -// &:focus { -// .focus(); -// } - -// &:active { -// .active(); -// } -// } - -// .notebookSplashScreenItem { -// padding: 12px 0 12px 12px; - -// .itemText { -// margin-left: 12px; -// font-family: @SemiboldFont; -// } -// } -// } -// } diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index 60743726b..bbbd7d528 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -35,7 +35,6 @@ import CollectionIcon from "../../../images/tree-collection.svg"; import * as Constants from "../../Common/Constants"; import { userContext } from "../../UserContext"; import { getCollectionName } from "../../Utils/APITypeUtils"; -import { useTheme } from "../../hooks/useTheme"; import Explorer from "../Explorer"; import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity"; import { useNotebook } from "../Notebook/useNotebook"; @@ -61,18 +60,15 @@ const useStyles = makeStyles({ display: "flex", flexDirection: "column", alignItems: "center", - // justifyContent: "center", minHeight: "100vh", backgroundColor: "var(--colorNeutralBackground1)", color: "var(--colorNeutralForeground1)", }, splashScreen: { display: "flex", - // overflow: "scroll", flexDirection: "column", alignItems: "center", textAlign: "left", - // ...shorthands.padding("40px") }, title: { fontSize: "48px", @@ -140,7 +136,6 @@ const useStyles = makeStyles({ moreStuffColumn: { display: "flex", flexDirection: "column", - // justifyContent:"space-between" }, columnTitle: { fontSize: "20px", @@ -166,9 +161,6 @@ const useStyles = makeStyles({ export const SplashScreen: React.FC = ({ explorer }) => { const styles = useStyles(); - // isDarkMode is used for conditional styling in the component - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { isDarkMode } = useTheme(); const container = explorer; const subscriptions: Array<{ dispose: () => void }> = []; diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index 0c54afa7c..3026b71ef 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -24,7 +24,7 @@ import { Allotment } from "allotment"; import { useClientWriteEnabled } from "hooks/useClientWriteEnabled"; import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot"; import { TabsState, useTabs } from "hooks/useTabs"; -import { monacoTheme } from "hooks/useTheme"; +import { useMonacoTheme } from "hooks/useTheme"; import React, { Fragment, createRef } from "react"; import "react-splitter-layout/lib/index.css"; import { format } from "react-string-format"; @@ -126,6 +126,7 @@ interface IQueryTabStates { export const QueryTabCopilotComponent = (props: IQueryTabComponentProps): any => { const styles = useQueryTabStyles(); + const monacoTheme = useMonacoTheme(); const copilotStore = useCopilotStore(); const isSampleCopilotActive = useSelectedNode.getState().isQueryCopilotCollectionSelected(); @@ -137,16 +138,18 @@ export const QueryTabCopilotComponent = (props: IQueryTabComponentProps): any => isSampleCopilotActive: isSampleCopilotActive, copilotStore: copilotStore, }; - return ; + return ; }; export const QueryTabComponent = (props: IQueryTabComponentProps): any => { const styles = useQueryTabStyles(); - return ; + const monacoTheme = useMonacoTheme(); + return ; }; type QueryTabComponentImplProps = IQueryTabComponentProps & { styles: QueryTabStyles; + monacoTheme: string; }; // Inner (legacy) class component. We only use this component via one of the two functional components above (since we need to use the `useQueryTabStyles` hook). @@ -761,7 +764,7 @@ class QueryTabComponentImpl extends React.Component this.onChangeContent(newContent)} onContentSelected={(selectedContent: string, selection: monaco.Selection) => this.onSelectedContent(selectedContent, selection) diff --git a/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx b/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx index 30d64e750..e981104ae 100644 --- a/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx +++ b/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx @@ -3,7 +3,7 @@ import { Label, TextField } from "@fluentui/react"; import { FluentProvider, webDarkTheme, webLightTheme } from "@fluentui/react-components"; import { KeyboardAction } from "KeyboardShortcuts"; import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils"; -import { isDarkMode } from "hooks/useTheme"; +import { useThemeStore } from "hooks/useTheme"; import React, { Component } from "react"; import DiscardIcon from "../../../images/discard.svg"; import SaveIcon from "../../../images/save-cosmos.svg"; @@ -259,7 +259,7 @@ export default class UserDefinedFunctionTabContent extends Component< render(): JSX.Element { const { udfId, udfBody, isUdfIdEditable } = this.state; - const currentTheme = isDarkMode ? webDarkTheme : webLightTheme; + const currentTheme = useThemeStore.getState().isDarkMode ? webDarkTheme : webLightTheme; return (
diff --git a/src/Main.tsx b/src/Main.tsx index f96fb68d0..60a30a912 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -63,7 +63,7 @@ import { appThemeFabric } from "./Platform/Fabric/FabricTheme"; import "./Shared/appInsights"; import { useConfig } from "./hooks/useConfig"; import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer"; -import { isDarkMode } from "./hooks/useTheme"; +import { useThemeStore } from "./hooks/useTheme"; import "./less/DarkModeMenus.less"; import "./less/ThemeSystem.less"; // Initialize icons before React is loaded @@ -153,7 +153,18 @@ const App = (): JSX.Element => { }; const Root: React.FC = () => { + // Use React state to track isDarkMode and subscribe to changes + const [isDarkMode, setIsDarkMode] = React.useState(useThemeStore.getState().isDarkMode); const currentTheme = isDarkMode ? webDarkTheme : webLightTheme; + + // Subscribe to theme changes + React.useEffect(() => { + return useThemeStore.subscribe( + (state) => { + setIsDarkMode(state.isDarkMode); + } + ); + }, []); useEffect(() => { if (isDarkMode) { diff --git a/src/hooks/useTheme.tsx b/src/hooks/useTheme.tsx index d5370c454..087e6d901 100644 --- a/src/hooks/useTheme.tsx +++ b/src/hooks/useTheme.tsx @@ -1,5 +1,30 @@ import { useFluent } from "@fluentui/react-components"; import React, { createContext, FC, ReactNode, useEffect, useState } from "react"; +import create from "zustand"; + +interface ThemeSettings { + mode: number; +} + +interface FxsTheme { + (): ThemeSettings; + subscribe: (context: null, callback: (update: ThemeSettings) => void) => void; +} + +interface MsPortalFxSettings { + "fxs-theme"?: FxsTheme; + [key: string]: unknown; +} + +declare global { + interface Window { + MsPortalFx?: { + Services: { + getSettings: () => Promise; + }; + }; + } +} interface ThemeContextType { theme: "Light" | "Dark"; @@ -17,19 +42,129 @@ export const CustomThemeProvider: FC = ({ children, theme }) const isDarkMode = theme === "Dark"; return {children}; }; -export const isDarkMode = true; -export const monacoTheme = isDarkMode ? "vs-dark" : "vs"; + +export interface ThemeStore { + isDarkMode: boolean; + themeMode: number; + toggleTheme: () => void; +} + +export const useThemeStore = create((set) => ({ + isDarkMode: false, + themeMode: 0, + toggleTheme: () => + set((state) => { + const newIsDarkMode = !state.isDarkMode; + const newThemeMode = newIsDarkMode ? 1 : 0; + + if (newIsDarkMode) { + document.body.classList.add("isDarkMode"); + } else { + document.body.classList.remove("isDarkMode"); + } + + // Save to localStorage for persistence + localStorage.setItem("cosmos-explorer-theme", newIsDarkMode ? "dark" : "light"); + localStorage.setItem("cosmos-explorer-theme-mode", String(newThemeMode)); + + return { + isDarkMode: newIsDarkMode, + themeMode: newThemeMode, + }; + }), +})); + +// Initialize the theme from localStorage or MsPortalFx if available +if (typeof window !== "undefined") { + // Try to initialize from MsPortalFx.Services if available + try { + if (window.MsPortalFx && window.MsPortalFx.Services) { + window.MsPortalFx.Services.getSettings() + .then((settings: MsPortalFxSettings) => { + if (settings["fxs-theme"]) { + const theme = settings["fxs-theme"]; + + // Initial theme value + const initialTheme = theme(); + if (initialTheme && typeof initialTheme.mode === "number") { + const isDark = initialTheme.mode === 1; + useThemeStore.setState({ + isDarkMode: isDark, + themeMode: initialTheme.mode, + }); + + if (isDark) { + document.body.classList.add("isDarkMode"); + } else { + document.body.classList.remove("isDarkMode"); + } + } + + theme.subscribe(null, (themeUpdate: ThemeSettings) => { + if (themeUpdate && typeof themeUpdate.mode === "number") { + const isDark = themeUpdate.mode === 1; + useThemeStore.setState({ + isDarkMode: isDark, + themeMode: themeUpdate.mode, + }); + + if (isDark) { + document.body.classList.add("isDarkMode"); + } else { + document.body.classList.remove("isDarkMode"); + } + } + }); + } + }) + .catch(() => { + fallbackToLocalStorage(); + }); + } else { + fallbackToLocalStorage(); + } + } catch (error) { + fallbackToLocalStorage(); + } +} + +function fallbackToLocalStorage() { + const savedTheme = localStorage.getItem("cosmos-explorer-theme"); + const savedThemeMode = localStorage.getItem("cosmos-explorer-theme-mode"); + + if (savedTheme || savedThemeMode) { + const isDark = savedTheme === "dark" || savedThemeMode === "1"; + const themeMode = isDark ? 1 : 0; + + useThemeStore.setState({ + isDarkMode: isDark, + themeMode: themeMode, + }); + + if (isDark) { + document.body.classList.add("isDarkMode"); + } + } +} + +// Dynamic exports that use the theme store +export const isDarkMode = () => useThemeStore.getState().isDarkMode; + +export const useMonacoTheme = () => { + const { isDarkMode } = useThemeStore(); + return isDarkMode ? "vs-dark" : "vs"; +}; + +export const monacoTheme = () => (useThemeStore.getState().isDarkMode ? "vs-dark" : "vs"); export const useTheme = () => { const { targetDocument } = useFluent(); const context = React.useContext(ThemeContext); const [isDarkMode, setIsDarkMode] = useState(() => { - // First check if we're in a theme context if (context) { return context.isDarkMode; } - // Fallback to checking body class return targetDocument?.body.classList.contains("isDarkMode") ?? true; }); @@ -38,20 +173,16 @@ export const useTheme = () => { return undefined; } const checkTheme = () => { - // First check if we're in a theme context if (context) { setIsDarkMode(context.isDarkMode); return; } - // Fallback to checking body class const hasDarkMode = targetDocument.body.classList.contains("isDarkMode"); setIsDarkMode(hasDarkMode); }; - // Initial check checkTheme(); - // Create a MutationObserver to watch for class changes const observer = new MutationObserver(() => { checkTheme(); });