0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat(console,phrases): integrate monaco editor (#5460)

* feat(console,phrases): integrate monaco editor

integrate monaco editor

* chore: update pnpm lock file

update pnpm lock file

* fix(console): remove unused styles

remove unused styles

* feat(console): update font styles

update font styles
This commit is contained in:
simeng-li 2024-03-06 14:08:46 +08:00 committed by GitHub
parent e1def81ed5
commit 2f72f8ffd7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 574 additions and 36 deletions

View file

@ -36,6 +36,7 @@
"@logto/schemas": "workspace:^1.13.1",
"@logto/shared": "workspace:^3.1.0",
"@mdx-js/react": "^1.6.22",
"@monaco-editor/react": "^4.6.0",
"@parcel/compressor-brotli": "2.9.3",
"@parcel/compressor-gzip": "2.9.3",
"@parcel/core": "2.9.3",

View file

@ -0,0 +1,24 @@
import { type EditorProps } from '@monaco-editor/react';
import type { IStandaloneThemeData } from './type';
// Logto dark theme extends vs-dark theme
export const logtoDarkTheme: IStandaloneThemeData = {
base: 'vs-dark',
inherit: true,
rules: [],
colors: {
'editor.background': '#181133', // :token/code/code-bg
},
};
// @see {@link https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html}
export const defaultOptions: EditorProps['options'] = {
minimap: {
enabled: false,
},
wordWrap: 'on',
renderLineHighlight: 'none',
fontFamily: 'Roboto Mono, monospace',
fontSize: 14,
};

View file

@ -0,0 +1,58 @@
@use '@/scss/underscore' as _;
@use '@/scss/code-editor' as codeEditor;
.codeEditor {
@include codeEditor.color;
@include codeEditor.font;
position: relative;
display: flex;
flex-direction: column;
border-radius: 8px;
background-color: var(--color-code-bg);
header {
padding: _.unit(4);
display: flex;
justify-content: space-between;
align-items: center;
.tabList {
display: flex;
gap: _.unit(2);
.tab {
padding: _.unit(1.5) _.unit(3);
font: var(--font-label-2);
font-family: 'Roboto Mono', monospace;
color: var(--color-code-grey);
cursor: pointer;
&.active,
&:hover {
color: var(--color-code-white);
background-color: var(--color-code-tab-active-bg);
border-radius: 8px;
}
}
}
.title {
font: var(--font-label-2);
font-family: 'Roboto Mono', monospace;
color: var(--color-code-white);
}
.actions {
display: flex;
gap: _.unit(2);
align-items: center;
}
}
.editorContainer {
position: relative;
flex-grow: 1;
padding-bottom: _.unit(4);
}
}

View file

@ -0,0 +1,126 @@
import { Editor, type BeforeMount, type OnMount, useMonaco } from '@monaco-editor/react';
import { type Nullable } from '@silverhand/essentials';
import classNames from 'classnames';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import Copy from '@/assets/icons/copy.svg';
import IconButton from '@/ds-components/IconButton';
import { onKeyDownHandler } from '@/utils/a11y';
import { logtoDarkTheme, defaultOptions } from './config.js';
import * as styles from './index.module.scss';
import type { IStandaloneCodeEditor, Model } from './type.js';
type Props = {
className?: string;
actions?: React.ReactNode;
models: Model[];
};
function MonacoCodeEditor({ className, actions, models }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const monaco = useMonaco();
const editorRef = useRef<Nullable<IStandaloneCodeEditor>>(null);
const [activeModelName, setActiveModelName] = useState<string>();
const activeModel = useMemo(
() => models.find((model) => model.name === activeModelName),
[activeModelName, models]
);
// Set the first model as the active model
useEffect(() => {
setActiveModelName(models[0]?.name);
}, [models]);
useEffect(() => {
// Add global declarations
// monaco will be ready after the editor is mounted, useEffect will be called after the monaco is ready
if (!monaco || !activeModel) {
return;
}
// Set the global declarations for the active model
// @see {@link https://microsoft.github.io/monaco-editor/typedoc/interfaces/languages.typescript.LanguageServiceDefaults.html#setExtraLibs}
if (activeModel.globalDeclarations) {
monaco.languages.typescript.typescriptDefaults.setExtraLibs([
{
content: activeModel.globalDeclarations,
filePath: `file:///global.d.ts`,
},
]);
}
}, [activeModel, monaco]);
const handleCodeCopy = useCallback(async () => {
const editor = editorRef.current;
if (editor) {
const code = editor.getValue();
await navigator.clipboard.writeText(code);
toast.success(t('general.copied'));
}
}, [t]);
const handleEditorWillMount = useCallback<BeforeMount>((monaco) => {
// Register the new logto theme
monaco.editor.defineTheme('logto-dark', logtoDarkTheme);
}, []);
const handleEditorDidMount = useCallback<OnMount>((editor) => {
// eslint-disable-next-line @silverhand/fp/no-mutation
editorRef.current = editor;
}, []);
return (
<div className={classNames(className, styles.codeEditor)}>
<header>
{models.length > 1 ? (
<div className={styles.tabList}>
{models.map(({ name }) => (
<div
key={name}
className={classNames(styles.tab, name === activeModelName && styles.active)}
role="button"
tabIndex={0}
onClick={() => {
setActiveModelName(name);
}}
onKeyDown={onKeyDownHandler(() => {
setActiveModelName(name);
})}
>
{name}
</div>
))}
</div>
) : (
<div className={styles.title}>{activeModel?.title}</div>
)}
<div className={styles.actions}>
{actions}
<IconButton size="small" onClick={handleCodeCopy}>
<Copy />
</IconButton>
</div>
</header>
<div className={styles.editorContainer}>
<Editor
height="100%"
language="typescript"
// TODO: need to check on the usage of value and defaultValue
defaultValue={activeModel?.defaultValue}
path={activeModel?.name}
theme="logto-dark"
options={defaultOptions}
beforeMount={handleEditorWillMount}
onMount={handleEditorDidMount}
/>
</div>
</div>
);
}
export default MonacoCodeEditor;

View file

@ -0,0 +1,15 @@
import { type Monaco, type OnMount } from '@monaco-editor/react';
export type IStandaloneThemeData = Parameters<Monaco['editor']['defineTheme']>[1];
export type IStandaloneCodeEditor = Parameters<OnMount>[0];
export type Model = {
/** Used as the unique key for the monaco editor model @see {@link https://github.com/suren-atoyan/monaco-react?tab=readme-ov-file#multi-model-editor} */
name: string;
/** The title of the model */
title: string;
defaultValue: string;
language: string;
globalDeclarations?: string;
};

View file

@ -0,0 +1,118 @@
import type { Model } from './MonacoCodeEditor/type.js';
const userJwtGlobalDeclarations = `
declare global {
export interface CustomJwtClaims extends Record<string, any> {}
/** The user info associated with the token.
*
* @param {string} id - The user id
* @param {string} [primaryEmail] - The user email
* @param {string} [primaryPhone] - The user phone
* @param {string} [username] - The user username
* @param {string} [name] - The user name
* @param {string} [avatar] - The user avatar
*
*/
export type User = {
id: string;
primaryEmail?: string;
primaryPhone?: string;
username?: string;
name?: string;
avatar?: string;
}
/** Logto internal data that can be used to pass additional information
* @param {User} user - The user info associated with the token.
*/
export type Data = {
user: User;
}
export interface Exports {
/**
* This function is called to get custom claims for the JWT token.
*
* @param {string} token -The JWT token.
* @param {Data} data - Logto internal data that can be used to pass additional information
* @param {User} data.user - The user info associated with the token.
*
* @returns The custom claims.
*/
getCustomJwtClaims: (token: string, data: Data) => Promise<CustomJwtClaims>;
}
const exports: Exports;
}
export { exports as default };
`;
const machineToMachineJwtGlobalDeclarations = `
declare global {
export interface CustomJwtClaims extends Record<string, any> {}
export interface Exports {
/**
* This function is called to get custom claims for the JWT token.
*
* @param {string} token -The JWT token.
*
* @returns The custom claims.
*/
getCustomJwtClaims: (token: string) => Promise<CustomJwtClaims>;
}
const exports: Exports;
}
export { exports as default };
`;
const defaultUserJwtClaimsCode = `/**
* This function is called to get custom claims for the JWT token.
*
* @param {string} token -The JWT token.
* @param {Data} data - Logto internal data that can be used to pass additional information
* @param {User} data.user - The user info associated with the token.
*
* @returns The custom claims.
*/
exports.getCustomJwtClaims = async (token, data) => {
return {};
}`;
const defaultMachineToMachineJwtClaimsCode = `/**
* This function is called to get custom claims for the JWT token.
*
* @param {string} token -The JWT token.
*
* @returns The custom claims.
*/
exports.getCustomJwtClaims = async (token) => {
return {};
}`;
export const userJwtFile: Model = {
name: 'user-jwt.ts',
title: 'TypeScript',
language: 'typescript',
defaultValue: defaultUserJwtClaimsCode,
globalDeclarations: userJwtGlobalDeclarations,
};
export const machineToMachineJwtFile: Model = {
name: 'machine-to-machine-jwt.ts',
title: 'TypeScript',
language: 'typescript',
defaultValue: defaultMachineToMachineJwtClaimsCode,
globalDeclarations: machineToMachineJwtGlobalDeclarations,
};
export enum JwtTokenType {
UserAccessToken = 'user-access-token',
MachineToMachineAccessToken = 'm2m-access-token',
}

View file

@ -5,8 +5,39 @@
flex-direction: column;
height: 100%;
.cardTitle {
.header {
flex-shrink: 0;
margin-bottom: _.unit(4);
}
.tabNav {
margin-bottom: _.unit(4);
}
}
.tabContent {
display: flex;
flex-direction: row;
gap: _.unit(2);
flex-grow: 1;
> * {
flex: 1;
}
}
.codePanel {
margin-bottom: _.unit(6);
position: relative;
display: flex;
flex-direction: column;
.cardTitle {
font: var(--font-title-2);
margin-bottom: _.unit(3);
}
.flexGrow {
flex-grow: 1;
}
}

View file

@ -1,44 +1,73 @@
import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact';
import classNames from 'classnames';
import type { TFuncKey } from 'i18next';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import Card from '@/ds-components/Card';
import CardTitle from '@/ds-components/CardTitle';
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
import MonacoCodeEditor from './MonacoCodeEditor';
import { type Model } from './MonacoCodeEditor/type';
import { userJwtFile, machineToMachineJwtFile, JwtTokenType } from './config';
import * as styles from './index.module.scss';
import { JwtTokenType } from './type';
export * from './type';
export { JwtTokenType } from './config';
type Props = {
tab: JwtTokenType;
};
const phrases = Object.freeze({
tab: {
[JwtTokenType.UserAccessToken]: 'user_jwt_tab',
[JwtTokenType.MachineToMachineAccessToken]: 'machine_to_machine_jwt_tab',
},
token: {
[JwtTokenType.UserAccessToken]: 'user_jwt',
[JwtTokenType.MachineToMachineAccessToken]: 'machine_to_machine_jwt',
},
} satisfies Record<
string,
Record<JwtTokenType, TFuncKey<'translation', 'admin_console.jwt_claims'>>
>);
const getPath = (tab: JwtTokenType) => `/jwt-claims/${tab}`;
function JwtClaims({ tab }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
// TODO: API integration, read/write the custom claims code value
const activeModel = useMemo<Model>(() => {
return tab === JwtTokenType.UserAccessToken ? userJwtFile : machineToMachineJwtFile;
}, [tab]);
return (
<div className={styles.container}>
<CardTitle
title="jwt_claims.title"
subtitle="jwt_claims.description"
className={styles.cardTitle}
className={styles.header}
/>
<TabNav>
<TabNavItem
href={getPath(JwtTokenType.UserAccessToken)}
isActive={tab === JwtTokenType.UserAccessToken}
>
{t('jwt_claims.user_jwt_tab')}
</TabNavItem>
<TabNavItem
href={getPath(JwtTokenType.MachineToMachineAccessToken)}
isActive={tab === JwtTokenType.MachineToMachineAccessToken}
>
{t('jwt_claims.machine_to_machine_jwt_tab')}
<TabNav className={styles.tabNav}>
{Object.values(JwtTokenType).map((tokenType) => (
<TabNavItem key={tokenType} href={getPath(tokenType)} isActive={tokenType === tab}>
{t(`jwt_claims.${phrases.tab[tokenType]}`)}
</TabNavItem>
))}
</TabNav>
<form className={classNames(styles.tabContent)}>
<Card className={styles.codePanel}>
<div className={styles.cardTitle}>
{t('jwt_claims.code_editor_title', {
token: t(`jwt_claims.${phrases.token[tab]}`),
})}
</div>
<MonacoCodeEditor className={styles.flexGrow} models={[activeModel]} />
</Card>
<div>Form Panel</div>
</form>
</div>
);
}

View file

@ -1,4 +0,0 @@
export enum JwtTokenType {
UserAccessToken = 'user-access-token',
MachineToMachineAccessToken = 'm2m-access-token',
}

View file

@ -0,0 +1,10 @@
@mixin color {
--color-code-bg: #181133;
--color-code-white: var(--color-base);
--color-code-grey: #adaab4;
--color-code-tab-active-bg: rgba(255, 255, 255, 24%);
}
@mixin font {
--font-code: 500 14px/20px 'Roboto Mono', monospace;
}

View file

@ -9,6 +9,7 @@ body {
color: var(--color-text);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: auto;
min-height: 100vh;
}
* {

View file

@ -39,6 +39,14 @@ export default function koaSecurityHeaders<StateT, ContextT, ResponseBodyT>(
const developmentOrigins = conditionalArray(!isProduction && 'ws:');
const appInsightsOrigins = ['https://*.applicationinsights.azure.com'];
// We use react-monaco-editor for code editing in the admin console. It loads the monaco editor asynchronously from a CDN.
// Allow the CDN src in the CSP.
// Allow blob: for monaco editor to load worker scripts
const monacoEditorCDNSource = [
'https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs/',
'blob:',
];
/**
* Default Applied rules:
* - crossOriginOpenerPolicy: https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#cross-origin-opener-policy-coop
@ -107,6 +115,7 @@ export default function koaSecurityHeaders<StateT, ContextT, ResponseBodyT>(
scriptSrc: [
"'self'",
...conditionalArray(!isProduction && ["'unsafe-eval'", "'unsafe-inline'"]),
...monacoEditorCDNSource,
],
connectSrc: [
"'self'",

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -3,7 +3,10 @@ const jwt_claims = {
description:
'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
user_jwt_tab: 'User JWT',
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
user_jwt: 'user JWT',
machine_to_machine_jwt: 'machine-to-machine JWT',
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -7,7 +7,13 @@ const jwt_claims = {
/** UNTRANSLATED */
user_jwt_tab: 'User JWT',
/** UNTRANSLATED */
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
/** UNTRANSLATED */
user_jwt: 'user JWT',
/** UNTRANSLATED */
machine_to_machine_jwt: 'machine-to-machine JWT',
/** UNTRANSLATED */
code_editor_title: 'Customize the {{token}} claims',
};
export default Object.freeze(jwt_claims);

View file

@ -2948,6 +2948,9 @@ importers:
'@mdx-js/react':
specifier: ^1.6.22
version: 1.6.22(react@18.2.0)
'@monaco-editor/react':
specifier: ^4.6.0
version: 4.6.0(monaco-editor@0.46.0)(react-dom@18.2.0)(react@18.2.0)
'@parcel/compressor-brotli':
specifier: 2.9.3
version: 2.9.3(@parcel/core@2.9.3)
@ -7933,6 +7936,28 @@ packages:
json5: 2.2.1
dev: true
/@monaco-editor/loader@1.4.0(monaco-editor@0.46.0):
resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==}
peerDependencies:
monaco-editor: '>= 0.21.0 < 1'
dependencies:
monaco-editor: 0.46.0
state-local: 1.0.7
dev: true
/@monaco-editor/react@4.6.0(monaco-editor@0.46.0)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==}
peerDependencies:
monaco-editor: '>= 0.25.0 < 1'
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@monaco-editor/loader': 1.4.0(monaco-editor@0.46.0)
monaco-editor: 0.46.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: true
/@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2:
resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==}
cpu: [arm64]
@ -17200,6 +17225,10 @@ packages:
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
dev: false
/monaco-editor@0.46.0:
resolution: {integrity: sha512-ADwtLIIww+9FKybWscd7OCfm9odsFYHImBRI1v9AviGce55QY8raT+9ihH8jX/E/e6QVSGM+pKj4jSUSRmALNQ==}
dev: true
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
@ -20081,6 +20110,10 @@ packages:
escape-string-regexp: 2.0.0
dev: true
/state-local@1.0.7:
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
dev: true
/state-toggle@1.0.3:
resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==}
dev: true