Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/portal/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ ARG GIT_HASH
# If the working directory is /src, Parcel will have some problem with it.
WORKDIR /usr/src/app
COPY ./portal/package.json ./portal/package-lock.json ./
# Copy the scripts
COPY ./portal/scripts/. ./scripts/.
# Copy the vite plugin
COPY ./portal/packages/. ./packages/.
RUN npm ci
Expand Down
2 changes: 2 additions & 0 deletions custombuild/cmd/portalx/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ ARG GIT_HASH
# If the working directory is /src, Parcel will have some problem with it.
WORKDIR /usr/src/app
COPY ./portal/package.json ./portal/package-lock.json ./
# Copy the scripts
COPY ./portal/scripts/. ./scripts/.
# Copy the vite plugin
COPY ./portal/packages/. ./packages/.
RUN npm ci
Expand Down
416 changes: 216 additions & 200 deletions portal/package-lock.json

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions portal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
],
"type": "module",
"scripts": {
"postinstall": "./scripts/npm-postinstall.sh",
"test": "jest",
"start": "vite",
"build": "vite build",
Expand Down Expand Up @@ -60,10 +61,11 @@
"@apollo/client": "3.8.7",
"@authgear/web": "1.0.1",
"@elgorditosalsero/react-gtm-hook": "2.7.2",
"@fluentui/merge-styles": "8.5.13",
"@fluentui/react": "8.112.8",
"@fluentui/react-hooks": "8.6.33",
"@fluentui/react-icons": "^2.0.239",
"@fluentui/font-icons-mdl2": "^8.5.55",
"@fluentui/merge-styles": "^8.6.13",
"@fluentui/react": "^8.121.13",
"@fluentui/react-hooks": "^8.8.16",
"@fluentui/react-icons": "^2.0.266",
"@fortawesome/fontawesome-free": "5.15.4",
"@monaco-editor/react": "4.6.0",
"@oursky/react-messageformat": "2.0.2",
Expand Down
45 changes: 45 additions & 0 deletions portal/scripts/npm-postinstall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/sh

set -x

# This is how we self-host the runtime assets of FluentUI.
#
# We utilize the public directory feature of Vite.
# See https://v2.vitejs.dev/guide/assets.html#the-public-directory
# This feature merely copies the files in the public directory to the root of the outDir.
# Since we use FileServer to serve asset, assets have to be put in the asset directory.
# We automate this copy process with a NPM postinstall script. (This script)
#
# Finally we tell fluentui to load the assets from the portal backend, instead of from the default CDN.
# This is done with window.FabricConfig.

# In docker build, the postinstall runs before the src are copied.
# So we run mkdir -p to ensure the directory exist.
mkdir -p ./src/public/shared-assets/
# In case you wonder why we do not just use shell expansion here,
# if ./src/public/shared-assets is really empty, sh DOES NOT expand, and take '*' literally.
# Since we do not have such a file, the command will fail.
find ./src/public/shared-assets -name 'fabric-icons-*.woff' -print -exec rm '{}' \;
# When window.FabricConfig.iconBaseUrl is set, it loads the font directly in the directory.
# So we just copy the fonts to outDir.
cp -r ./node_modules/@fluentui/font-icons-mdl2/fonts/. ./src/public/shared-assets/.

# When window.FabricConfig.fontBaseUrl is set, it loads the font with a certain structure.
# The original URL is https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-bold.woff2
# When fontBaseUrl is set, the URL looks like https://origin/shared-assets/fonts/segoeui-westeuropean/segoeui-bold.woff2

# FluentUI actually has support for many fonts.
# For the full list of the fonts it may load at runtime, see ./node_modules/@fluentui/react/dist/css/fabric.css
# Since our site is lang=en, it will ever load "Segoe UI Web (West European)"
# So we just download and copy them.
# Since this process has to be done once only, the following commands are commented out.
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-light.woff2 -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-light.woff2
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-light.woff -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-light.woff
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-semilight.woff2 -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-semilight.woff2
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-semilight.woff -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-semilight.woff
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-regular.woff2 -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-regular.woff2
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-regular.woff -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-regular.woff
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-semibold.woff2 -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-semibold.woff2
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-semibold.woff -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-semibold.woff
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-bold.woff2 -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-bold.woff2
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-bold.woff -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-bold.woff
17 changes: 5 additions & 12 deletions portal/src/ChoiceButton.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import React, {
useCallback,
ReactNode,
ReactElement,
ComponentType,
} from "react";
import React, { useCallback, ReactElement, ComponentType } from "react";
import {
CompoundButton,
IButtonStyles,
IButtonProps,
useTheme,
IRenderFunction,
Expand All @@ -18,12 +12,12 @@ export interface IconComponentProps {
}

export interface ChoiceButtonProps {
className?: string;
styles?: IButtonStyles;
className?: IButtonProps["className"];
styles?: IButtonProps["styles"];
checked?: IButtonProps["checked"];
disabled?: IButtonProps["disabled"];
text?: ReactNode;
secondaryText?: ReactNode;
text?: IButtonProps["text"];
secondaryText?: IButtonProps["secondaryText"];
onClick?: IButtonProps["onClick"];
IconComponent?: ComponentType<IconComponentProps>;
}
Expand Down Expand Up @@ -87,7 +81,6 @@ export default function ChoiceButton(props: ChoiceButtonProps): ReactElement {
<CompoundButton
{...rest}
toggle={true}
// @ts-expect-error
styles={styles}
onRenderIcon={IconComponent == null ? undefined : onRenderIcon}
/>
Expand Down
1 change: 0 additions & 1 deletion portal/src/TextFieldWithCopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const TextFieldWithCopyButton: React.VFC<TextFieldWithCopyButtonProps> =
function TextFieldWithCopyButton(props: TextFieldWithCopyButtonProps) {
const { disabled, additionalIconButtons, ...rest } = props;
const { themes } = useSystemConfig();
// eslint-disable-next-line no-useless-assignment
const { copyButtonProps, Feedback } = useCopyFeedback({
textToCopy: props.value ?? "",
});
Expand Down
18 changes: 8 additions & 10 deletions portal/src/graphql/portal/LoginMethodConfigurationScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1600,6 +1600,8 @@ function AuthenticationButton(props: AuthenticationButtonProps) {
const checked = targetValue === currentValue;
const iconName = AUTHENTICATION_BUTTON_ICON[targetValue];

const { renderToString } = useContext(Context);

const IconComponent = useCallback(
(props) => {
return (
Expand Down Expand Up @@ -1641,16 +1643,12 @@ function AuthenticationButton(props: AuthenticationButtonProps) {
styles={buttonStyles}
disabled={disabled}
checked={checked}
text={
<FormattedMessage
id={`LoginMethodConfigurationScreen.second-level.${targetValue}.title`}
/>
}
secondaryText={
<FormattedMessage
id={`LoginMethodConfigurationScreen.second-level.${targetValue}.description`}
/>
}
text={renderToString(
`LoginMethodConfigurationScreen.second-level.${targetValue}.title`
)}
secondaryText={renderToString(
`LoginMethodConfigurationScreen.second-level.${targetValue}.description`
)}
IconComponent={IconComponent}
onClick={onClick}
/>
Expand Down
8 changes: 8 additions & 0 deletions portal/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
display: none;
}
</style>
<script>
window.FabricConfig = {
// The lack of trailing slash is important.
fontBaseUrl: window.location.origin + "/shared-assets",
// The trailing slash is important.
iconBaseUrl: window.location.origin + "/shared-assets/",
};
</script>
<link rel="shortcut icon" href="./favicon.png" />
<link rel="stylesheet" href="./index.css" />
<link rel="stylesheet" href="./icons/authgear-portal-icons.css" />
Expand Down
56 changes: 55 additions & 1 deletion portal/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ import "@fortawesome/fontawesome-free/css/all.min.css";
import React from "react";
import { render } from "react-dom";
import { initializeIcons, registerIcons } from "@fluentui/react";

// See below for details.
// Monaco editor initialization imports - Start
import { loader } from "@monaco-editor/react";
import * as monaco from "monaco-editor";
// @ts-expect-error
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
// @ts-expect-error
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
// @ts-expect-error
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
// @ts-expect-error
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
// @ts-expect-error
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
// Monaco editor initialization imports - End

import {
Chart as ChartJS,
CategoryScale,
Expand Down Expand Up @@ -62,4 +79,41 @@ ChartJS.register(
// and AnalyticsSignupMethodsWidget
ChartJS.register(ArcElement, Tooltip);

render(<ReactApp />, document.getElementById("react-app-root"));
// See https://github.com/microsoft/monaco-editor/blob/main/docs/integrate-esm.md#using-vite
// See https://github.com/suren-atoyan/monaco-react?tab=readme-ov-file#use-monaco-editor-as-an-npm-package
//
// By using this approach, it is now our own responsibility to keep monaco-editor and @monaco-editor/react compatible.
// @monaco-editor/react uses @monaco-editor/loader, and @monaco-editor/loader uses a specific version of monaco-editor.
// So when you need to update them, you do
// 1. Pick a version of @monaco-editor/react.
// 2. Inspect @monaco-editor/loader in package-lock.json to see the actual version of @monaco-editor/loader
// 3. Inspect https://github.com/suren-atoyan/monaco-loader/blob/master/src/config/index.js to see what version of monaco-editor it is using.
// 4. Install the same version of monaco-editor.
//
// Note that monaco-editor has some breaking changes in 0.45.0
// See https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md#0450
// So the highest version we can use is 0.44.0, until @monaco-editor/react supports >= 0.45.0
window.MonacoEnvironment = {
getWorker(_, label) {
switch (label) {
case "json":
return new jsonWorker();
case "css":
case "scss":
case "less":
return new cssWorker();
case "html":
case "handlebars":
case "razor":
return new htmlWorker();
case "javascript":
case "typescript":
return new tsWorker();
}
return new editorWorker();
},
};
loader.config({ monaco });
loader.init().then(() => {
render(<ReactApp />, document.getElementById("react-app-root"));
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to commit the files in shared-assets? 🤔
Or they should be generated after npm install?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The postinstall script was intended to automate the update of these files, not for generating the files after npm install. Having the public directory contain we will serve with a simple git checkout is easier to understand.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. But I am worried about the repo size :sosad:

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
24 changes: 13 additions & 11 deletions portal/src/util/mergeStyles.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
import { useCallback, useMemo } from "react";
import { IStyleFunctionOrObject, IStyleSet } from "@fluentui/react";
import { IStyleFunctionOrObject } from "@fluentui/react";
import {
concatStyleSetsWithProps,
concatStyleSets,
IConcatenatedStyleSet,
IStyleSetBase,
} from "@fluentui/merge-styles";

export function useMergedStyles<TStylesProps, IStyleSet>(
...styless: (IStyleFunctionOrObject<TStylesProps, IStyleSet> | undefined)[]
): IStyleFunctionOrObject<TStylesProps, IStyleSet> {
export function useMergedStyles<TStylesProps, TStyleSet extends IStyleSetBase>(
...styless: (IStyleFunctionOrObject<TStylesProps, TStyleSet> | undefined)[]
): (
props: TStylesProps
) => ReturnType<typeof concatStyleSetsWithProps<TStylesProps, TStyleSet>> {
return useCallback(
(props) => {
return concatStyleSetsWithProps(props, ...styless);
},
// eslint-disable-next-line
// eslint-disable-next-line react-hooks/exhaustive-deps
[...styless]
);
}

export function useMergedStylesPlain(
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
...styless: (IStyleSet | undefined)[]
): IConcatenatedStyleSet<IStyleSet> {
export function useMergedStylesPlain<TStyleSet extends IStyleSetBase>(
...styless: (TStyleSet | undefined)[]
): IConcatenatedStyleSet<TStyleSet> {
return useMemo(
() => concatStyleSets(...styless),
// eslint-disable-next-line
// eslint-disable-next-line react-hooks/exhaustive-deps
[...styless]
) as IConcatenatedStyleSet<IStyleSet>;
) as IConcatenatedStyleSet<TStyleSet>;
}