Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for new editors system #1166

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions apps/test-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@
"@itwin/itwinui-icons-react": "^2.8.0",
"@itwin/itwinui-layouts-react": "~0.4.1",
"@itwin/itwinui-layouts-css": "~0.4.0",
"@itwin/presentation-backend": "4.4.0",
Copy link
Collaborator

@GerardasB GerardasB Dec 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these presentation dependencies necessary to showcase the new editors? Should we simply create a new frontstage for editors and use tool settings/dummy data in one of widgets?
Need to think about names, current EditorFrontstage is really for opening iModels in read-write.
(I see the comment now, that this is actually planned to be removed)

"@itwin/presentation-common": "^4.0.0",
"@itwin/presentation-components": "^5.0.0",
"@itwin/presentation-frontend": "4.4.0",
"@itwin/reality-data-client": "1.2.2",
"@itwin/webgl-compatibility": "^4.0.0",
"@tanstack/react-router": "^1.87.6",
Expand Down
3 changes: 3 additions & 0 deletions apps/test-app/src/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ECSchemaRpcInterface } from "@itwin/ecschema-rpcinterface-common";
import { ECSchemaRpcImpl } from "@itwin/ecschema-rpcinterface-impl";
import { BackendIModelsAccess } from "@itwin/imodels-access-backend";
import { IModelsClient } from "@itwin/imodels-client-authoring";
import { Presentation } from "@itwin/presentation-backend";

