Initial Fabric support (#1607)

* Add Platform.Fabric to run in context of Fabric

* Use separate StyleConstants

We want to have more flexibility with Styles at runtime
but Constants depend on ConfigContext and therefore
get loaded very early at startup.

* Add Fabric specific styles and Fluent theme

documentDBFabric.less contains all styles for Fabric.
We use React.lazy to import them conditionally at
runtime preventing webpack from preprocessing
them into main.css.

* Restyle CommandBar for Fabric
with more roundness and native colors.

* Disable Notebooks when running in Fabric

* Disable Synapse and Scripts commands for Fabric

* Fix code formatting issues

* Fetch encrypted token from sessionStorage for fabric platform

* Fix Tabs style

* Dark refresh icons for Fabric

* Use new ResourceTree2 for Fabric

* Fluent tree should have a fixed width
otherwise the action buttons jump around on hover.

* Disable remaining Script actions in Fabric

* Revert accidentally committed change
which broke a test

* Fix cross-origin error second try

* Adjust @FabrixBoxMargin css

* Hide Database Scale node on Fabric

* Remove all Collection child nodes on Fabric

* Add a comment about why we need FabricPlatform.tsx

* Fix equality checks

* Fix more Colors for Fabric

* Switch resource tree to "medium" size

* Fix formatting again

* Fix formatting again

* Disable no-var-requires error on some intended var import.

* Increase memory limit for build

* Use standard Segoe UI font for Fabric

* Improve Tabs design for Fabric

* Fix active Tab style bug in Portal
introduced with 39a7765aef

---------

Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com>
This commit is contained in:
Vsevolod Kukol 2023-09-15 17:33:27 +02:00 committed by GitHub
parent c2d2ff3dee
commit 379395567c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 675 additions and 84 deletions

View File

@ -85,6 +85,8 @@ jobs:
path: .cache path: .cache
key: ${{ runner.os }}-build-cache key: ${{ runner.os }}-build-cache
- run: npm run pack:prod - run: npm run pack:prod
env:
NODE_OPTIONS: '--max-old-space-size=4096'
- run: cp -r ./Contracts ./dist/contracts - run: cp -r ./Contracts ./dist/contracts
- run: cp -r ./configs ./dist/configs - run: cp -r ./configs ./dist/configs
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2

View File

@ -10,6 +10,7 @@
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif; @DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
@SemiboldFont: "Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif; @SemiboldFont: "Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
@GrayScale: "grayscale()"; @GrayScale: "grayscale()";
@NoColor: "brightness(0) saturate(100%)";
@xSmallFontSize: 4px; @xSmallFontSize: 4px;
@smallFontSize: 8px; @smallFontSize: 8px;
@ -147,14 +148,41 @@
// CommandBar // CommandBar
@CommandBarButtonHeight: 40px; @CommandBarButtonHeight: 40px;
/**********************************************************************************
Portal Consts
/**********************************************************************************/
@PortalAccentMediumHigh: #0058ad;
@PortalAccentMedium: #004e87;
@PortalAccentLight: #eef7ff;
@PortalAccentAccentExtra: #ddf0ff;
/**********************************************************************************
Fabric Consts
/**********************************************************************************/
@FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
@FabricBoxBorderRadius: 8px;
@FabricBoxBorderShadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
@FabricBoxMargin: 4px 3px 4px 3px;
@FabricAccentMediumHigh: #0c695a;
@FabricAccentMedium: #117865;
@FabricAccentLight: #f5f5f5;
@FabricAccentExtra: #ebebeb;
@FabricButtonBorderRadius: 4px;
/********************************************************************************** /**********************************************************************************
Common Flex Property Common Flex Property
/**********************************************************************************/ /**********************************************************************************/
.flex-display(@display: flex) { .flex-display(@display: flex) {
display: ~"-webkit-@{display}"; display:~"-webkit-@{display}";
display: ~"-ms-@{display}box"; // IE10 uses -ms-flexbox display:~"-ms-@{display}box"; // IE10 uses -ms-flexbox
display: ~"-ms-@{display}"; // IE11 display:~"-ms-@{display}"; // IE11
display: @display; display: @display;
} }
@ -168,13 +196,15 @@
High contrast mode active High contrast mode active
**************************************************************************************/ **************************************************************************************/
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { @media all and (-ms-high-contrast: none),
(-ms-high-contrast: active) {
.selectedRadio, .selectedRadio,
.selectedRadio:hover, .selectedRadio:hover,
.selectedRadio:active, .selectedRadio:active,
.selectedRadio.dirty, .selectedRadio.dirty,
.tab [type="radio"]:checked ~ label, .tab [type="radio"]:checked~label,
.tab [type="radio"]:checked ~ label:hover { .tab [type="radio"]:checked~label:hover {
-ms-high-contrast-adjust: none; -ms-high-contrast-adjust: none;
-webkit-text-fill-color: HighlightText; -webkit-text-fill-color: HighlightText;
color: HighlightText; color: HighlightText;
@ -183,6 +213,7 @@
} }
.queryMetricsSummaryTuple { .queryMetricsSummaryTuple {
th, th,
td { td {
&:nth-child(2) { &:nth-child(2) {

View File

@ -2646,6 +2646,11 @@ a:link {
width: @ActiveTabWidth; width: @ActiveTabWidth;
} }
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .tabNavText {
font-weight: bolder;
border-bottom: 2px solid rgba(0,120,212,1);
}
.nav-tabs > li.active:focus > .tabNavContentContainer { .nav-tabs > li.active:focus > .tabNavContentContainer {
.focus(); .focus();
} }

207
less/documentDBFabric.less Normal file
View File

@ -0,0 +1,207 @@
@import "./Common/Constants";
html {
font-family: @FabricFont;
}
body {
font-family: @FabricFont;
background-color: #f5f5f5;
}
a {
color: @FabricAccentMedium;
text-decoration: none;
}
a:hover,
a:focus {
color: @FabricAccentMediumHigh;
text-decoration: underline;
}
#divExplorer {
background-color: #f5f5f5;
}
.resourceTreeAndTabs {
border-radius: @FabricBoxBorderRadius;
box-shadow: @FabricBoxBorderShadow;
margin: @FabricBoxMargin;
margin-top: 4px;
background-color: #ffffff;
}
.tabsManagerContainer {
background-color: #fafafa
}
.nav-tabs-margin {
padding-top: 8px;
background-color: #fafafa
}
.commandBarContainer {
background-color: #ffffff;
border-bottom: none;
border-radius: @FabricBoxBorderRadius;
box-shadow: @FabricBoxBorderShadow;
margin: @FabricBoxMargin;
padding-top: 2px;
}
.dividerContainer {
padding: @SmallSpace 0px @SmallSpace 0px;
.flex-display();
span {
border-left: @ButtonBorderWidth solid @BaseMedium;
margin: 0 10px 0 10px;
}
}
.nav-tabs>li>.tabNavContentContainer>.tab_Content:hover {
border-bottom: 2px solid #e0e0e0;
}
.nav-tabs>li.active>.tabNavContentContainer>.tab_Content,
.nav-tabs>li.active>.tabNavContentContainer>.tab_Content:hover {
border-bottom: 2px solid @FabricAccentMedium;
}
.tabNavContentContainer {
padding: @SmallSpace 0px @SmallSpace 0px;
&:hover {
background-color: transparent;
border-color: transparent;
}
.tab_Content {
border-right: 0px none transparent;
margin: 0px @SmallSpace 0px @SmallSpace;
width: calc(@TabsWidth - (@SmallSpace * 2));
padding-bottom: @SmallSpace;
.statusIconContainer {
margin-left: 0px;
}
.tabIconSection {
.cancelButton {
padding: 0px 0px 0px @SmallSpace;
&:hover {
background-color: transparent;
}
&:focus {
background-color: transparent;
}
&:active {
background-color: transparent;
}
}
}
}
}
.resourceTree {
padding: 12px;
}
.accordion {
.accordionItemContainer {
.accordionItemHeader {
border-radius: 4px;
}
}
}
.treeComponent {
.nodeItem {
&:focus {
outline: 2px @FabricAccentMedium;
}
.treeNodeHeader {
padding: 5px 5px;
border-radius: 4px;
&:hover {
background-color: @FabricAccentLight;
.treeMenuEllipsis {
opacity: 1;
}
}
&.showingMenu {
background-color: #eee;
}
}
.selected {
&>.treeNodeHeader {
background-color: @FabricAccentExtra;
}
}
}
}
.dataExplorerErrorConsoleContainer {
border-radius: @FabricBoxBorderRadius;
box-shadow: @FabricBoxBorderShadow;
margin: @FabricBoxMargin;
width: auto;
align-self: auto;
}
.filterbtnstyle {
background: #fff;
color: #000;
border: solid 1px #d1d1d1;
border-radius: 4px;
}
.filterbtnstyle:hover {
background: @FabricAccentLight;
color: #000;
border: solid 1px #d1d1d1;
}
.filterbtnstyle:active {
background: @FabricAccentLight;
color: #000;
border: solid 1px #d1d1d1;
}
.filterbtnstyle:focus {
background: #fff;
color: #000;
border: solid 1px #d1d1d1;
}
.gridRowSelected .tabdocumentsGridElement:hover {
background-color: @FabricAccentLight !important;
}
.refreshcol {
filter: brightness(0) saturate(100%);
}
.refreshcol1 {
filter: brightness(0) saturate(100%);
}
.fileImportImg img {
filter: brightness(0) saturate(100%);
}

View File

@ -365,9 +365,6 @@ export const EmulatorMasterKey =
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")] //[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
// A variable @MyVariable defined in Constants.less is accessible as StyleConstants.MyVariable
export const StyleConstants = require("less-vars-loader!../../less/Common/Constants.less");
export class Notebook { export class Notebook {
public static readonly defaultBasePath = "./notebooks"; public static readonly defaultBasePath = "./notebooks";
public static readonly heartbeatDelayMs = 60000; public static readonly heartbeatDelayMs = 60000;

View File

@ -5,8 +5,10 @@ import refreshImg from "../../images/refresh-cosmos.svg";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree"; import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
import { ResourceTree2 } from "../Explorer/Tree2/ResourceTree";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { getApiShortDisplayName } from "../Utils/APITypeUtils"; import { getApiShortDisplayName } from "../Utils/APITypeUtils";
import { Platform, configContext } from "./../ConfigContext";
import { NormalizedEventKey } from "./Constants"; import { NormalizedEventKey } from "./Constants";
export interface ResourceTreeContainerProps { export interface ResourceTreeContainerProps {
@ -76,10 +78,10 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
<ResourceTokenTree /> <ResourceTokenTree />
) : userContext.features.enableKoResourceTree ? ( ) : userContext.features.enableKoResourceTree ? (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" /> <div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
) : configContext.platform === Platform.Fabric ? (
<ResourceTree2 container={container} />
) : ( ) : (
<ResourceTree container={container} /> <ResourceTree container={container} />
// Uncomment the following line to use the fluent ui tree
// <ResourceTree2 container={container} />
)} )}
</div> </div>
{/* Collections Window - End */} {/* Collections Window - End */}

View File

@ -0,0 +1,18 @@
import { Platform, configContext } from "../ConfigContext";
// eslint-disable-next-line @typescript-eslint/no-var-requires
export const StyleConstants = require("less-vars-loader!../../less/Common/Constants.less");
export function updateStyles(): void {
if (configContext.platform === Platform.Fabric) {
StyleConstants.AccentMediumHigh = StyleConstants.FabricAccentMediumHigh;
StyleConstants.AccentMedium = StyleConstants.FabricAccentMedium;
StyleConstants.AccentLight = StyleConstants.FabricAccentLight;
StyleConstants.AccentAccentExtra = StyleConstants.FabricAccentMediumHigh;
} else {
StyleConstants.AccentMediumHigh = StyleConstants.PortalAccentMediumHigh;
StyleConstants.AccentMedium = StyleConstants.PortalAccentMedium;
StyleConstants.AccentLight = StyleConstants.PortalAccentLight;
StyleConstants.AccentAccentExtra = StyleConstants.PortalAccentMediumHigh;
}
}

View File

@ -16,6 +16,7 @@ export enum Platform {
Portal = "Portal", Portal = "Portal",
Hosted = "Hosted", Hosted = "Hosted",
Emulator = "Emulator", Emulator = "Emulator",
Fabric = "Fabric",
} }
export interface ConfigContext { export interface ConfigContext {
@ -187,6 +188,7 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
console.error(`Invalid platform query parameter: ${platform}`); console.error(`Invalid platform query parameter: ${platform}`);
break; break;
case Platform.Portal: case Platform.Portal:
case Platform.Fabric:
case Platform.Hosted: case Platform.Hosted:
case Platform.Emulator: case Platform.Emulator:
updateConfigContext({ platform }); updateConfigContext({ platform });

View File

@ -1,7 +1,7 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import * as Plotly from "plotly.js-cartesian-dist-min"; import * as Plotly from "plotly.js-cartesian-dist-min";
import { StyleConstants } from "../../Common/Constants";
import { sendCachedDataMessage, sendReadyMessage } from "../../Common/MessageHandler"; import { sendCachedDataMessage, sendReadyMessage } from "../../Common/MessageHandler";
import { StyleConstants } from "../../Common/StyleConstants";
import { MessageTypes } from "../../Contracts/ExplorerContracts"; import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation"; import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import "./Heatmap.less"; import "./Heatmap.less";

View File

@ -18,6 +18,7 @@ import * as ViewModels from "../Contracts/ViewModels";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils"; import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
import { useSidePanel } from "../hooks/useSidePanel"; import { useSidePanel } from "../hooks/useSidePanel";
import { Platform, configContext } from "./../ConfigContext";
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent"; import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
import Explorer from "./Explorer"; import Explorer from "./Explorer";
import { useNotebook } from "./Notebook/useNotebook"; import { useNotebook } from "./Notebook/useNotebook";
@ -99,7 +100,10 @@ export const createCollectionContextMenuButton = (
}); });
} }
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") { if (
configContext.platform !== Platform.Fabric &&
(userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
) {
items.push({ items.push({
iconSrc: AddStoredProcedureIcon, iconSrc: AddStoredProcedureIcon,
onClick: () => { onClick: () => {

View File

@ -23,9 +23,9 @@ import * as React from "react";
import * as _ from "underscore"; import * as _ from "underscore";
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png"; import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { QueriesClient } from "../../../Common/QueriesClient"; import { QueriesClient } from "../../../Common/QueriesClient";
import { StyleConstants } from "../../../Common/StyleConstants";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";

View File

@ -23,7 +23,8 @@ import {
Text, Text,
} from "@fluentui/react"; } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { StyleConstants, Urls } from "../../../Common/Constants"; import { Urls } from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/StyleConstants";
import { hoursInAMonth } from "../../../Shared/Constants"; import { hoursInAMonth } from "../../../Shared/Constants";
import { import {
computeRUUsagePriceHourly, computeRUUsagePriceHourly,

View File

@ -18,6 +18,7 @@ import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squa
import TriangleDownIcon from "../../../../images/Triangle-down.svg"; import TriangleDownIcon from "../../../../images/Triangle-down.svg";
import TriangleRightIcon from "../../../../images/Triangle-right.svg"; import TriangleRightIcon from "../../../../images/Triangle-right.svg";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/StyleConstants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@ -237,7 +238,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
private renderContextMenuButton(node: TreeNode): JSX.Element { private renderContextMenuButton(node: TreeNode): JSX.Element {
const menuItemLabel = "More"; const menuItemLabel = "More";
const buttonStyles: Partial<IButtonStyles> = { const buttonStyles: Partial<IButtonStyles> = {
rootFocused: { outline: `1px dashed ${Constants.StyleConstants.FocusColor}` }, rootFocused: { outline: `1px dashed ${StyleConstants.FocusColor}` },
}; };
return ( return (

View File

@ -1,7 +1,7 @@
import { Link } from "@fluentui/react/lib/Link"; import { Link } from "@fluentui/react/lib/Link";
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { sendMessage } from "Common/MessageHandler"; import { sendMessage } from "Common/MessageHandler";
import { Platform } from "ConfigContext"; import { Platform, configContext } from "ConfigContext";
import { MessageTypes } from "Contracts/ExplorerContracts"; import { MessageTypes } from "Contracts/ExplorerContracts";
import { IGalleryItem } from "Juno/JunoClient"; import { IGalleryItem } from "Juno/JunoClient";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation"; import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
@ -1343,9 +1343,10 @@ export default class Explorer {
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount // TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
const isNotebookEnabled = const isNotebookEnabled =
userContext.features.notebooksDownBanner || configContext.platform !== Platform.Fabric &&
useNotebook.getState().isPhoenixNotebooks || (userContext.features.notebooksDownBanner ||
useNotebook.getState().isPhoenixFeatures; useNotebook.getState().isPhoenixNotebooks ||
useNotebook.getState().isPhoenixFeatures);
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled); useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
useNotebook useNotebook
.getState() .getState()

View File

@ -8,7 +8,9 @@ import { useNotebook } from "Explorer/Notebook/useNotebook";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import * as React from "react"; import * as React from "react";
import create, { UseStore } from "zustand"; import create, { UseStore } from "zustand";
import { ConnectionStatusType, PoolIdType, StyleConstants } from "../../../Common/Constants"; import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/StyleConstants";
import { Platform, configContext } from "../../../ConfigContext";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
@ -84,15 +86,27 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
); );
} }
const rootStyle =
configContext.platform === Platform.Fabric
? {
root: {
backgroundColor: "transparent",
padding: "0px 14px 0px 14px",
},
}
: {
root: {
backgroundColor: backgroundColor,
},
};
return ( return (
<div className="commandBarContainer"> <div className="commandBarContainer">
<FluentCommandBar <FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands" ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)} items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricControlButtons} farItems={uiFabricControlButtons}
styles={{ styles={rootStyle}
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }} overflowButtonProps={{ ariaLabel: "More commands" }}
/> />
</div> </div>

View File

@ -54,7 +54,11 @@ export function createStaticCommandBarButtons(
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
buttons.push(newCollectionBtn); buttons.push(newCollectionBtn);
if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") { if (
configContext.platform !== Platform.Fabric &&
userContext.apiType !== "Tables" &&
userContext.apiType !== "Cassandra"
) {
const addSynapseLink = createOpenSynapseLinkDialogButton(container); const addSynapseLink = createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) { if (addSynapseLink) {
@ -257,7 +261,9 @@ export function createDivider(): CommandButtonComponentProps {
} }
function areScriptsSupported(): boolean { function areScriptsSupported(): boolean {
return userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; return (
configContext.platform !== Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
);
} }
function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps { function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps {

View File

@ -9,7 +9,9 @@ import {
import * as React from "react"; import * as React from "react";
import _ from "underscore"; import _ from "underscore";
import ChevronDownIcon from "../../../../images/Chevron_down.svg"; import ChevronDownIcon from "../../../../images/Chevron_down.svg";
import { PoolIdType, StyleConstants } from "../../../Common/Constants"; import { PoolIdType } from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/StyleConstants";
import { configContext, Platform } from "../../../ConfigContext";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
@ -24,11 +26,14 @@ import { MemoryTracker } from "./MemoryTrackerComponent";
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => { export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
const buttonHeightPx = StyleConstants.CommandBarButtonHeight; const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
const hoverColor =
configContext.platform == Platform.Fabric ? StyleConstants.FabricAccentLight : StyleConstants.AccentLight;
const getFilter = (isDisabled: boolean): string => { const getFilter = (isDisabled: boolean): string => {
if (isDisabled) { if (isDisabled) {
return StyleConstants.GrayScale; return StyleConstants.GrayScale;
} }
return undefined; return configContext.platform == Platform.Fabric ? StyleConstants.NoColor : undefined;
}; };
return btns return btns
@ -68,6 +73,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
height: buttonHeightPx, height: buttonHeightPx,
paddingRight: 0, paddingRight: 0,
paddingLeft: 0, paddingLeft: 0,
borderRadius: configContext.platform == Platform.Fabric ? StyleConstants.FabricButtonBorderRadius : "0px",
minWidth: 24, minWidth: 24,
marginLeft: isSplit ? 0 : 5, marginLeft: isSplit ? 0 : 5,
marginRight: isSplit ? 0 : 5, marginRight: isSplit ? 0 : 5,
@ -79,17 +85,17 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
splitButtonMenuButton: { splitButtonMenuButton: {
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
selectors: { selectors: {
":hover": { backgroundColor: StyleConstants.AccentLight }, ":hover": { backgroundColor: hoverColor },
}, },
width: 16, width: 16,
}, },
label: { fontSize: StyleConstants.mediumFontSize }, label: { fontSize: StyleConstants.mediumFontSize },
rootHovered: { backgroundColor: StyleConstants.AccentLight }, rootHovered: { backgroundColor: hoverColor },
rootPressed: { backgroundColor: StyleConstants.AccentLight }, rootPressed: { backgroundColor: hoverColor },
splitButtonMenuButtonExpanded: { splitButtonMenuButtonExpanded: {
backgroundColor: StyleConstants.AccentExtra, backgroundColor: StyleConstants.AccentExtra,
selectors: { selectors: {
":hover": { backgroundColor: StyleConstants.AccentLight }, ":hover": { backgroundColor: hoverColor },
}, },
}, },
splitButtonDivider: { splitButtonDivider: {
@ -120,7 +126,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes // TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
selectors: { selectors: {
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize }, ".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize },
".ms-ContextualMenu-link:hover": { backgroundColor: StyleConstants.AccentLight }, ".ms-ContextualMenu-link:hover": { backgroundColor: hoverColor },
".ms-ContextualMenu-icon": { width: 16, height: 16 }, ".ms-ContextualMenu-icon": { width: 16, height: 16 },
}, },
}, },

View File

@ -3,7 +3,7 @@ import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import styled from "styled-components"; import styled from "styled-components";
import { StyleConstants } from "../../../Common/Constants"; import { StyleConstants } from "../../../Common/StyleConstants";
interface Props { interface Props {
lastSaved?: Date | null; lastSaved?: Date | null;

View File

@ -11,8 +11,9 @@ import {
Stack, Stack,
Text, Text,
} from "@fluentui/react"; } from "@fluentui/react";
import { QueryCopilotSampleDatabaseId, StyleConstants } from "Common/Constants"; import { QueryCopilotSampleDatabaseId } from "Common/Constants";
import { handleError } from "Common/ErrorHandlingUtils"; import { handleError } from "Common/ErrorHandlingUtils";
import { StyleConstants } from "Common/StyleConstants";
import { createCollection } from "Common/dataAccess/createCollection"; import { createCollection } from "Common/dataAccess/createCollection";
import * as DataModels from "Contracts/DataModels"; import * as DataModels from "Contracts/DataModels";
import { ContainerSampleGenerator } from "Explorer/DataSamples/ContainerSampleGenerator"; import { ContainerSampleGenerator } from "Explorer/DataSamples/ContainerSampleGenerator";

View File

@ -138,12 +138,7 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
/> />
)} )}
</span> </span>
<span <span className="tabNavText">{useObservable(tab?.tabTitle || getReactTabTitle())}</span>
className="tabNavText"
style={active ? { fontWeight: "bolder", borderBottom: "2px solid rgba(0,120,212,1)" } : {}}
>
{useObservable(tab?.tabTitle || getReactTabTitle())}
</span>
{tabKind !== ReactTabKind.Home && ( {tabKind !== ReactTabKind.Home && (
<span className="tabIconSection"> <span className="tabIconSection">
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} /> <CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} />

View File

@ -37,6 +37,7 @@ import QueryTablesTab from "../Tabs/QueryTablesTab";
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2"; import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
import { Platform, configContext } from "./../../ConfigContext";
import ConflictId from "./ConflictId"; import ConflictId from "./ConflictId";
import DocumentId from "./DocumentId"; import DocumentId from "./DocumentId";
import StoredProcedure from "./StoredProcedure"; import StoredProcedure from "./StoredProcedure";
@ -205,7 +206,8 @@ export default class Collection implements ViewModels.Collection {
.map((node) => <Trigger>node); .map((node) => <Trigger>node);
}); });
const showScriptsMenus: boolean = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; const showScriptsMenus: boolean =
configContext.platform != Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
this.showStoredProcedures = ko.observable<boolean>(showScriptsMenus); this.showStoredProcedures = ko.observable<boolean>(showScriptsMenus);
this.showTriggers = ko.observable<boolean>(showScriptsMenus); this.showTriggers = ko.observable<boolean>(showScriptsMenus);
this.showUserDefinedFunctions = ko.observable<boolean>(showScriptsMenus); this.showUserDefinedFunctions = ko.observable<boolean>(showScriptsMenus);

View File

@ -39,6 +39,7 @@ import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
import TabsBase from "../Tabs/TabsBase"; import TabsBase from "../Tabs/TabsBase";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
import { Platform, configContext } from "./../../ConfigContext";
import StoredProcedure from "./StoredProcedure"; import StoredProcedure from "./StoredProcedure";
import Trigger from "./Trigger"; import Trigger from "./Trigger";
import UserDefinedFunction from "./UserDefinedFunction"; import UserDefinedFunction from "./UserDefinedFunction";
@ -69,7 +70,8 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
shallow shallow
); );
const { activeTab, refreshActiveTab } = useTabs(); const { activeTab, refreshActiveTab } = useTabs();
const showScriptNodes = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; const showScriptNodes =
configContext.platform !== Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
const pseudoDirPath = "PsuedoDir"; const pseudoDirPath = "PsuedoDir";
const buildGalleryCallout = (): JSX.Element => { const buildGalleryCallout = (): JSX.Element => {

View File

@ -40,6 +40,7 @@ import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
import TabsBase from "../Tabs/TabsBase"; import TabsBase from "../Tabs/TabsBase";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
import { Platform, configContext } from "./../../ConfigContext";
import StoredProcedure from "./StoredProcedure"; import StoredProcedure from "./StoredProcedure";
import Trigger from "./Trigger"; import Trigger from "./Trigger";
import UserDefinedFunction from "./UserDefinedFunction"; import UserDefinedFunction from "./UserDefinedFunction";
@ -249,7 +250,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
* @param container * @param container
*/ */
private static showScriptNodes(container: Explorer): boolean { private static showScriptNodes(container: Explorer): boolean {
return userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; return (
configContext.platform !== Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
);
} }
private buildCollectionNode(database: ViewModels.Database, collection: ViewModels.Collection): TreeNode { private buildCollectionNode(database: ViewModels.Database, collection: ViewModels.Collection): TreeNode {

View File

@ -89,8 +89,8 @@ export const ResourceTree2: React.FC<ResourceTreeProps> = ({ container }: Resour
aria-label="CosmosDB resources" aria-label="CosmosDB resources"
openItems={openItems} openItems={openItems}
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
size="small" size="medium"
style={{ height: "100%" }} style={{ height: "100%", width: "290px" }}
> >
{[dataNodeTree].map((node) => ( {[dataNodeTree].map((node) => (
<TreeNode2Component <TreeNode2Component

View File

@ -17,6 +17,7 @@ import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity"; import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import { useNotebook } from "../Notebook/useNotebook"; import { useNotebook } from "../Notebook/useNotebook";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
import { Platform, configContext } from "./../../ConfigContext";
export const buildCollectionNode = ( export const buildCollectionNode = (
database: ViewModels.Database, database: ViewModels.Database,
@ -25,6 +26,46 @@ export const buildCollectionNode = (
container: Explorer, container: Explorer,
refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void
): TreeNode2 => { ): TreeNode2 => {
let children: TreeNode2[];
// Flat Tree for Fabric
if (configContext.platform !== Platform.Fabric) {
children = buildCollectionNodeChildren(database, collection, isNotebookEnabled, container, refreshActiveTab);
}
return {
label: collection.id(),
iconSrc: CollectionIcon,
children: children,
className: "collectionHeader",
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(container, collection),
onClick: () => {
useSelectedNode.getState().setSelectedNode(collection);
collection.openTab();
// push to most recent
mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
},
onExpanded: () => {
// Rewritten version of expandCollapseCollection
useSelectedNode.getState().setSelectedNode(collection);
useCommandBar.getState().setContextButtons([]);
refreshActiveTab(
(tab: TabsBase) =>
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
);
},
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
};
};
const buildCollectionNodeChildren = (
database: ViewModels.Database,
collection: ViewModels.Collection,
isNotebookEnabled: boolean,
container: Explorer,
refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void
): TreeNode2[] => {
const showScriptNodes = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; const showScriptNodes = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
const children: TreeNode2[] = []; const children: TreeNode2[] = [];
children.push({ children.push({
@ -110,27 +151,7 @@ export const buildCollectionNode = (
}); });
} }
return { return children;
label: collection.id(),
iconSrc: CollectionIcon,
children: children,
className: "collectionHeader",
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(container, collection),
onClick: () => {
useSelectedNode.getState().setSelectedNode(collection);
},
onExpanded: () => {
// Rewritten version of expandCollapseCollection
useSelectedNode.getState().setSelectedNode(collection);
useCommandBar.getState().setContextButtons([]);
refreshActiveTab(
(tab: TabsBase) =>
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
);
},
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
};
}; };
const buildStoredProcedureNode = ( const buildStoredProcedureNode = (

View File

@ -9,6 +9,7 @@ import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFacto
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
import { Platform, configContext } from "./../../ConfigContext";
export const useDatabaseTreeNodes = (container: Explorer, isNotebookEnabled: boolean): TreeNode2[] => { export const useDatabaseTreeNodes = (container: Explorer, isNotebookEnabled: boolean): TreeNode2[] => {
const databases = useDatabases((state) => state.databases); const databases = useDatabases((state) => state.databases);
@ -35,7 +36,7 @@ export const useDatabaseTreeNodes = (container: Explorer, isNotebookEnabled: boo
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database), onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database),
}; };
if (database.isDatabaseShared()) { if (database.isDatabaseShared() && configContext.platform !== Platform.Fabric) {
databaseNode.children.push({ databaseNode.children.push({
id: database.isSampleDB ? "sampleScaleSettings" : "", id: database.isSampleDB ? "sampleScaleSettings" : "",
label: "Scale", label: "Scale",

View File

@ -1,5 +1,5 @@
// CSS Dependencies // CSS Dependencies
import { initializeIcons } from "@fluentui/react"; import { initializeIcons, loadTheme } from "@fluentui/react";
import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel"; import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel";
import { MongoQuickstartTutorial } from "Explorer/Quickstart/Tutorials/MongoQuickstartTutorial"; import { MongoQuickstartTutorial } from "Explorer/Quickstart/Tutorials/MongoQuickstartTutorial";
import { SQLQuickstartTutorial } from "Explorer/Quickstart/Tutorials/SQLQuickstartTutorial"; import { SQLQuickstartTutorial } from "Explorer/Quickstart/Tutorials/SQLQuickstartTutorial";
@ -26,6 +26,8 @@ import "../less/TableStyles/CustomizeColumns.less";
import "../less/TableStyles/EntityEditor.less"; import "../less/TableStyles/EntityEditor.less";
import "../less/TableStyles/fulldatatables.less"; import "../less/TableStyles/fulldatatables.less";
import "../less/TableStyles/queryBuilder.less"; import "../less/TableStyles/queryBuilder.less";
import * as StyleConstants from "./Common/StyleConstants";
import { configContext, Platform } from "ConfigContext";
import "../less/documentDB.less"; import "../less/documentDB.less";
import "../less/forms.less"; import "../less/forms.less";
import "../less/infobox.less"; import "../less/infobox.less";
@ -57,6 +59,7 @@ import "./Libs/jquery";
import "./Shared/appInsights"; import "./Shared/appInsights";
import { useConfig } from "./hooks/useConfig"; import { useConfig } from "./hooks/useConfig";
import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer"; import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
import { appThemeFabric } from "./Platform/Fabric/FabricTheme";
initializeIcons(); initializeIcons();
@ -67,6 +70,10 @@ const App: React.FunctionComponent = () => {
const shouldShowModal = useQueryCopilot((state) => state.showFeedbackModal); const shouldShowModal = useQueryCopilot((state) => state.showFeedbackModal);
const config = useConfig(); const config = useConfig();
if (config?.platform === Platform.Fabric) {
loadTheme(appThemeFabric);
}
StyleConstants.updateStyles();
const explorer = useKnockoutExplorer(config?.platform); const explorer = useKnockoutExplorer(config?.platform);
const toggleLeftPaneExpanded = () => { const toggleLeftPaneExpanded = () => {
@ -84,6 +91,7 @@ const App: React.FunctionComponent = () => {
return ( return (
<div className="flexContainer" aria-hidden="false"> <div className="flexContainer" aria-hidden="false">
<LoadFabricOverrides />
<div id="divExplorer" className="flexContainer hideOverflows"> <div id="divExplorer" className="flexContainer hideOverflows">
<div id="freeTierTeachingBubble"> </div> <div id="freeTierTeachingBubble"> </div>
{/* Main Command Bar - Start */} {/* Main Command Bar - Start */}
@ -135,6 +143,19 @@ const App: React.FunctionComponent = () => {
ReactDOM.render(<App />, document.body); ReactDOM.render(<App />, document.body);
function LoadFabricOverrides(): JSX.Element {
if (configContext.platform === Platform.Fabric) {
const FabricStyle = React.lazy(() => import("./Platform/Fabric/FabricPlatform"));
return (
<React.Suspense fallback={<div></div>}>
<FabricStyle />
</React.Suspense>
);
} else {
return <></>;
}
}
function LoadingExplorer(): JSX.Element { function LoadingExplorer(): JSX.Element {
return ( return (
<div className="splashLoaderContainer"> <div className="splashLoaderContainer">

View File

@ -0,0 +1,7 @@
import React from "react";
import "../../../less/documentDBFabric.less";
// This is a dummy export, allowing us to conditionally import documentDBFabric.less
// by lazy-importing this in Main.tsx (see LoadFabricOverrides() there)
export default function InitFabric() {
return <></>;
}

View File

@ -0,0 +1,208 @@
import { Theme, createTheme } from "@fluentui/react";
export const appThemeFabric: Theme = createTheme({
palette: {
/**
* Color code for themeDarker.
*/
themeDarker: "#033f38",
/**
* Color code for themeDark.
*/
themeDark: "#0a5c50",
/**
* Color code for themeDarkAlt.
*/
themeDarkAlt: "#0c695a",
/**
* Color code for themePrimary.
*/
themePrimary: "#117865",
/**
* Color code for themeSecondary.
*/
themeSecondary: "#1f937e",
/**
* Color code for themeTertiary.
*/
themeTertiary: "#52c7aa",
/**
* Color code for themeLight.
*/
themeLight: "#9ee0cb",
/**
* Color code for themeLighter.
*/
themeLighter: "#c0ecdd",
/**
* Color code for themeLighterAlt.
*/
themeLighterAlt: "#e3f7ef",
/**
* Color code for the strongest color, which is black in the default theme.
* This is a very light color in inverted themes.
*/
black: "#000000",
/**
* Color code for blackTranslucent40.
*/
blackTranslucent40: "rgba(0, 0, 0, 0.4)",
/**
* Color code for neutralDark.
*/
neutralDark: "#141414",
/**
* Color code for neutralPrimary.
*/
neutralPrimary: "#242424",
/**
* Color code for neutralPrimaryAlt.
*/
neutralPrimaryAlt: "#383838",
/**
* Color code for neutralSecondary.
*/
neutralSecondary: "#5c5c5c",
/**
* Color code for neutralSecondaryAlt.
*/
neutralSecondaryAlt: "#858585",
/**
* Color code for neutralTertiary.
*/
neutralTertiary: "#9e9e9e",
/**
* Color code for neutralTertiaryAlt.
*/
neutralTertiaryAlt: "#c7c7c7",
/**
* Color code for neutralQuaternary.
*/
neutralQuaternary: "#d1d1d1",
/**
* Color code for neutralQuaternaryAlt.
*/
neutralQuaternaryAlt: "#e0e0e0",
/**
* Color code for neutralLight.
*/
neutralLight: "#ebebeb",
/**
* Color code for neutralLighter.
*/
neutralLighter: "#f5f5f5",
/**
* Color code for neutralLighterAlt.
*/
neutralLighterAlt: "#fafafa",
/**
* Color code for the accent.
*/
accent: "#117865",
/**
* Color code for the softest color, which is white in the default theme. This is a very dark color in dark themes.
* This is the page background.
*/
white: "#ffffff",
/**
* Color code for whiteTranslucent40
*/
whiteTranslucent40: "rgba(255, 255, 255, 0.4)",
/**
* Color code for yellowDark.
*/
yellowDark: "#d39300",
/**
* Color code for yellow.
*/
yellow: "#fde300",
/**
* Color code for yellowLight.
*/
yellowLight: "#fef7b2",
/**
* Color code for orange.
*/
orange: "#f7630c",
/**
* Color code for orangeLight.
*/
orangeLight: "#f98845",
/**
* Color code for orangeLighter.
*/
orangeLighter: "#fdcfb4",
/**
* Color code for redDark.
*/
redDark: "#750b1c",
/**
* Color code for red.
*/
red: "#d13438",
/**
* Color code for magentaDark.
*/
magentaDark: "#6b0043",
/**
* Color code for magenta.
*/
magenta: "#bf0077",
/**
* Color code for magentaLight.
*/
magentaLight: "#d957a8",
/**
* Color code for purpleDark.
*/
purpleDark: "#401b6c",
/**
* Color code for purple.
*/
purple: "#5c2e91",
/**
* Color code for purpleLight.
*/
purpleLight: "#c6b1de",
/**
* Color code for blueDark.
*/
blueDark: "#003966",
/**
* Color code for blueMid.
*/
blueMid: "#004e8c",
/**
* Color code for blue.
*/
blue: "#0078d4",
/**
* Color code for blueLight.
*/
blueLight: "#3a96dd",
/**
* Color code for tealDark.
*/
tealDark: "#006666",
/**
* Color code for teal.
*/
teal: "#038387",
/**
* Color code for tealLight.
*/
tealLight: "#00b7c3",
/**
* Color code for greenDark.
*/
greenDark: "#0b6a0b",
/**
* Color code for green.
*/
green: "#107c10",
/**
* Color code for greenLight.
*/
greenLight: "#13a10e",
},
});

View File

@ -4,7 +4,7 @@
import { DefaultButton, IButtonStyles, IContextualMenuItem } from "@fluentui/react"; import { DefaultButton, IButtonStyles, IContextualMenuItem } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { FunctionComponent, useEffect, useState } from "react"; import { FunctionComponent, useEffect, useState } from "react";
import { StyleConstants } from "../../../Common/Constants"; import { StyleConstants } from "../../../Common/StyleConstants";
import { DatabaseAccount } from "../../../Contracts/DataModels"; import { DatabaseAccount } from "../../../Contracts/DataModels";
import { useDatabaseAccounts } from "../../../hooks/useDatabaseAccounts"; import { useDatabaseAccounts } from "../../../hooks/useDatabaseAccounts";
import { useSubscriptions } from "../../../hooks/useSubscriptions"; import { useSubscriptions } from "../../../hooks/useSubscriptions";

View File

@ -1,7 +1,7 @@
import { useBoolean } from "@fluentui/react-hooks"; import { useBoolean } from "@fluentui/react-hooks";
import * as React from "react"; import * as React from "react";
import ErrorImage from "../../../../images/error.svg";
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg"; import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
import ErrorImage from "../../../../images/error.svg";
import { AuthType } from "../../../AuthType"; import { AuthType } from "../../../AuthType";
import { HttpHeaders } from "../../../Common/Constants"; import { HttpHeaders } from "../../../Common/Constants";
import { configContext } from "../../../ConfigContext"; import { configContext } from "../../../ConfigContext";
@ -16,6 +16,19 @@ interface Props {
setAuthType: (authType: AuthType) => void; setAuthType: (authType: AuthType) => void;
} }
export const fetchEncryptedToken = async (connectionString: string): Promise<string> => {
const headers = new Headers();
headers.append(HttpHeaders.connectionString, connectionString);
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
const response = await fetch(url, { headers, method: "POST" });
if (!response.ok) {
throw response;
}
// This API has a quirk where it must be parsed twice
const result: GenerateTokenResponse = JSON.parse(await response.json());
return decodeURIComponent(result.readWrite || result.read);
};
export const ConnectExplorer: React.FunctionComponent<Props> = ({ export const ConnectExplorer: React.FunctionComponent<Props> = ({
setEncryptedToken, setEncryptedToken,
login, login,
@ -44,16 +57,8 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
return; return;
} }
const headers = new Headers(); const encryptedToken = await fetchEncryptedToken(connectionString);
headers.append(HttpHeaders.connectionString, connectionString); setEncryptedToken(encryptedToken);
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
const response = await fetch(url, { headers, method: "POST" });
if (!response.ok) {
throw response;
}
// This API has a quirk where it must be parsed twice
const result: GenerateTokenResponse = JSON.parse(await response.json());
setEncryptedToken(decodeURIComponent(result.readWrite || result.read));
setAuthType(AuthType.ConnectionString); setAuthType(AuthType.ConnectionString);
}} }}
> >

View File

@ -1,5 +1,5 @@
import { IButtonStyles, ICommandBarStyles, ISeparatorStyles, IStackTokens } from "@fluentui/react"; import { IButtonStyles, ICommandBarStyles, ISeparatorStyles, IStackTokens } from "@fluentui/react";
import { StyleConstants } from "../Common/Constants"; import { StyleConstants } from "../Common/StyleConstants";
export const commandBarItemStyles: IButtonStyles = { root: { paddingLeft: 20 } }; export const commandBarItemStyles: IButtonStyles = { root: { paddingLeft: 20 } };

View File

@ -1,3 +1,5 @@
import { Platform, configContext } from "./../ConfigContext";
export const getDataExplorerWindow = (currentWindow: Window): Window | undefined => { export const getDataExplorerWindow = (currentWindow: Window): Window | undefined => {
// Data explorer is always loaded in an iframe, so traverse the parents until we hit the top and return the first child window. // Data explorer is always loaded in an iframe, so traverse the parents until we hit the top and return the first child window.
try { try {
@ -5,7 +7,11 @@ export const getDataExplorerWindow = (currentWindow: Window): Window | undefined
if (currentWindow.parent === currentWindow) { if (currentWindow.parent === currentWindow) {
return undefined; return undefined;
} }
if (currentWindow.parent === currentWindow.top) { if (configContext.platform === Platform.Fabric && currentWindow.parent.parent === currentWindow.top) {
// in Fabric data explorer is inside an extension iframe, so we have two parent iframes
return currentWindow;
}
if (configContext.platform !== Platform.Fabric && currentWindow.parent === currentWindow.top) {
return currentWindow; return currentWindow;
} }
currentWindow = currentWindow.parent; currentWindow = currentWindow.parent;

View File

@ -1,6 +1,8 @@
import { createUri } from "Common/UrlUtility"; import { createUri } from "Common/UrlUtility";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { fetchEncryptedToken } from "Platform/Hosted/Components/ConnectExplorer";
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility"; import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
import { fetchAccessData } from "hooks/usePortalAccessToken";
import { ReactTabKind, useTabs } from "hooks/useTabs"; import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
@ -60,6 +62,26 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
} else if (platform === Platform.Portal) { } else if (platform === Platform.Portal) {
const explorer = await configurePortal(); const explorer = await configurePortal();
setExplorer(explorer); setExplorer(explorer);
} else if (platform === Platform.Fabric) {
// TODO For now, retrieve info from session storage. Replace with info injected into Data Explorer
const connectionString = sessionStorage.getItem("connectionString");
if (!connectionString) {
console.error("No connection string found in session storage");
return;
}
const encryptedToken = await fetchEncryptedToken(connectionString);
// TODO Duplicated from useTokenMetadata
const encryptedTokenMetadata = await fetchAccessData(encryptedToken);
const win = (window as unknown) as HostedExplorerChildFrame;
win.hostedConfig = {
authType: AuthType.EncryptedToken,
encryptedToken,
encryptedTokenMetadata,
};
const explorer = await configureHosted();
setExplorer(explorer);
} }
} }
}; };