Skip to content

Commit

Permalink
Update itwinui version
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasDov committed Nov 11, 2024
1 parent 6a3c453 commit eef7e25
Show file tree
Hide file tree
Showing 16 changed files with 229 additions and 179 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"wdth",
"wght"
],
"cSpell.ignorePaths": ["package.json", ".vscode", "*.yaml"],
"cSpell.ignorePaths": ["package.json", ".vscode", "*.yaml", "*.yml"],
"cSpell.ignoreRegExpList": ["from\\s\".*\";"],

"editor.insertSpaces": true,
Expand Down
2 changes: 1 addition & 1 deletion app/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@itwin/itwinui-illustrations-react": "^2.1.0",
"@itwin/itwinui-layouts-css": "^0.2.0",
"@itwin/itwinui-layouts-react": "^0.2.0",
"@itwin/itwinui-react": "~2.12.25",
"@itwin/itwinui-react": "3.15.5",
"@itwin/presentation-common": "^4.9.7",
"@itwin/presentation-components": "^4.4.1",
"@itwin/presentation-frontend": "^4.9.7",
Expand Down
1 change: 1 addition & 0 deletions app/frontend/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import "./App.scss";
import "@itwin/itwinui-layouts-css/styles.css";
import "@itwin/itwinui-react/styles.css";
import * as React from "react";
import { Navigate, Route, Routes, useNavigate } from "react-router-dom";
import { SvgDeveloper, SvgFolderOpened } from "@itwin/itwinui-icons-react";
Expand Down
102 changes: 65 additions & 37 deletions app/frontend/src/app/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,20 @@
import { UserProfile } from "oidc-client-ts";
import * as React from "react";
import { SvgImodelHollow } from "@itwin/itwinui-icons-react";
import { Avatar, Button, DropdownMenu, getUserColor, Header, HeaderBreadcrumbs, HeaderLogo, IconButton, MenuItem } from "@itwin/itwinui-react";
import {
Avatar,
Button,
DropdownMenu,
getUserColor,
Header,
HeaderBreadcrumbs,
HeaderLogo,
IconButton,
MenuDivider,
MenuExtraContent,
MenuItem,
Text,
} from "@itwin/itwinui-react";
import { appNavigationContext } from "./AppContext";
import { AuthorizationState, useAuthorization } from "./Authorization";
import { HorizontalStack } from "./common/CenteredStack";
Expand All @@ -17,30 +30,34 @@ export function AppHeader(): React.ReactElement {
const { state, user, signIn, signOut } = useAuthorization();
const navigation = React.useContext(appNavigationContext);

const actions = [
<IconButton key="Repository" as="a" href="https://github.com/iTwin/presentation-rules-editor" title="Source code" styleType="borderless">
<GitHubLogoSmall />
</IconButton>,
];
switch (state) {
case AuthorizationState.Offline:
actions.push(
<HorizontalStack key="offlineMode">
Offline mode <OfflineModeExplainer />
</HorizontalStack>,
);
break;
const actions = React.useMemo(() => {
const initialActions = [
<IconButton key="Repository" as="a" href="https://github.com/iTwin/presentation-rules-editor" label="Source code" styleType="borderless">
<GitHubLogoSmall />
</IconButton>,
];
switch (state) {
case AuthorizationState.Offline:
initialActions.push(
<HorizontalStack key="offlineMode">
Offline mode <OfflineModeExplainer />
</HorizontalStack>,
);
break;

case AuthorizationState.SignedOut:
actions.push(
<Button key="signin" styleType="borderless" onClick={signIn}>
Sign In
</Button>,
);
break;
}

const userIcon = state === AuthorizationState.SignedIn && user !== undefined ? <HeaderUserIcon profile={user.profile} signOut={signOut} /> : null;
case AuthorizationState.SignedOut:
initialActions.push(
<Button key="signin" styleType="borderless" onClick={signIn}>
Sign In
</Button>,
);
break;
case AuthorizationState.SignedIn:
initialActions.push(<HeaderUserIcon profile={user.profile} signOut={signOut} />);
break;
}
return initialActions;
}, [state, signIn, user, signOut]);

return (
<Header
Expand All @@ -51,7 +68,6 @@ export function AppHeader(): React.ReactElement {
}
breadcrumbs={<Breadcrumbs />}
actions={actions}
userIcon={userIcon}
/>
);
}
Expand All @@ -64,20 +80,32 @@ interface HeaderUserIconProps {

function HeaderUserIcon(props: HeaderUserIconProps): React.ReactElement | null {
const { profile, signOut } = props;
const preferredName = profile?.preferred_username || profile?.name || profile?.nickname;
const initials = profile?.given_name && profile?.family_name ? profile.given_name[0] + profile.family_name[0] : (preferredName ?? "?").substring(0, 2);
const displayName = preferredName ?? "Unknown Account";
const userDetails = React.useMemo(() => {
const preferredName = profile?.preferred_username || profile?.name || profile?.nickname;
const initials = profile?.given_name && profile?.family_name ? profile.given_name[0] + profile.family_name[0] : (preferredName ?? "?").substring(0, 2);
const displayName = preferredName ?? "Unknown Account";
return { preferredName, initials, displayName };
}, [profile]);

const userIconMenuItems = React.useMemo(
() => [
<MenuExtraContent key={0}>
<>
<Text variant="leading">{userDetails.displayName}</Text>
</>
</MenuExtraContent>,
<MenuDivider key={1} />,
<MenuItem key={2} onClick={signOut}>
Sign out
</MenuItem>,
],
[userDetails, signOut],
);

return (
<DropdownMenu
menuItems={() => [
<MenuItem key="signout" onClick={signOut}>
Sign Out
</MenuItem>,
]}
>
<IconButton styleType="borderless" title="Account Actions">
<Avatar title={displayName} abbreviation={initials} backgroundColor={getUserColor(displayName)} />
<DropdownMenu menuItems={userIconMenuItems}>
<IconButton styleType="borderless" label="Account Actions">
<Avatar title={userDetails.displayName} abbreviation={userDetails.initials} backgroundColor={getUserColor(userDetails.displayName)} />
</IconButton>
</DropdownMenu>
);
Expand Down
2 changes: 1 addition & 1 deletion app/frontend/src/app/IModelBrowser/IModelBrowser.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
display: flex;
justify-content: space-between;

.iui-input-container {
.search-input {
width: 384px;
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/frontend/src/app/IModelBrowser/IModelBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ export const IModelBrowser = React.memo(function IModelBrowser(props: IModelBrow
<Grid>
<Grid.Item className="imodel-browser-controls" columnSpan={12}>
<LabeledInput
className="search-input"
placeholder="Search"
svgIcon={<SvgSearch />}
iconDisplayStyle="inline"
value={searchQuery}
maxLength={255}
onChange={(event) => setSearchQuery(event.target.value)}
Expand Down
18 changes: 10 additions & 8 deletions app/frontend/src/app/ITwinJsApp/InitializedApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as React from "react";
import { StagePanelState, WidgetState } from "@itwin/appui-react";
import { AuthorizationClient } from "@itwin/core-common";
import { IModelApp, IModelConnection, OutputMessagePriority } from "@itwin/core-frontend";
import { useToaster } from "@itwin/itwinui-react";
import { ChildNodeSpecificationTypes, ContentSpecificationTypes, Ruleset, RuleTypes } from "@itwin/presentation-common";
import { EditableRuleset, SoloRulesetEditor } from "@itwin/presentation-rules-editor-react";
import { useIModelBrowserSettings } from "../IModelBrowser/IModelBrowser";
Expand All @@ -18,7 +19,7 @@ import { ContentTabs } from "./content-tabs/ContentTabs";
import { areIModelIdentifiersEqual, IModelIdentifier, isDemoIModel, isSnapshotIModel } from "./IModelIdentifier";
import { backendApiContext, rulesetEditorContext, RulesetEditorTab } from "./ITwinJsAppContext";
import { parseEditorState } from "./misc/EditorStateSerializer";
import { displayToast } from "./misc/Notifications";
import { displayToast, Toaster } from "./misc/Notifications";
import { Frontstage } from "./ui-framework/Frontstage";
import { StagePanel, StagePanelZone } from "./ui-framework/StagePanel";
import { UIFramework } from "./ui-framework/UIFramework";
Expand Down Expand Up @@ -114,6 +115,7 @@ function useIModel(
authorizationClient: AuthorizationClient | undefined,
): IModelConnection | undefined {
const [iModel, setIModel] = React.useState<IModelConnection>();
const toaster = useToaster();
const setMostRecentIModel = useRecentIModels();

React.useEffect(() => {
Expand All @@ -137,9 +139,9 @@ function useIModel(
}
} catch (error) {
if (isSnapshotIModel(iModelIdentifier)) {
displayIModelError(IModelApp.localization.getLocalizedString("App:error:imodel-open-local", { imodel: iModelIdentifier }), error);
displayIModelError(toaster, IModelApp.localization.getLocalizedString("App:error:imodel-open-local", { imodel: iModelIdentifier }), error);
} else {
displayIModelError(IModelApp.localization.getLocalizedString("App:error:imodel-open-remote"), error);
displayIModelError(toaster, IModelApp.localization.getLocalizedString("App:error:imodel-open-remote"), error);
}
}
})();
Expand All @@ -152,14 +154,14 @@ function useIModel(
await openedIModel.close();
} catch (error) {
if (isSnapshotIModel(iModelIdentifier)) {
displayIModelError(IModelApp.localization.getLocalizedString("App:error:imodel-close-local", { imodel: iModelIdentifier }), error);
displayIModelError(toaster, IModelApp.localization.getLocalizedString("App:error:imodel-close-local", { imodel: iModelIdentifier }), error);
} else {
displayIModelError(IModelApp.localization.getLocalizedString("App:error:imodel-close-remote"), error);
displayIModelError(toaster, IModelApp.localization.getLocalizedString("App:error:imodel-close-remote"), error);
}
}
})();
};
}, [authorizationClient, backendApi, iModelIdentifier, setMostRecentIModel]);
}, [authorizationClient, backendApi, iModelIdentifier, setMostRecentIModel, toaster]);