void (async () => {
try {
Expand Down Expand Up @@ -44,6 +45,8 @@ void (async () => {
} else {
await initializeWeb(opts);
}

Presentation.initialize();
} catch (error: any) {
Logger.logError(loggerCategory, error);
process.exitCode = 1;
Expand Down
2 changes: 2 additions & 0 deletions apps/test-app/src/common/rpcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SnapshotIModelRpcInterface,
} from "@itwin/core-common";
import { ECSchemaRpcInterface } from "@itwin/ecschema-rpcinterface-common";
import { PresentationRpcInterface } from "@itwin/presentation-common";

/**
* Returns a list of RPCs supported by this application
Expand All @@ -19,5 +20,6 @@ export function getSupportedRpcs(): RpcInterfaceDefinition[] {
IModelTileRpcInterface,
SnapshotIModelRpcInterface,
ECSchemaRpcInterface,
PresentationRpcInterface,
];
}
18 changes: 14 additions & 4 deletions apps/test-app/src/frontend/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import {
import { ThemeProvider as IUI2_ThemeProvider } from "@itwin/itwinui-react-v2";
import { useEngagementTime } from "./appui/useEngagementTime";
import { AppLocalizationProvider } from "./Localization";
import { EditorSpec, EditorsRegistryProvider } from "@itwin/components-react";
import {
ColorEditorSpec,
WeightEditorSpec,
} from "@itwin/imodel-components-react";

interface AppProps {
featureOverrides?: React.ComponentProps<
Expand All @@ -32,10 +37,12 @@ export function App({ featureOverrides }: AppProps) {
<WidgetContentProvider>
<AppPreviewFeatures featureOverrides={featureOverrides}>
<AppLocalizationProvider>
<ConfigurableUiContent
appBackstage={<BackstageComposer />}
childWindow={ChildWindow}
/>
<EditorsRegistryProvider editors={rootEditors}>
<ConfigurableUiContent
appBackstage={<BackstageComposer />}
childWindow={ChildWindow}
/>
</EditorsRegistryProvider>
</AppLocalizationProvider>
</AppPreviewFeatures>
</WidgetContentProvider>
Expand All @@ -48,3 +55,6 @@ export function App({ featureOverrides }: AppProps) {
function ChildWindow(props: React.PropsWithChildren<object>) {
return <IUI2_ThemeProvider>{props.children}</IUI2_ThemeProvider>;
}

// add custom editors from `@itwin/imodel-components-react` to the registry
const rootEditors: EditorSpec[] = [WeightEditorSpec, ColorEditorSpec];
5 changes: 5 additions & 0 deletions apps/test-app/src/frontend/AppInitializer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { TestAppLocalization } from "./useTranslation";
import { ElectronApp } from "@itwin/core-electron/lib/cjs/ElectronFrontend";
import { FrontendIModelsAccess } from "@itwin/imodels-access-frontend";
import { IModelsClient } from "@itwin/imodels-client-management";
import { Presentation } from "@itwin/presentation-frontend";

function createInitializer() {
let initializing: Promise<void> | undefined;
Expand Down Expand Up @@ -106,6 +107,8 @@ function createInitializer() {
await IModelApp.startup(options);
}

await Presentation.initialize();

ToolAdmin.exceptionHandler = async (err) =>
Promise.resolve(UnexpectedErrors.handle(err));
await IModelApp.localization.registerNamespace(
Expand Down Expand Up @@ -165,6 +168,8 @@ function createInitializer() {
}
});

await IModelApp.quantityFormatter.setActiveUnitSystem("metric");

// TODO: should not be required. Start event loop to open key-in palette.
IModelApp.startEventLoop();
}
Expand Down
19 changes: 19 additions & 0 deletions apps/test-app/src/frontend/SchemaContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { IModelConnection } from "@itwin/core-frontend";
import { SchemaContext } from "@itwin/ecschema-metadata";
import { ECSchemaRpcLocater } from "@itwin/ecschema-rpcinterface-common";

const schemaContextsCache = new Map<string, SchemaContext>();
export function getSchemaContext(imodel: IModelConnection) {
const context = schemaContextsCache.get(imodel.key);
if (context) {
return context;
}

const newContext = new SchemaContext();
newContext.addLocater(new ECSchemaRpcLocater(imodel.getRpcProps()));
schemaContextsCache.set(imodel.key, newContext);

imodel.onClose.addListener(() => schemaContextsCache.delete(imodel.key));

return newContext;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,8 @@ import {
StagePanelSection,
StageUsage,
StandardContentLayouts,
ToolbarItemUtilities,
ToolbarOrientation,
ToolbarUsage,
UiItemsProvider,
} from "@itwin/appui-react";
import { CreateArcTool, CreateLineStringTool } from "@itwin/editor-frontend";
import { SvgDraw, SvgEdit } from "@itwin/itwinui-icons-react";
import { ViewportContent } from "@itwin/appui-test-providers";

Expand Down Expand Up @@ -53,24 +49,6 @@ export function createEditorFrontstageProvider(): UiItemsProvider {
icon: <SvgEdit />,
}),
],
getToolbarItems: () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

const layouts = {
standard: {
orientation: ToolbarOrientation.Horizontal,
usage: ToolbarUsage.ContentManipulation,
},
};
return [
ToolbarItemUtilities.createForTool(CreateLineStringTool, {
itemPriority: 10,
layouts,
}),
ToolbarItemUtilities.createForTool(CreateArcTool, {
itemPriority: 10,
layouts,
}),
];
},
getWidgets: () => {
const layouts = {
standard: {
Expand Down
130 changes: 130 additions & 0 deletions apps/test-app/src/frontend/appui/providers/PropertyGridProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import * as React from "react";
import {
StagePanelLocation,
StagePanelSection,
UiItemsProvider,
useActiveIModelConnection,
WidgetState,
} from "@itwin/appui-react";
import { VirtualizedPropertyGridWithDataProvider } from "@itwin/components-react";
import { useResizeObserver } from "@itwin/core-react/internal";
import {
IPresentationPropertyDataProvider,
PresentationPropertyDataProvider,
SchemaMetadataContextProvider,
usePropertyDataProviderWithUnifiedSelection,
} from "@itwin/presentation-components";
import { getSchemaContext } from "../../SchemaContext";
import {
IModelApp,
NotifyMessageDetails,
OutputMessagePriority,
OutputMessageType,
} from "@itwin/core-frontend";

export function createPropertyGridProvider() {
return {
id: "appui-test-app:property-grid-provider",
getWidgets: () => {
return [
{
id: "property-grid-widget-new",
label: "With new editors",
content: <PropertyGrid usedEditor="new" />,
defaultState: WidgetState.Open,
layouts: {
standard: {
location: StagePanelLocation.Left,
section: StagePanelSection.Start,
},
},
},
{
id: "property-grid-widget-old",
label: "With old editors",
content: <PropertyGrid usedEditor="old" />,
defaultState: WidgetState.Open,
layouts: {
standard: {
location: StagePanelLocation.Right,
section: StagePanelSection.Start,
},
},
},
];
},
} satisfies UiItemsProvider;
}

function PropertyGrid({ usedEditor }: { usedEditor: "old" | "new" }) {
const imodel = useActiveIModelConnection();
const [dataProvider, setDataProvider] =
React.useState<PresentationPropertyDataProvider>();
React.useEffect(() => {
if (!imodel) {
setDataProvider(undefined);
return;
}

const provider = new PresentationPropertyDataProvider({ imodel });
setDataProvider(provider);
return () => {
provider.dispose();
};
}, [imodel]);

if (!dataProvider) {
return null;
}

return (
<PropertyGridInternal dataProvider={dataProvider} usedEditor={usedEditor} />
);
}

function PropertyGridInternal({
dataProvider,
usedEditor,
}: {
dataProvider: IPresentationPropertyDataProvider;
usedEditor: "old" | "new";
}) {
const [{ width, height }, setSize] = React.useState({
width: 0,
height: 0,
});
const onResize = React.useCallback((w: number, h: number) => {
setSize({ width: w, height: h });
}, []);
const ref = useResizeObserver(onResize);
usePropertyDataProviderWithUnifiedSelection({ dataProvider });

return (
<SchemaMetadataContextProvider
imodel={dataProvider.imodel}
schemaContextProvider={getSchemaContext}
>
<div ref={ref} style={{ width: "100%", height: "100%" }}>
<VirtualizedPropertyGridWithDataProvider
height={height}
width={width}
dataProvider={dataProvider}
isPropertyEditingEnabled
onPropertyUpdated={async (args) => {
const message = `Property updated: ${usedEditor} - ${JSON.stringify(args.newValue)}`;
IModelApp.notifications.outputMessage(
new NotifyMessageDetails(
OutputMessagePriority.Info,
message,
undefined,
OutputMessageType.Sticky
)
);
return true;
}}
usedEditor={usedEditor}
/>
</div>
</SchemaMetadataContextProvider>
);
}
5 changes: 5 additions & 0 deletions apps/test-app/src/frontend/registerFrontstages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
createITwinUIV2Frontstage,
createITwinUIV2FrontstageProvider,
} from "./appui/frontstages/iTwinUIV2Frontstage";
import { createPropertyGridProvider } from "./appui/providers/PropertyGridProvider";

interface RegisterFrontstagesArgs {
iModelConnection?: IModelConnection;
Expand Down Expand Up @@ -169,6 +170,10 @@ export function registerFrontstages({
stageIds: [createITwinUIV2Frontstage.stageId],
});

UiItemsManager.register(createPropertyGridProvider(), {
stageIds: [createMainFrontstage.stageId],
});

if (ProcessDetector.isElectronAppFrontend) {
UiFramework.frontstages.addFrontstage(createEditorFrontstage());
UiItemsManager.register(createEditorFrontstageProvider(), {
Expand Down
2 changes: 1 addition & 1 deletion apps/test-app/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function Root() {
const search = Route.useSearch();
const menu = search.menu !== 0;
return (
<ThemeProvider>
<ThemeProvider theme="os">
<PageLayout>
{menu && (
<PageLayout.Header>
Expand Down
34 changes: 17 additions & 17 deletions apps/test-app/src/routes/iTwin_.$iTwinId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,42 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import React from "react";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { IModelGrid } from "@itwin/imodel-browser-react";
import { config } from "../frontend/config";
import { useAuth } from "../frontend/Auth";
import { PageLayout } from "@itwin/itwinui-layouts-react";
import { SignInPage } from "../frontend/SignInPage";
import React from 'react'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant formatter changes

import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { IModelGrid } from '@itwin/imodel-browser-react'
import { config } from '../frontend/config'
import { useAuth } from '../frontend/Auth'
import { PageLayout } from '@itwin/itwinui-layouts-react'
import { SignInPage } from '../frontend/SignInPage'

export const Route = createFileRoute("/iTwin_/$iTwinId")({
component: ITwin,
});
})

const { serverEnvironmentPrefix } = config;
const { serverEnvironmentPrefix } = config
const apiOverrides = {
serverEnvironmentPrefix,
};
}

function ITwin() {
const { accessToken } = useAuth();
const { iTwinId } = Route.useParams();
const navigate = useNavigate();
if (!accessToken) return <SignInPage />;
const { accessToken } = useAuth()
const { iTwinId } = Route.useParams()
const navigate = useNavigate()
if (!accessToken) return <SignInPage />
return (
<PageLayout.Content padded={true}>
<IModelGrid
onThumbnailClick={(iModel) => {
void navigate({
to: "/iTwin/$iTwinId/iModel/$iModelId",
to: '/iTwin/$iTwinId/iModel/$iModelId',
params: { iTwinId, iModelId: iModel.id },
});
})
}}
iTwinId={iTwinId}
accessToken={accessToken}
apiOverrides={apiOverrides}
searchText=""
/>
</PageLayout.Content>
);
)
}
Loading
Loading