return iModel;
}
Expand All @@ -175,9 +177,9 @@ function useRecentIModels(): (iModelIdentifier: IModelIdentifier) => void {
}).current;
}

function displayIModelError(message: string, error: unknown): void {
function displayIModelError(toaster: Toaster, message: string, error: unknown): void {
const errorMessage = error && typeof error === "object" ? (error as { message: unknown }).message : error;
displayToast(OutputMessagePriority.Error, message, typeof errorMessage === "string" ? errorMessage : undefined);
displayToast(toaster, OutputMessagePriority.Error, message, typeof errorMessage === "string" ? errorMessage : undefined);
}

interface UseSoloRulesetEditorReturnType {
Expand Down
9 changes: 5 additions & 4 deletions app/frontend/src/app/ITwinJsApp/content-tabs/ContentTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "./ContentTabs.scss";
import * as React from "react";
import { IModelApp, IModelConnection, OutputMessagePriority } from "@itwin/core-frontend";
import { SvgLink } from "@itwin/itwinui-icons-react";
import { Button, Tabs } from "@itwin/itwinui-react";
import { Button, Tabs, useToaster } from "@itwin/itwinui-react";
import { SoloRulesetEditor } from "@itwin/presentation-rules-editor-react";
import { OpeningIModelHint } from "../common/OpeningIModelHint";
import { rulesetEditorContext, RulesetEditorTab } from "../ITwinJsAppContext";
Expand Down Expand Up @@ -57,11 +57,12 @@ interface ShareButtonProps {
}

function ShareButton(props: ShareButtonProps): React.ReactElement {
const handleShareButtonClick = async () => {
const toaster = useToaster();
const handleShareButtonClick = React.useCallback(async () => {
window.location.hash = serializeEditorState(props.editor.model.getValue());
await navigator.clipboard.writeText(window.location.toString());
displayToast(OutputMessagePriority.Success, IModelApp.localization.getLocalizedString("App:toast:link-copied"));
};
displayToast(toaster, OutputMessagePriority.Success, IModelApp.localization.getLocalizedString("App:toast:link-copied"));
}, [props.editor.model, toaster]);

return (
<Button id="share" size="small" startIcon={<SvgLink />} onClick={handleShareButtonClick}>
Expand Down
32 changes: 22 additions & 10 deletions app/frontend/src/app/ITwinJsApp/misc/Notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,40 @@
*--------------------------------------------------------------------------------------------*/

import { OutputMessagePriority } from "@itwin/core-frontend";
import { toaster, ToastOptions } from "@itwin/itwinui-react";

export function displayToast(messageType: OutputMessagePriority, messageShort: string, _messageDetail?: string): void {
const settings: ToastOptions = {
placementPosition: "top",
};

export function displayToast(toaster: Toaster, messageType: OutputMessagePriority, messageShort: string, _messageDetail?: string): void {
toaster.setSettings({ placement: "top" });
switch (messageType) {
case OutputMessagePriority.Fatal:
case OutputMessagePriority.Error:
toaster.negative(messageShort, settings);
toaster.negative(messageShort);
break;
case OutputMessagePriority.Warning:
toaster.warning(messageShort, { ...settings, duration: 3000 });
toaster.warning(messageShort, { duration: 3000 });
break;
case OutputMessagePriority.Info:
case OutputMessagePriority.Debug:
case OutputMessagePriority.None:
toaster.informational(messageShort, { ...settings, duration: 3000 });
toaster.informational(messageShort, { duration: 3000 });
break;
case OutputMessagePriority.Success:
toaster.positive(messageShort, { ...settings, duration: 3000 });
toaster.positive(messageShort, { duration: 3000 });
break;
}
}

export type ToasterType = "warning" | "negative" | "positive" | "informational";

export type Toaster = Record<
ToasterType,
(
content: React.ReactNode,
options?: ToastOptions,
) => {
close: () => void;
}
> & { setSettings: (settings: { placement: "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" }) => void };

export interface ToastOptions {
duration?: number;
}
16 changes: 0 additions & 16 deletions app/frontend/src/app/ITwinJsApp/widgets/PropertyGridWidget.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,6 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

.property-grid {
display: grid;
grid-template-rows: 44px 1fr;

> .controls {
display: flex;
align-items: center;
margin: 0 4px;
}

> .iui-toggle-switch {
justify-self: end;
margin-right: 12px;
}
}

.presentation-rules-editor-property-grid {
padding: var(--iui-size-2xs);
padding-right: 0;
Expand Down
16 changes: 2 additions & 14 deletions app/frontend/src/app/common/AsyncActionButton.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,6 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

.async-action-button {
// Counteract 'position: absolute' that is going to be applied to the progress indicator
position: relative;

> div.iui-progress-indicator-radial {
// Make progress indicator not be considered when determining element layout
position: absolute;

// Position the progress indicator
height: 100%;
display: inline-flex;
align-items: center;
margin-left: 10px;
}
.action-in-progress-radial {
margin-left: 10px;
}
6 changes: 5 additions & 1 deletion app/frontend/src/app/common/AsyncActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ export function AsyncActionButton(props: AsyncActionButtonProps): React.ReactEle

return (
<div className="async-action-button">
{/* <Button disabled={actionInProgress} onClick={handleButtonClick}>
{props.children}
</Button>
{actionInProgress && <ProgressRadial indeterminate={true} size="small" />} */}
<Button disabled={actionInProgress} onClick={handleButtonClick}>
{props.children}
</Button>
{actionInProgress && <ProgressRadial indeterminate={true} size="small" />}
<ProgressRadial className="action-in-progress-radial" indeterminate={true} size="small" />
</div>
);
}
Loading

0 comments on commit eef7e25

Please sign in to comment.