diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 7a44fe2b22..710aaf02f9 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/setup-node@v2 with: - node-version: "14.17.6" + node-version: "14.21.3" - uses: actions/checkout@v2 - name: Turbo Cache id: turbo-cache @@ -44,8 +44,6 @@ jobs: name: storybooks path: | packages/fuselage/storybook-static - packages/fuselage-ui-kit/storybook-static - packages/uikit-playground/build packages/onboarding-ui/storybook-static packages/layout/storybook-static - if: github.ref == 'refs/heads/master' @@ -71,7 +69,7 @@ jobs: steps: - uses: actions/setup-node@v2 with: - node-version: "14.17.6" + node-version: "14.21.3" registry-url: "https://registry.npmjs.org" scope: "@rocket.chat" - uses: actions/checkout@v2 @@ -125,10 +123,8 @@ jobs: name: docs path: . - run: | - rm -rf "fuselage/${{ needs.build-and-test.outputs.branch-name }}" "layout/${{ needs.build-and-test.outputs.branch-name }}" "uikit-playground/${{ needs.build-and-test.outputs.branch-name }}" "fuselage-ui-kit/${{ needs.build-and-test.outputs.branch-name }}" "onboarding-ui/${{ needs.build-and-test.outputs.branch-name }}" + rm -rf "fuselage/${{ needs.build-and-test.outputs.branch-name }}" "layout/${{ needs.build-and-test.outputs.branch-name }}" "onboarding-ui/${{ needs.build-and-test.outputs.branch-name }}" mv -v "packages/fuselage/storybook-static" "fuselage/${{ needs.build-and-test.outputs.branch-name }}" - mv -v "packages/uikit-playground/build" "uikit-playground/${{ needs.build-and-test.outputs.branch-name }}" - mv -v "packages/fuselage-ui-kit/storybook-static" "fuselage-ui-kit/${{ needs.build-and-test.outputs.branch-name }}" mv -v "packages/onboarding-ui/storybook-static" "onboarding-ui/${{ needs.build-and-test.outputs.branch-name }}" mv -v "packages/layout/storybook-static" "layout/${{ needs.build-and-test.outputs.branch-name }}" rm -rf packages diff --git a/.github/workflows/ci-pr-closed.yml b/.github/workflows/ci-pr-closed.yml index 613c3ae58a..3c441576cf 100644 --- a/.github/workflows/ci-pr-closed.yml +++ b/.github/workflows/ci-pr-closed.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v2 with: ref: gh-pages - - run: rm -rf "fuselage/${{ github.event.number }}" "layout/${{ github.event.number }}" "fuselage-ui-kit/${{ github.event.number }}" "uikit-playground/${{ github.event.number }}" "onboarding-ui/${{ github.event.number }}" + - run: rm -rf "fuselage/${{ github.event.number }}" "layout/${{ github.event.number }}" "onboarding-ui/${{ github.event.number }}" - uses: crazy-max/ghaction-github-pages@v2 with: target_branch: gh-pages diff --git a/.github/workflows/ci-pr-opened.yml b/.github/workflows/ci-pr-opened.yml index 20604660c6..a8120e6270 100644 --- a/.github/workflows/ci-pr-opened.yml +++ b/.github/workflows/ci-pr-opened.yml @@ -42,8 +42,6 @@ jobs: name: storybooks path: | fuselage/storybook-static - uikit-playground/build - fuselage-ui-kit/storybook-static onboarding-ui/storybook-static layout/storybook-static publish-to-gh-pages: @@ -65,8 +63,6 @@ jobs: - run: | rm -rf "fuselage/${{ needs.download-artifact.outputs.pr-number }}" "layout/${{ needs.download-artifact.outputs.pr-number }}" "uikit-playground/${{ needs.download-artifact.outputs.pr-number }}" "fuselage-ui-kit/${{ needs.download-artifact.outputs.pr-number }}" "onboarding-ui/${{ needs.download-artifact.outputs.pr-number }}" mv -v packages/fuselage/storybook-static "fuselage/${{ needs.download-artifact.outputs.pr-number }}" - mv -v packages/uikit-playground/build "uikit-playground/${{ needs.download-artifact.outputs.pr-number }}" - mv -v packages/fuselage-ui-kit/storybook-static "fuselage-ui-kit/${{ needs.download-artifact.outputs.pr-number }}" mv -v packages/onboarding-ui/storybook-static "onboarding-ui/${{ needs.download-artifact.outputs.pr-number }}" mv -v packages/layout/storybook-static "layout/${{ needs.download-artifact.outputs.pr-number }}" rm -rf packages diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index 44e51812ef..0cbc2dd304 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-node@v2 with: - node-version: "14.17.6" + node-version: "14.21.3" - uses: actions/checkout@v2 - uses: actions/cache@v2 id: yarn-cache @@ -42,7 +42,5 @@ jobs: name: "storybooks-${{ github.event.number }}" path: | packages/fuselage/storybook-static - packages/uikit-playground/build - packages/fuselage-ui-kit/storybook-static packages/onboarding-ui/storybook-static packages/layout/storybook-static diff --git a/.vscode/settings.json b/.vscode/settings.json index 19c965cbde..076e091e14 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,12 +10,21 @@ } ], "eslint.onIgnoredFiles": "warn", - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - }, "eslint.options": { "extensions": [".js", ".jsx", ".md", ".mdx", ".ts", ".tsx", ".pegjs"] }, + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact", + "markdown", + "mdx", + "pegjs" + ], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, "editor.formatOnSave": true, "[typescript]": { "editor.formatOnSave": false diff --git a/.yarn/patches/@storybook-react-docgen-typescript-plugin-npm-1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0-b31cc57c40.patch b/.yarn/patches/@storybook-react-docgen-typescript-plugin-npm-1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0-b31cc57c40.patch new file mode 100644 index 0000000000..ca069ee350 --- /dev/null +++ b/.yarn/patches/@storybook-react-docgen-typescript-plugin-npm-1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0-b31cc57c40.patch @@ -0,0 +1,130 @@ +diff --git a/dist/generateDocgenCodeBlock.js b/dist/generateDocgenCodeBlock.js +index 0993ac13e4b2aae6d24cf408d6a585b4ddeb7337..1405896291288eb1322d6c42144afd3b4fbd1abf 100644 +--- a/dist/generateDocgenCodeBlock.js ++++ b/dist/generateDocgenCodeBlock.js +@@ -34,7 +34,7 @@ function insertTsIgnoreBeforeStatement(statement) { + * ``` + */ + function setDisplayName(d) { +- return insertTsIgnoreBeforeStatement(typescript_1.default.createExpressionStatement(typescript_1.default.createBinary(typescript_1.default.createPropertyAccess(typescript_1.default.createIdentifier(d.displayName), typescript_1.default.createIdentifier("displayName")), typescript_1.default.SyntaxKind.EqualsToken, typescript_1.default.createLiteral(d.displayName)))); ++ return insertTsIgnoreBeforeStatement(typescript_1.default.factory.createExpressionStatement(typescript_1.default.factory.createBinaryExpression(typescript_1.default.factory.createPropertyAccessExpression(typescript_1.default.factory.createIdentifier(d.displayName), typescript_1.default.factory.createIdentifier("displayName")), typescript_1.default.SyntaxKind.EqualsToken, typescript_1.default.factory.createStringLiteral(d.displayName)))); + } + /** + * Set a component prop description. +@@ -65,7 +65,7 @@ function createPropDefinition(propName, prop, options) { + * + * @param defaultValue Default prop value or null if not set. + */ +- const setDefaultValue = (defaultValue) => typescript_1.default.createPropertyAssignment(typescript_1.default.createLiteral("defaultValue"), ++ const setDefaultValue = (defaultValue) => typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createStringLiteral("defaultValue"), + // Use a more extensive check on defaultValue. Sometimes the parser + // returns an empty object. + defaultValue !== null && +@@ -75,12 +75,19 @@ function createPropDefinition(propName, prop, options) { + (typeof defaultValue.value === "string" || + typeof defaultValue.value === "number" || + typeof defaultValue.value === "boolean") +- ? typescript_1.default.createObjectLiteral([ +- typescript_1.default.createPropertyAssignment(typescript_1.default.createIdentifier("value"), typescript_1.default.createLiteral(defaultValue.value)), ++ ? typescript_1.default.factory.createObjectLiteralExpression([ ++ typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createIdentifier("value"), typeof defaultValue.value === "string" ++ ? typescript_1.default.factory.createStringLiteral(defaultValue.value) ++ : // eslint-disable-next-line no-nested-ternary ++ typeof defaultValue.value === "number" ++ ? typescript_1.default.factory.createNumericLiteral(defaultValue.value) ++ : defaultValue.value ++ ? typescript_1.default.factory.createTrue() ++ : typescript_1.default.factory.createFalse()), + ]) +- : typescript_1.default.createNull()); ++ : typescript_1.default.factory.createNull()); + /** Set a property with a string value */ +- const setStringLiteralField = (fieldName, fieldValue) => typescript_1.default.createPropertyAssignment(typescript_1.default.createLiteral(fieldName), typescript_1.default.createLiteral(fieldValue)); ++ const setStringLiteralField = (fieldName, fieldValue) => typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createStringLiteral(fieldName), typescript_1.default.factory.createStringLiteral(fieldValue)); + /** + * ``` + * SimpleComponent.__docgenInfo.props.someProp.description = "Prop description."; +@@ -101,7 +108,7 @@ function createPropDefinition(propName, prop, options) { + * ``` + * @param required Whether prop is required or not. + */ +- const setRequired = (required) => typescript_1.default.createPropertyAssignment(typescript_1.default.createLiteral("required"), required ? typescript_1.default.createTrue() : typescript_1.default.createFalse()); ++ const setRequired = (required) => typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createStringLiteral("required"), required ? typescript_1.default.factory.createTrue() : typescript_1.default.factory.createFalse()); + /** + * ``` + * SimpleComponent.__docgenInfo.props.someProp.type = { +@@ -113,7 +120,7 @@ function createPropDefinition(propName, prop, options) { + */ + const setValue = (typeValue) => Array.isArray(typeValue) && + typeValue.every((value) => typeof value.value === "string") +- ? typescript_1.default.createPropertyAssignment(typescript_1.default.createLiteral("value"), typescript_1.default.createArrayLiteral(typeValue.map((value) => typescript_1.default.createObjectLiteral([ ++ ? typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createStringLiteral("value"), typescript_1.default.factory.createArrayLiteralExpression(typeValue.map((value) => typescript_1.default.factory.createObjectLiteralExpression([ + setStringLiteralField("value", value.value), + ])))) + : undefined; +@@ -130,9 +137,9 @@ function createPropDefinition(propName, prop, options) { + if (valueField) { + objectFields.push(valueField); + } +- return typescript_1.default.createPropertyAssignment(typescript_1.default.createLiteral(options.typePropName), typescript_1.default.createObjectLiteral(objectFields)); ++ return typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createStringLiteral(options.typePropName), typescript_1.default.factory.createObjectLiteralExpression(objectFields)); + }; +- return typescript_1.default.createPropertyAssignment(typescript_1.default.createLiteral(propName), typescript_1.default.createObjectLiteral([ ++ return typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createStringLiteral(propName), typescript_1.default.factory.createObjectLiteralExpression([ + setDefaultValue(prop.defaultValue), + setDescription(prop.description), + setName(prop.name), +@@ -158,10 +165,10 @@ function createPropDefinition(propName, prop, options) { + * @param relativeFilename Relative file path of the component source file. + */ + function insertDocgenIntoGlobalCollection(d, docgenCollectionName, relativeFilename) { +- return insertTsIgnoreBeforeStatement(typescript_1.default.createIf(typescript_1.default.createBinary(typescript_1.default.createTypeOf(typescript_1.default.createIdentifier(docgenCollectionName)), typescript_1.default.SyntaxKind.ExclamationEqualsEqualsToken, typescript_1.default.createLiteral("undefined")), insertTsIgnoreBeforeStatement(typescript_1.default.createStatement(typescript_1.default.createBinary(typescript_1.default.createElementAccess(typescript_1.default.createIdentifier(docgenCollectionName), typescript_1.default.createLiteral(`${relativeFilename}#${d.displayName}`)), typescript_1.default.SyntaxKind.EqualsToken, typescript_1.default.createObjectLiteral([ +- typescript_1.default.createPropertyAssignment(typescript_1.default.createIdentifier("docgenInfo"), typescript_1.default.createPropertyAccess(typescript_1.default.createIdentifier(d.displayName), typescript_1.default.createIdentifier("__docgenInfo"))), +- typescript_1.default.createPropertyAssignment(typescript_1.default.createIdentifier("name"), typescript_1.default.createLiteral(d.displayName)), +- typescript_1.default.createPropertyAssignment(typescript_1.default.createIdentifier("path"), typescript_1.default.createLiteral(`${relativeFilename}#${d.displayName}`)), ++ return insertTsIgnoreBeforeStatement(typescript_1.default.factory.createIfStatement(typescript_1.default.factory.createBinaryExpression(typescript_1.default.factory.createTypeOfExpression(typescript_1.default.factory.createIdentifier(docgenCollectionName)), typescript_1.default.SyntaxKind.ExclamationEqualsEqualsToken, typescript_1.default.factory.createStringLiteral("undefined")), insertTsIgnoreBeforeStatement(typescript_1.default.factory.createExpressionStatement(typescript_1.default.factory.createBinaryExpression(typescript_1.default.factory.createElementAccessExpression(typescript_1.default.factory.createIdentifier(docgenCollectionName), typescript_1.default.factory.createStringLiteral(`${relativeFilename}#${d.displayName}`)), typescript_1.default.SyntaxKind.EqualsToken, typescript_1.default.factory.createObjectLiteralExpression([ ++ typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createIdentifier("docgenInfo"), typescript_1.default.factory.createPropertyAccessExpression(typescript_1.default.factory.createIdentifier(d.displayName), typescript_1.default.factory.createIdentifier("__docgenInfo"))), ++ typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createIdentifier("name"), typescript_1.default.factory.createStringLiteral(d.displayName)), ++ typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createIdentifier("path"), typescript_1.default.factory.createStringLiteral(`${relativeFilename}#${d.displayName}`)), + ])))))); + } + /** +@@ -180,15 +187,15 @@ function insertDocgenIntoGlobalCollection(d, docgenCollectionName, relativeFilen + * @param options Generator options. + */ + function setComponentDocGen(d, options) { +- return insertTsIgnoreBeforeStatement(typescript_1.default.createStatement(typescript_1.default.createBinary( ++ return insertTsIgnoreBeforeStatement(typescript_1.default.factory.createExpressionStatement(typescript_1.default.factory.createBinaryExpression( + // SimpleComponent.__docgenInfo +- typescript_1.default.createPropertyAccess(typescript_1.default.createIdentifier(d.displayName), typescript_1.default.createIdentifier("__docgenInfo")), typescript_1.default.SyntaxKind.EqualsToken, typescript_1.default.createObjectLiteral([ ++ typescript_1.default.factory.createPropertyAccessExpression(typescript_1.default.factory.createIdentifier(d.displayName), typescript_1.default.factory.createIdentifier("__docgenInfo")), typescript_1.default.SyntaxKind.EqualsToken, typescript_1.default.factory.createObjectLiteralExpression([ + // SimpleComponent.__docgenInfo.description +- typescript_1.default.createPropertyAssignment(typescript_1.default.createLiteral("description"), typescript_1.default.createLiteral(d.description)), ++ typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createStringLiteral("description"), typescript_1.default.factory.createStringLiteral(d.description)), + // SimpleComponent.__docgenInfo.displayName +- typescript_1.default.createPropertyAssignment(typescript_1.default.createLiteral("displayName"), typescript_1.default.createLiteral(d.displayName)), ++ typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createStringLiteral("displayName"), typescript_1.default.factory.createStringLiteral(d.displayName)), + // SimpleComponent.__docgenInfo.props +- typescript_1.default.createPropertyAssignment(typescript_1.default.createLiteral("props"), typescript_1.default.createObjectLiteral(Object.entries(d.props).map(([propName, prop]) => createPropDefinition(propName, prop, options)))), ++ typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createStringLiteral("props"), typescript_1.default.factory.createObjectLiteralExpression(Object.entries(d.props).map(([propName, prop]) => createPropDefinition(propName, prop, options)))), + ])))); + } + function generateDocgenCodeBlock(options) { +@@ -196,7 +203,7 @@ function generateDocgenCodeBlock(options) { + const relativeFilename = path_1.default + .relative("./", path_1.default.resolve("./", options.filename)) + .replace(/\\/g, "/"); +- const wrapInTryStatement = (statements) => typescript_1.default.createTry(typescript_1.default.createBlock(statements, true), typescript_1.default.createCatchClause(typescript_1.default.createVariableDeclaration(typescript_1.default.createIdentifier("__react_docgen_typescript_loader_error")), typescript_1.default.createBlock([])), undefined); ++ const wrapInTryStatement = (statements) => typescript_1.default.factory.createTryStatement(typescript_1.default.factory.createBlock(statements, true), typescript_1.default.factory.createCatchClause(typescript_1.default.factory.createVariableDeclaration(typescript_1.default.factory.createIdentifier("__react_docgen_typescript_loader_error")), typescript_1.default.factory.createBlock([])), undefined); + const codeBlocks = options.componentDocs.map((d) => wrapInTryStatement([ + options.setDisplayName ? setDisplayName(d) : null, + setComponentDocGen(d, options), +@@ -208,7 +215,7 @@ function generateDocgenCodeBlock(options) { + const printer = typescript_1.default.createPrinter({ newLine: typescript_1.default.NewLineKind.LineFeed }); + const printNode = (sourceNode) => printer.printNode(typescript_1.default.EmitHint.Unspecified, sourceNode, sourceFile); + // Concat original source code with code from generated code blocks. +- const result = codeBlocks.reduce((acc, node) => `${acc}\n${printNode(node)}`, ++ const result = codeBlocks.reduce((acc, node) => `${acc}\n${printNode(node)}`, + // Use original source text rather than using printNode on the parsed form + // to prevent issue where literals are stripped within components. + // Ref: https://github.com/strothj/react-docgen-typescript-loader/issues/7 diff --git a/README.md b/README.md index 9fb5e0f754..748c8a8c59 100644 --- a/README.md +++ b/README.md @@ -10,29 +10,27 @@ ![Pull requests](https://img.shields.io/github/issues-pr/RocketChat/fuselage?style=flat-square) ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/RocketChat/fuselage?style=flat-square) -| Package | Description | Version | Dependencies | -|---------|-------------|---------|--------------| -| 📦 [`@rocket.chat/css-in-js`](/packages/css-in-js) | Toolset to transpile and use CSS on runtime | [![npm](https://img.shields.io/npm/v/@rocket.chat/css-in-js?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/css-in-js) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/css-in-js?style=flat-square) | -| 📦 [`@rocket.chat/css-supports`](/packages/css-supports) | Memoized and SSR-compatible facade of CSS.supports API | [![npm](https://img.shields.io/npm/v/@rocket.chat/css-supports?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/css-supports) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/css-supports?style=flat-square) | -| 📦 [`@rocket.chat/emitter`](/packages/emitter) | Event Emitter by Rocket.Chat | [![npm](https://img.shields.io/npm/v/@rocket.chat/emitter?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/emitter) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/emitter?style=flat-square) | -| 📦 [`@rocket.chat/eslint-config-alt`](/packages/eslint-config-alt) | ESLint configuration for Rocket.Chat repositories | [![npm](https://img.shields.io/npm/v/@rocket.chat/eslint-config-alt?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/eslint-config-alt) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/eslint-config-alt?style=flat-square) | -| 📦 [`@rocket.chat/fuselage`](/packages/fuselage) | Rocket.Chat's React Components Library | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage?style=flat-square) | -| 📦 [`@rocket.chat/fuselage-hooks`](/packages/fuselage-hooks) | React hooks for Fuselage, Rocket.Chat's design system and UI toolkit | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage-hooks?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-hooks) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage-hooks?style=flat-square) | -| 📦 [`@rocket.chat/fuselage-polyfills`](/packages/fuselage-polyfills) | A bundle of useful poly/ponyfills used by fuselage | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage-polyfills?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-polyfills) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage-polyfills?style=flat-square) | -| 📦 [`@rocket.chat/fuselage-toastbar`](/packages/fuselage-toastbar) | Fuselage ToastBar component | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage-toastbar?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-toastbar) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage-toastbar?style=flat-square) | -| 📦 [`@rocket.chat/fuselage-tokens`](/packages/fuselage-tokens) | Design tokens for Fuselage, Rocket.Chat's design system | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage-tokens?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-tokens) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage-tokens?style=flat-square) | -| 📦 [`@rocket.chat/fuselage-ui-kit`](/packages/fuselage-ui-kit) | UiKit elements for Rocket.Chat Apps built under Fuselage design system | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage-ui-kit?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-ui-kit) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage-ui-kit?style=flat-square) | -| 📦 [`@rocket.chat/icons`](/packages/icons) | Rocket.Chat's Icons | [![npm](https://img.shields.io/npm/v/@rocket.chat/icons?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/icons) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/icons?style=flat-square) | -| 📦 [`@rocket.chat/layout`](/packages/layout) | Shared Application Layout Components | [![npm](https://img.shields.io/npm/v/@rocket.chat/layout?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/layout) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/layout?style=flat-square) | -| 📦 [`@rocket.chat/logo`](/packages/logo) | Rocket.Chat logo package | [![npm](https://img.shields.io/npm/v/@rocket.chat/logo?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/logo) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/logo?style=flat-square) | -| 📦 [`@rocket.chat/memo`](/packages/memo) | Memoization utilities | [![npm](https://img.shields.io/npm/v/@rocket.chat/memo?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/memo) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/memo?style=flat-square) | -| 📦 [`@rocket.chat/message-parser`](/packages/message-parser) | Rocket.Chat parser for messages | [![npm](https://img.shields.io/npm/v/@rocket.chat/message-parser?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/message-parser) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/message-parser?style=flat-square) | -| 📦 [`@rocket.chat/mp3-encoder`](/packages/mp3-encoder) | A LAME encoder to be used in web workers | [![npm](https://img.shields.io/npm/v/@rocket.chat/mp3-encoder?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/mp3-encoder) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/mp3-encoder?style=flat-square) | -| 📦 [`@rocket.chat/onboarding-ui`](/packages/onboarding-ui) | Set of components and functions for the onboarding experience on Rocket.Chat | [![npm](https://img.shields.io/npm/v/@rocket.chat/onboarding-ui?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/onboarding-ui) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/onboarding-ui?style=flat-square) | -| 📦 [`@rocket.chat/peggy-loader`](/packages/peggy-loader) | Peggy loader for webpack | [![npm](https://img.shields.io/npm/v/@rocket.chat/peggy-loader?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/peggy-loader) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/peggy-loader?style=flat-square) | -| 📦 [`@rocket.chat/prettier-config`](/packages/prettier-config) | Prettier configuration for Rocket.Chat repositories | [![npm](https://img.shields.io/npm/v/@rocket.chat/prettier-config?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/prettier-config) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/prettier-config?style=flat-square) | -| 📦 [`@rocket.chat/string-helpers`](/packages/string-helpers) | Helper functions for string manipulation | [![npm](https://img.shields.io/npm/v/@rocket.chat/string-helpers?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/string-helpers) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/string-helpers?style=flat-square) | -| 📦 [`@rocket.chat/styled`](/packages/styled) | A simple styled API for React components | [![npm](https://img.shields.io/npm/v/@rocket.chat/styled?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/styled) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/styled?style=flat-square) | -| 📦 [`@rocket.chat/stylis-logical-props-middleware`](/packages/stylis-logical-props-middleware) | Stylis middleware to handle CSS Logical Properties and their fallbacks | [![npm](https://img.shields.io/npm/v/@rocket.chat/stylis-logical-props-middleware?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/stylis-logical-props-middleware) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/stylis-logical-props-middleware?style=flat-square) | -| 📦 [`@rocket.chat/ui-kit`](/packages/ui-kit) | Interactive UI elements for Rocket.Chat Apps | [![npm](https://img.shields.io/npm/v/@rocket.chat/ui-kit?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/ui-kit) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/ui-kit?style=flat-square) | -| 📦 [`@rocket.chat/uikit-playground`](/packages/uikit-playground) | | [![npm](https://img.shields.io/npm/v/@rocket.chat/uikit-playground?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/uikit-playground) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/uikit-playground?style=flat-square) | +| Package | Description | Version | Dependencies | +| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| 📦 [`@rocket.chat/css-in-js`](/packages/css-in-js) | Toolset to transpile and use CSS on runtime | [![npm](https://img.shields.io/npm/v/@rocket.chat/css-in-js?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/css-in-js) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/css-in-js?style=flat-square) | +| 📦 [`@rocket.chat/css-supports`](/packages/css-supports) | Memoized and SSR-compatible facade of CSS.supports API | [![npm](https://img.shields.io/npm/v/@rocket.chat/css-supports?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/css-supports) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/css-supports?style=flat-square) | +| 📦 [`@rocket.chat/emitter`](/packages/emitter) | Event Emitter by Rocket.Chat | [![npm](https://img.shields.io/npm/v/@rocket.chat/emitter?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/emitter) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/emitter?style=flat-square) | +| 📦 [`@rocket.chat/eslint-config-alt`](/packages/eslint-config-alt) | ESLint configuration for Rocket.Chat repositories | [![npm](https://img.shields.io/npm/v/@rocket.chat/eslint-config-alt?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/eslint-config-alt) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/eslint-config-alt?style=flat-square) | +| 📦 [`@rocket.chat/fuselage`](/packages/fuselage) | Rocket.Chat's React Components Library | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage?style=flat-square) | +| 📦 [`@rocket.chat/fuselage-hooks`](/packages/fuselage-hooks) | React hooks for Fuselage, Rocket.Chat's design system and UI toolkit | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage-hooks?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-hooks) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage-hooks?style=flat-square) | +| 📦 [`@rocket.chat/fuselage-polyfills`](/packages/fuselage-polyfills) | A bundle of useful poly/ponyfills used by fuselage | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage-polyfills?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-polyfills) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage-polyfills?style=flat-square) | +| 📦 [`@rocket.chat/fuselage-toastbar`](/packages/fuselage-toastbar) | Fuselage ToastBar component | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage-toastbar?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-toastbar) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage-toastbar?style=flat-square) | +| 📦 [`@rocket.chat/fuselage-tokens`](/packages/fuselage-tokens) | Design tokens for Fuselage, Rocket.Chat's design system | [![npm](https://img.shields.io/npm/v/@rocket.chat/fuselage-tokens?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-tokens) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage-tokens?style=flat-square) | +| 📦 [`@rocket.chat/icons`](/packages/icons) | Rocket.Chat's Icons | [![npm](https://img.shields.io/npm/v/@rocket.chat/icons?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/icons) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/icons?style=flat-square) | +| 📦 [`@rocket.chat/layout`](/packages/layout) | Shared Application Layout Components | [![npm](https://img.shields.io/npm/v/@rocket.chat/layout?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/layout) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/layout?style=flat-square) | +| 📦 [`@rocket.chat/logo`](/packages/logo) | Rocket.Chat logo package | [![npm](https://img.shields.io/npm/v/@rocket.chat/logo?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/logo) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/logo?style=flat-square) | +| 📦 [`@rocket.chat/memo`](/packages/memo) | Memoization utilities | [![npm](https://img.shields.io/npm/v/@rocket.chat/memo?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/memo) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/memo?style=flat-square) | +| 📦 [`@rocket.chat/message-parser`](/packages/message-parser) | Rocket.Chat parser for messages | [![npm](https://img.shields.io/npm/v/@rocket.chat/message-parser?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/message-parser) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/message-parser?style=flat-square) | +| 📦 [`@rocket.chat/mp3-encoder`](/packages/mp3-encoder) | A LAME encoder to be used in web workers | [![npm](https://img.shields.io/npm/v/@rocket.chat/mp3-encoder?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/mp3-encoder) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/mp3-encoder?style=flat-square) | +| 📦 [`@rocket.chat/onboarding-ui`](/packages/onboarding-ui) | Set of components and functions for the onboarding experience on Rocket.Chat | [![npm](https://img.shields.io/npm/v/@rocket.chat/onboarding-ui?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/onboarding-ui) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/onboarding-ui?style=flat-square) | +| 📦 [`@rocket.chat/peggy-loader`](/packages/peggy-loader) | Peggy loader for webpack | [![npm](https://img.shields.io/npm/v/@rocket.chat/peggy-loader?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/peggy-loader) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/peggy-loader?style=flat-square) | +| 📦 [`@rocket.chat/prettier-config`](/packages/prettier-config) | Prettier configuration for Rocket.Chat repositories | [![npm](https://img.shields.io/npm/v/@rocket.chat/prettier-config?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/prettier-config) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/prettier-config?style=flat-square) | +| 📦 [`@rocket.chat/string-helpers`](/packages/string-helpers) | Helper functions for string manipulation | [![npm](https://img.shields.io/npm/v/@rocket.chat/string-helpers?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/string-helpers) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/string-helpers?style=flat-square) | +| 📦 [`@rocket.chat/styled`](/packages/styled) | A simple styled API for React components | [![npm](https://img.shields.io/npm/v/@rocket.chat/styled?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/styled) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/styled?style=flat-square) | +| 📦 [`@rocket.chat/stylis-logical-props-middleware`](/packages/stylis-logical-props-middleware) | Stylis middleware to handle CSS Logical Properties and their fallbacks | [![npm](https://img.shields.io/npm/v/@rocket.chat/stylis-logical-props-middleware?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/stylis-logical-props-middleware) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/stylis-logical-props-middleware?style=flat-square) | +| 📦 [`@rocket.chat/ui-kit`](/packages/ui-kit) | Interactive UI elements for Rocket.Chat Apps | [![npm](https://img.shields.io/npm/v/@rocket.chat/ui-kit?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/ui-kit) | ![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/ui-kit?style=flat-square) | diff --git a/_templates/create-package/library/files/package.json.t b/_templates/create-package/library/files/package.json.t index deeb0a02aa..5fc81bc330 100644 --- a/_templates/create-package/library/files/package.json.t +++ b/_templates/create-package/library/files/package.json.t @@ -31,8 +31,8 @@ to: packages/<%=package%>/package.json "scripts": { "clean": "rimraf dist", "build": "run .:build:esm && run .:build:cjs", - ".:build:esm": "tsc -p tsconfig-esm.json", - ".:build:cjs": "tsc -p tsconfig-cjs.json", + ".:build:esm": "tsc -p tsconfig.esm.json", + ".:build:cjs": "tsc -p tsconfig.cjs.json", "lint": "lint", "lint-and-fix": "lint-and-fix", "lint-staged": "lint-staged", @@ -50,7 +50,7 @@ to: packages/<%=package%>/package.json "prettier": "~2.5.1", "rimraf": "~3.0.2", "ts-jest": "~27.1.3", - "typedoc": "~0.22.11", + "typedoc": "~0.24.1", "typescript": "~4.3.5" }, "eslintConfig": { @@ -65,14 +65,6 @@ to: packages/<%=package%>/package.json "errorOnDeprecated": true, "testMatch": [ "/src/**/*.spec.[jt]s?(x)" - ], - "globals": { - "ts-jest": { - "tsconfig": { - "noUnusedLocals": false, - "noUnusedParameters": false - } - } - } + ] } } diff --git a/_templates/create-package/library/files/tsconfig-cjs.json.t b/_templates/create-package/library/files/tsconfig.cjs.json.t similarity index 77% rename from _templates/create-package/library/files/tsconfig-cjs.json.t rename to _templates/create-package/library/files/tsconfig.cjs.json.t index 2b0e5180bc..d5cd88b93b 100644 --- a/_templates/create-package/library/files/tsconfig-cjs.json.t +++ b/_templates/create-package/library/files/tsconfig.cjs.json.t @@ -1,5 +1,5 @@ --- -to: packages/<%=package%>/tsconfig-cjs.json +to: packages/<%=package%>/tsconfig.cjs.json --- { "extends": "./tsconfig.json", diff --git a/_templates/create-package/library/files/tsconfig-esm.json.t b/_templates/create-package/library/files/tsconfig.esm.json.t similarity index 62% rename from _templates/create-package/library/files/tsconfig-esm.json.t rename to _templates/create-package/library/files/tsconfig.esm.json.t index 51c6192fd7..3140020b71 100644 --- a/_templates/create-package/library/files/tsconfig-esm.json.t +++ b/_templates/create-package/library/files/tsconfig.esm.json.t @@ -1,5 +1,5 @@ --- -to: packages/<%=package%>/tsconfig-esm.json +to: packages/<%=package%>/tsconfig.esm.json --- { "extends": "./tsconfig.json", diff --git a/_templates/create-package/library/files/tsconfig.json.t b/_templates/create-package/library/files/tsconfig.json.t index 3c83872fe8..aad3770f2e 100644 --- a/_templates/create-package/library/files/tsconfig.json.t +++ b/_templates/create-package/library/files/tsconfig.json.t @@ -2,17 +2,12 @@ to: packages/<%=package%>/tsconfig.json --- { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "es5", "module": "ESNext", - "declaration": true, - "declarationMap": true, - "sourceMap": true, "outDir": "./dist/esm", - "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "resolveJsonModule": true }, diff --git a/package.json b/package.json index 4c639eb55b..ee72993a0e 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,10 @@ "husky": "~7.0.4", "hygen": "~6.1.5", "lerna": "~4.0.0", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "turbo": "~1.1.10", "update-readme": "workspace:~", - "webpack": "~5.76.0" + "webpack": "~5.78.0" }, "scripts": { "postinstall": "husky install", @@ -33,11 +33,14 @@ "release-next": "yarn workspaces foreach --no-private -v npm publish --tag next --tolerate-republish" }, "devEngines": { - "node": "~14.17.6" + "node": "~14.21.3" }, "volta": { - "node": "14.21.2", + "node": "14.21.3", "yarn": "1.22.19" }, - "packageManager": "yarn@3.5.0" + "packageManager": "yarn@3.5.0", + "resolutions": { + "@storybook/react-docgen-typescript-plugin@1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0": "patch:@storybook/react-docgen-typescript-plugin@npm%3A1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0#./.yarn/patches/@storybook-react-docgen-typescript-plugin-npm-1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0-b31cc57c40.patch" + } } diff --git a/packages/css-in-js/package.json b/packages/css-in-js/package.json index b490d79886..4eb50013db 100644 --- a/packages/css-in-js/package.json +++ b/packages/css-in-js/package.json @@ -48,19 +48,19 @@ "@rollup/plugin-json": "~4.1.0", "@rollup/plugin-node-resolve": "~13.1.3", "@rollup/plugin-typescript": "~8.3.4", - "@types/jest": "~27.4.1", + "@types/jest": "~29.5.0", "@types/stylis": "^4.0.2", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", - "prettier": "~2.7.1", + "lint-staged": "~13.2.1", + "prettier": "~2.8.7", "rollup": "~2.67.3", "rollup-plugin-terser": "~7.0.2", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "dependencies": { "@emotion/hash": "^0.9.0", diff --git a/packages/css-in-js/rollup.config.js b/packages/css-in-js/rollup.config.js index e747754a78..f3d8b8b59b 100644 --- a/packages/css-in-js/rollup.config.js +++ b/packages/css-in-js/rollup.config.js @@ -28,7 +28,9 @@ const plugins = [ json(), nodeResolve(), commonjs(), - typescript(), + typescript({ + tsconfig: './tsconfig.build.json', + }), ]; export default [ diff --git a/packages/css-in-js/src/sheet.ts b/packages/css-in-js/src/sheet.ts index 304a656559..e535138380 100644 --- a/packages/css-in-js/src/sheet.ts +++ b/packages/css-in-js/src/sheet.ts @@ -74,7 +74,7 @@ const attachRulesIntoStyleSheet: RuleAttacher = (rules) => { }; const wrapReferenceCounting = (attacher: RuleAttacher): RuleAttacher => { - const refs = {}; + const refs: Record = {}; const queueMicrotask = (fn: () => void): void => { if ( diff --git a/packages/css-in-js/tsconfig.build.json b/packages/css-in-js/tsconfig.build.json new file mode 100644 index 0000000000..5417c7dc09 --- /dev/null +++ b/packages/css-in-js/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["src/**/*.spec.ts"] +} diff --git a/packages/css-in-js/tsconfig.json b/packages/css-in-js/tsconfig.json index 7f9a1bd7c9..b4cc396e40 100644 --- a/packages/css-in-js/tsconfig.json +++ b/packages/css-in-js/tsconfig.json @@ -1,28 +1,15 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "./src", "module": "ESNext", - "target": "es5", "lib": ["dom", "es6"], - "sourceMap": true, - "allowJs": false, - "jsx": "react", - "declaration": true, "declarationDir": "./dist", "outDir": "./dist", "moduleResolution": "node", - "forceConsistentCasingInFileNames": true, "noEmit": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noImplicitAny": true, - "strictNullChecks": true, - "suppressImplicitAnyIndexErrors": true, - "noUnusedLocals": true, - "noUnusedParameters": true, "esModuleInterop": true, - "skipLibCheck": true, - "importsNotUsedAsValues": "error" + "skipLibCheck": true }, "include": ["src"], "exclude": ["dist", "node_modules", "src/*.spec.ts"] diff --git a/packages/css-supports/package.json b/packages/css-supports/package.json index bd03893cdd..45ccba6bf5 100644 --- a/packages/css-supports/package.json +++ b/packages/css-supports/package.json @@ -27,7 +27,7 @@ ], "scripts": { "clean": "rimraf dist", - "build": "tsc -p tsconfig-esm.json && tsc -p tsconfig-cjs.json", + "build": "tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json", "lint": "lint", "lint-and-fix": "lint-and-fix", "lint-staged": "lint-staged", @@ -38,13 +38,13 @@ "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", "bump": "workspace:~", - "eslint": "~8.26.0", + "eslint": "~8.38.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", - "prettier": "~2.7.1", + "lint-staged": "~13.2.1", + "prettier": "~2.8.7", "rimraf": "~3.0.2", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "dependencies": { "@rocket.chat/memo": "workspace:~" diff --git a/packages/css-supports/tsconfig-cjs.json b/packages/css-supports/tsconfig.cjs.json similarity index 100% rename from packages/css-supports/tsconfig-cjs.json rename to packages/css-supports/tsconfig.cjs.json diff --git a/packages/css-supports/tsconfig-esm.json b/packages/css-supports/tsconfig.esm.json similarity index 100% rename from packages/css-supports/tsconfig-esm.json rename to packages/css-supports/tsconfig.esm.json diff --git a/packages/css-supports/tsconfig.json b/packages/css-supports/tsconfig.json index add42563e2..23682df3d9 100644 --- a/packages/css-supports/tsconfig.json +++ b/packages/css-supports/tsconfig.json @@ -1,19 +1,13 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "./src", - "target": "es5", "module": "ESNext", - "declaration": true, - "declarationMap": true, - "sourceMap": true, "outDir": "./dist/esm", - "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, "moduleResolution": "node", - "resolveJsonModule": true, - "importsNotUsedAsValues": "error" + "resolveJsonModule": true }, "include": ["src/**/*"], "typedocOptions": { diff --git a/packages/emitter/jest.config.js b/packages/emitter/jest.config.js index ad0af26742..639dd2b92d 100644 --- a/packages/emitter/jest.config.js +++ b/packages/emitter/jest.config.js @@ -2,12 +2,4 @@ module.exports = { preset: 'ts-jest', errorOnDeprecated: true, testMatch: ['**/src/**/*.spec.[jt]s?(x)'], - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - }, - }, - }, }; diff --git a/packages/emitter/package.json b/packages/emitter/package.json index 4b5786d3fc..3df42e2129 100644 --- a/packages/emitter/package.json +++ b/packages/emitter/package.json @@ -48,19 +48,18 @@ "@rollup/plugin-json": "~4.1.0", "@rollup/plugin-node-resolve": "~13.1.3", "@rollup/plugin-typescript": "~8.3.4", - "@types/jest": "~27.4.1", + "@types/jest": "~29.5.0", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "rollup": "~2.67.3", "rollup-plugin-terser": "~7.0.2", - "ts-jest": "~27.1.5", - "tslib": "^2.3.1", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" } } diff --git a/packages/emitter/rollup.config.js b/packages/emitter/rollup.config.js index 42f07bdd0f..400f7f3cbf 100644 --- a/packages/emitter/rollup.config.js +++ b/packages/emitter/rollup.config.js @@ -47,6 +47,8 @@ export default { json(), nodeResolve(), commonjs(), - typescript(), + typescript({ + tsconfig: './tsconfig.build.json', + }), ], }; diff --git a/packages/emitter/src/emitter.spec.ts b/packages/emitter/src/emitter.spec.ts index abc5a6ecf7..a34160b5a5 100644 --- a/packages/emitter/src/emitter.spec.ts +++ b/packages/emitter/src/emitter.spec.ts @@ -5,87 +5,98 @@ const times = (n: number, fn: () => void): number => { return n; }; -describe('Emitter function', () => { - let handler: () => void; - let emitter: Emitter; +let handler: () => void; +let emitter: Emitter; - beforeEach(() => { - handler = jest.fn(); - emitter = new Emitter(); +beforeEach(() => { + handler = jest.fn(); + emitter = new Emitter(); +}); + +describe('`on` method', () => { + it('should call `test` handler 5 times - with only one listener', () => { + emitter.on('test', handler); + times(5, () => emitter.emit('test')); + expect(handler).toHaveBeenCalledTimes(5); + }); + + it('should call `test2` handler 0 times - with multiple listeners', () => { + emitter.on('test', () => null); + emitter.on('test2', handler); + times(5, () => emitter.emit('test')); + expect(handler).toHaveBeenCalledTimes(0); + }); +}); + +describe('`once` method', () => { + it('should call `test` handler only once', () => { + emitter.once('test', handler); + times(5, () => emitter.emit('test')); + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('should call `test` handler only once - with multiple events and same handler', () => { + emitter.once('test', handler); + emitter.once('test2', handler); + times(5, () => emitter.emit('test')); + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('should call `test` handler 5 times afert `once + remove + on` same event', () => { + emitter.once('test', handler); + emitter.off('test', handler); + emitter.on('test', handler); + times(5, () => emitter.emit('test')); + expect(handler).toHaveBeenCalledTimes(5); }); - describe('`on` method', () => { - it('It should call `test` handler 5 times - with only one listener', () => { - emitter.on('test', handler); - times(5, () => emitter.emit('test')); - expect(handler).toHaveBeenCalledTimes(5); - }); - - it('It should call `test2` handler 0 times - with multiple listeners', () => { - emitter.on('test', () => null); - emitter.on('test2', handler); - times(5, () => emitter.emit('test')); - expect(handler).toHaveBeenCalledTimes(0); - }); + it('should call `test` handler once after add multiple `once` and remove n-1', () => { + emitter.once('test', handler); + emitter.once('test', handler)(); + emitter.once('test', handler)(); + emitter.once('test', handler)(); + emitter.once('test', handler)(); + emitter.once('test', handler)(); + times(5, () => emitter.emit('test')); + expect(handler).toHaveBeenCalledTimes(1); }); - describe('`once` method', () => { - it('It should call `test` handler only once', () => { - emitter.once('test', handler); - times(5, () => emitter.emit('test')); - expect(handler).toHaveBeenCalledTimes(1); - }); - - it('It should call `test` handler only once - with multiple events and same handler', () => { - emitter.once('test', handler); - emitter.once('test2', handler); - times(5, () => emitter.emit('test')); - expect(handler).toHaveBeenCalledTimes(1); - }); - - it('It should call `test` handler 5 times afert `once + remove + on` same event', () => { - emitter.once('test', handler); - emitter.off('test', handler); - emitter.on('test', handler); - times(5, () => emitter.emit('test')); - expect(handler).toHaveBeenCalledTimes(5); - }); - - it('It should call `test` handler once after add multiple `once` and remove n-1', () => { - emitter.once('test', handler); - emitter.once('test', handler)(); - emitter.once('test', handler)(); - emitter.once('test', handler)(); - emitter.once('test', handler)(); - emitter.once('test', handler)(); - times(5, () => emitter.emit('test')); - expect(handler).toHaveBeenCalledTimes(1); - }); - - it('It should call `test` handler once after add same handler with different `once` events and remove n-1', () => { - emitter.once('test', handler); - emitter.once('test2', handler)(); - times(5, () => emitter.emit('test')); - expect(handler).toHaveBeenCalledTimes(1); - }); + it('should call `test` handler once after add same handler with different `once` events and remove n-1', () => { + emitter.once('test', handler); + emitter.once('test2', handler)(); + times(5, () => emitter.emit('test')); + expect(handler).toHaveBeenCalledTimes(1); }); +}); + +describe('`off` method', () => { + it('should have no `test` handler after removal', () => { + emitter.on('test', handler); + emitter.off('test', handler); + expect(emitter.has('test')).toBe(false); + }); + + it('should have no `test` handler after use stop callback', () => { + emitter.on('test', handler)(); + expect(emitter.has('test')).toBe(false); + }); + + it('should have no `test` handler after emit once', () => { + emitter.once('test', handler); + emitter.emit('test'); + expect(emitter.has('test')).toBe(false); + }); + + it('should remove only the specified handler', () => { + const handler = jest.fn(); + const unusedHandler = jest.fn(); + + emitter.on('test', handler); + emitter.off('test', unusedHandler); + + emitter.emit('test'); - describe('`off` method', () => { - it('It should have no `test` handler after removal', () => { - emitter.on('test', handler); - emitter.off('test', handler); - expect(emitter.has('test')).toBe(false); - }); - - it('It should have no `test` handler after use stop callback', () => { - emitter.on('test', handler)(); - expect(emitter.has('test')).toBe(false); - }); - - it('It should have no `test` handler after emit once', () => { - emitter.once('test', handler); - emitter.emit('test'); - expect(emitter.has('test')).toBe(false); - }); + expect(handler).toHaveBeenCalledTimes(1); + expect(unusedHandler).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/emitter/src/index.ts b/packages/emitter/src/index.ts index 5ddb41c348..618db92d7d 100644 --- a/packages/emitter/src/index.ts +++ b/packages/emitter/src/index.ts @@ -117,11 +117,10 @@ export class Emitter */ on< T extends AnyEventOf, - EventType extends AnyEventTypeOf = EventTypeOf - >( - type: EventType, - handler: EventHandlerOf - ): OffCallbackHandler { + TType extends AnyEventTypeOf = EventTypeOf + >(type: TType, handler: EventHandlerOf): OffCallbackHandler; + + on(type: keyof EventMap, handler: (...args: any[]) => void) { const handlers = this[evts].get(type) ?? []; handlers.push(handler); this[evts].set(type, handlers); @@ -139,7 +138,9 @@ export class Emitter >( type: EventType, handler: EventHandlerOf - ): OffCallbackHandler { + ): OffCallbackHandler; + + once(type: keyof EventMap, handler: (...args: any[]) => void) { const counter = this[once].get(handler) || 0; this[once].set(handler, counter + 1); return this.on(type, handler); @@ -151,27 +152,29 @@ export class Emitter off< T extends AnyEventOf, EventType extends AnyEventTypeOf = EventTypeOf - >(type: EventType, handler: EventHandlerOf): void { + >(type: EventType, handler: EventHandlerOf): void; + + off(type: keyof EventMap, handler: (...args: any[]) => void) { const handlers = this[evts].get(type); if (!handlers) { return; } - const counter = this[once].get(handler); + const counter = this[once].get(handler) ?? 0; if (counter > 1) { this[once].set(handler, counter - 1); } else { this[once].delete(handler); } - if (handlers.length === 1) { - this[evts].delete(type); - return; - } handlers.splice( handlers.findIndex((callback) => callback === handler) >>> 0, 1 ); + + if (handlers.length === 0) { + this[evts].delete(type); + } } /** @@ -186,15 +189,15 @@ export class Emitter ...[event]: EventOf extends void ? [undefined?] : [EventOf] - ): void { - [...(this[evts].get(type) ?? [])].forEach( - (handler: EventHandlerOf) => { - handler(event); - - if (this[once].get(handler)) { - this.off(type, handler); - } + ): void; + + emit(type: keyof EventMap, ...[event]: any[]) { + [...(this[evts].get(type) ?? [])].forEach((handler) => { + handler(event); + + if (this[once].get(handler)) { + this.off(type, handler); } - ); + }); } } diff --git a/packages/emitter/tsconfig.build.json b/packages/emitter/tsconfig.build.json new file mode 100644 index 0000000000..5417c7dc09 --- /dev/null +++ b/packages/emitter/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["src/**/*.spec.ts"] +} diff --git a/packages/emitter/tsconfig.json b/packages/emitter/tsconfig.json index 25e0b577e7..4c899c6467 100644 --- a/packages/emitter/tsconfig.json +++ b/packages/emitter/tsconfig.json @@ -1,30 +1,17 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "./src", "module": "ESNext", "target": "ES5", "lib": ["dom", "ES2015"], - "sourceMap": true, - "allowJs": false, - "jsx": "react", - "declaration": true, "declarationDir": "./dist", "outDir": "./dist", "moduleResolution": "node", - "forceConsistentCasingInFileNames": true, - // "noEmit": true, - // "noImplicitReturns": true, - // "noImplicitThis": true, - // "noImplicitAny": true, - // "strictNullChecks": true, - "suppressImplicitAnyIndexErrors": true, - "noUnusedLocals": true, - "noUnusedParameters": true, "esModuleInterop": true, "resolveJsonModule": true, - "skipLibCheck": true, - "importsNotUsedAsValues": "error" + "skipLibCheck": true }, "include": ["src"], - "exclude": ["dist", "node_modules", "src/*.spec.ts"] + "exclude": ["dist", "node_modules"] } diff --git a/packages/eslint-config-alt/.eslintrc.js b/packages/eslint-config-alt/.eslintrc.js index 74a90692eb..6074be0213 100644 --- a/packages/eslint-config-alt/.eslintrc.js +++ b/packages/eslint-config-alt/.eslintrc.js @@ -1,3 +1,4 @@ +/** @type {import('eslint').Linter.Config} */ module.exports = { extends: './minimal', }; diff --git a/packages/eslint-config-alt/minimal/index.js b/packages/eslint-config-alt/minimal/index.js index b94c1c5395..e67bca66d3 100644 --- a/packages/eslint-config-alt/minimal/index.js +++ b/packages/eslint-config-alt/minimal/index.js @@ -1,3 +1,4 @@ +/** @type {import('eslint').Linter.Config} */ module.exports = { extends: ['../original', 'prettier'], plugins: ['prettier'], diff --git a/packages/eslint-config-alt/original/index.js b/packages/eslint-config-alt/original/index.js index 49c8f49802..2f7e6b4e99 100644 --- a/packages/eslint-config-alt/original/index.js +++ b/packages/eslint-config-alt/original/index.js @@ -1,3 +1,4 @@ +/** @type {import('eslint').Linter.Config} */ module.exports = { extends: [ './rules/best-practices', diff --git a/packages/eslint-config-alt/package.json b/packages/eslint-config-alt/package.json index 8056e99906..7a96e7f5dc 100644 --- a/packages/eslint-config-alt/package.json +++ b/packages/eslint-config-alt/package.json @@ -34,25 +34,25 @@ }, "peerDependencies": { "@babel/eslint-parser": "^7.13.14", - "eslint": "^7.29.0", + "eslint": "~8.38.0", "prettier": "~2.7.1" }, "devDependencies": { - "@babel/eslint-parser": "~7.19.1", + "@babel/eslint-parser": "~7.21.3", "bump": "workspace:~", - "eslint": "~8.26.0", + "eslint": "~8.38.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", - "prettier": "~2.7.1" + "lint-staged": "~13.2.1", + "prettier": "~2.8.7" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "~5.11.0", - "@typescript-eslint/parser": "~5.11.0", - "eslint-config-prettier": "~8.5.0", - "eslint-import-resolver-typescript": "~3.5.3", + "@typescript-eslint/eslint-plugin": "~5.58.0", + "@typescript-eslint/parser": "~5.58.0", + "eslint-config-prettier": "~8.8.0", + "eslint-import-resolver-typescript": "~3.5.5", "eslint-plugin-import": "~2.26.0", "eslint-plugin-prettier": "~4.2.1", - "eslint-plugin-react": "~7.31.11", + "eslint-plugin-react": "~7.32.2", "eslint-plugin-react-hooks": "~4.6.0" } } diff --git a/packages/eslint-config-alt/react/index.js b/packages/eslint-config-alt/react/index.js index 410802b1b5..bb4d4c29b6 100644 --- a/packages/eslint-config-alt/react/index.js +++ b/packages/eslint-config-alt/react/index.js @@ -1,3 +1,4 @@ +/** @type {import('eslint').Linter.Config} */ module.exports = { extends: '../minimal', plugins: ['react', 'react-hooks'], diff --git a/packages/eslint-config-alt/typescript/index.js b/packages/eslint-config-alt/typescript/index.js index bd9409cb8c..7ace5a1338 100644 --- a/packages/eslint-config-alt/typescript/index.js +++ b/packages/eslint-config-alt/typescript/index.js @@ -1,3 +1,4 @@ +/** @type {import('eslint').Linter.Config} */ module.exports = { extends: [ 'plugin:@typescript-eslint/recommended', @@ -59,4 +60,13 @@ module.exports = { typescript: {}, }, }, + overrides: [ + { + files: ['*.+(ts|tsx|cts|ctsx|mts|mtsx)'], + rules: { + '@typescript-eslint/no-dupe-class-members': 'error', + 'no-dupe-class-members': 'off', + }, + }, + ], }; diff --git a/packages/fuselage-hooks/jest.config.js b/packages/fuselage-hooks/jest.config.js index 4c600f5329..385588fce8 100644 --- a/packages/fuselage-hooks/jest.config.js +++ b/packages/fuselage-hooks/jest.config.js @@ -3,13 +3,5 @@ module.exports = { errorOnDeprecated: true, testMatch: ['/src/**/*.spec.{ts,tsx}'], testEnvironment: 'jsdom', - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - }, - }, - }, setupFilesAfterEnv: ['testing-utils/setup/noErrorsLogged'], }; diff --git a/packages/fuselage-hooks/package.json b/packages/fuselage-hooks/package.json index 58fdd48dc0..e1e1bcd3d7 100644 --- a/packages/fuselage-hooks/package.json +++ b/packages/fuselage-hooks/package.json @@ -34,7 +34,9 @@ "access": "public" }, "scripts": { - "build": "rollup -c", + "build": "run-s .:build:clean .:build:rollup", + ".:build:clean": "rimraf dist", + ".:build:rollup": "rollup -c", "lint": "lint", "lint-and-fix": "lint-and-fix", "lint-staged": "lint-staged", @@ -49,27 +51,28 @@ "@rollup/plugin-json": "~4.1.0", "@rollup/plugin-node-resolve": "~13.1.3", "@rollup/plugin-typescript": "~8.3.4", - "@testing-library/react-hooks": "~7.0.2", - "@testing-library/user-event": "^13.5.0", - "@types/jest": "~27.4.1", - "@types/react": "~17.0.53", - "@types/react-dom": "^17.0.18", + "@testing-library/react-hooks": "~8.0.1", + "@testing-library/user-event": "~14.4.3", + "@types/jest": "~29.5.0", + "@types/react": "~17.0.57", + "@types/react-dom": "^17.0.19", "@types/resize-observer-browser": "~0.1.7", "@types/use-sync-external-store": "~0.0.3", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "react": "^17.0.2", + "rimraf": "~5.0.0", "rollup": "~2.67.3", "rollup-plugin-terser": "~7.0.2", "testing-utils": "workspace:~", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "peerDependencies": { "@rocket.chat/fuselage-tokens": "*", diff --git a/packages/fuselage-hooks/rollup.config.js b/packages/fuselage-hooks/rollup.config.js index 06c9d65b93..5be78c35b3 100644 --- a/packages/fuselage-hooks/rollup.config.js +++ b/packages/fuselage-hooks/rollup.config.js @@ -47,6 +47,8 @@ export default { json(), nodeResolve(), commonjs(), - typescript(), + typescript({ + tsconfig: 'tsconfig.build.json', + }), ], }; diff --git a/packages/fuselage-hooks/src/useAutoFocus.ts b/packages/fuselage-hooks/src/useAutoFocus.ts index c863b442cb..e9f86b977c 100644 --- a/packages/fuselage-hooks/src/useAutoFocus.ts +++ b/packages/fuselage-hooks/src/useAutoFocus.ts @@ -16,7 +16,7 @@ export const useAutoFocus = < isFocused = true, options?: FocusOptions ): Ref => { - const elementRef = useRef(); + const elementRef = useRef(null); const { preventScroll } = options || {}; diff --git a/packages/fuselage-hooks/src/useBorderBoxSize.server.spec.ts b/packages/fuselage-hooks/src/useBorderBoxSize.server.spec.ts index 3075047aa5..856addc389 100644 --- a/packages/fuselage-hooks/src/useBorderBoxSize.server.spec.ts +++ b/packages/fuselage-hooks/src/useBorderBoxSize.server.spec.ts @@ -8,7 +8,7 @@ import { useRef } from 'react'; import { useBorderBoxSize } from './useBorderBoxSize'; it('immediately returns zero size', () => { - const { result } = renderHook(() => useBorderBoxSize(useRef())); + const { result } = renderHook(() => useBorderBoxSize(useRef(null))); expect(result.current.inlineSize).toStrictEqual(0); expect(result.current.blockSize).toStrictEqual(0); diff --git a/packages/fuselage-hooks/src/useBorderBoxSize.spec.ts b/packages/fuselage-hooks/src/useBorderBoxSize.spec.ts index eefc38b47e..d335cbadde 100644 --- a/packages/fuselage-hooks/src/useBorderBoxSize.spec.ts +++ b/packages/fuselage-hooks/src/useBorderBoxSize.spec.ts @@ -31,14 +31,14 @@ const wrapRef = (ref: RefObject) => { }; it('immediately returns size', async () => { - const { result } = renderHook(() => useBorderBoxSize(wrapRef(useRef()))); + const { result } = renderHook(() => useBorderBoxSize(wrapRef(useRef(null)))); expect(result.current.inlineSize).toStrictEqual(50); expect(result.current.blockSize).toStrictEqual(40); }); it('gets the observed element size after resize', async () => { - const { result } = renderHook(() => useBorderBoxSize(wrapRef(useRef()))); + const { result } = renderHook(() => useBorderBoxSize(wrapRef(useRef(null)))); // triggers MutationObserver await act(async () => { @@ -74,7 +74,7 @@ it('debounces the observed element size', async () => { const delay = 2 * halfDelay; const { result } = renderHook(() => - useBorderBoxSize(wrapRef(useRef()), { debounceDelay: delay }) + useBorderBoxSize(wrapRef(useRef(null)), { debounceDelay: delay }) ); // triggers MutationObserver diff --git a/packages/fuselage-hooks/src/useBorderBoxSize.ts b/packages/fuselage-hooks/src/useBorderBoxSize.ts index 1e929647ed..01bda6d36a 100644 --- a/packages/fuselage-hooks/src/useBorderBoxSize.ts +++ b/packages/fuselage-hooks/src/useBorderBoxSize.ts @@ -7,7 +7,9 @@ import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; export const useBorderBoxSize = ( ref: RefObject, - options: { + { + debounceDelay = 0, + }: { debounceDelay?: number; } = {} ): Readonly<{ @@ -19,10 +21,7 @@ export const useBorderBoxSize = ( blockSize: ref.current?.offsetHeight ?? 0, })); - const setSizeWithDebounce = useDebouncedCallback( - setSize, - options.debounceDelay - ); + const setSizeWithDebounce = useDebouncedCallback(setSize, debounceDelay); useIsomorphicLayoutEffect(() => { const element = ref.current; diff --git a/packages/fuselage-hooks/src/useBreakpoints.spec.ts b/packages/fuselage-hooks/src/useBreakpoints.spec.ts index 404711769e..0880c3dacf 100644 --- a/packages/fuselage-hooks/src/useBreakpoints.spec.ts +++ b/packages/fuselage-hooks/src/useBreakpoints.spec.ts @@ -17,7 +17,9 @@ it('returns matching breakpoint names', async () => { const finalBreakpoints = breakpoints.slice(0, -2); setViewport({ - width: initialBreakpoints[initialBreakpoints.length - 1].minViewportWidth, + width: + initialBreakpoints[initialBreakpoints.length - 1].minViewportWidth ?? + undefined, }); const { result } = renderHook(() => useBreakpoints()); @@ -28,7 +30,9 @@ it('returns matching breakpoint names', async () => { await act(async () => { setViewport({ - width: finalBreakpoints[finalBreakpoints.length - 1].minViewportWidth, + width: + finalBreakpoints[finalBreakpoints.length - 1].minViewportWidth ?? + undefined, }); }); diff --git a/packages/fuselage-hooks/src/useBreakpoints.ts b/packages/fuselage-hooks/src/useBreakpoints.ts index 373454343f..02d75f384e 100644 --- a/packages/fuselage-hooks/src/useBreakpoints.ts +++ b/packages/fuselage-hooks/src/useBreakpoints.ts @@ -3,18 +3,29 @@ import { useMemo } from 'react'; import { useMediaQueries } from './useMediaQueries'; -const mediaQueries = breakpointsDefinitions - .slice(1) - .map((breakpoint) => `(min-width: ${breakpoint.minViewportWidth}px)`); - /** * Hook to catch which responsive design' breakpoints are active. * * @returns an array of the active breakpoint names * @public */ -export const useBreakpoints = (): string[] => { - const matches = useMediaQueries(...mediaQueries); +export const useBreakpoints = (unit: 'px' | 'em' = 'em'): string[] => { + const matches = useMediaQueries( + ...useMemo( + () => + breakpointsDefinitions + .slice(1) + .map( + (breakpoint) => + `(min-width: ${ + unit === 'px' + ? `${breakpoint.minViewportWidth}px` + : `${breakpoint.minViewportWidth! / 16}em` + })` + ), + [unit] + ) + ); return useMemo( () => diff --git a/packages/fuselage-hooks/src/useClipboard.spec.ts b/packages/fuselage-hooks/src/useClipboard.spec.ts index 2da5ec7889..b6d1cefc5f 100644 --- a/packages/fuselage-hooks/src/useClipboard.spec.ts +++ b/packages/fuselage-hooks/src/useClipboard.spec.ts @@ -3,7 +3,7 @@ import { withClipboardMock } from 'testing-utils/mocks/withClipboardMock'; import { useClipboard } from './useClipboard'; -let container: Element; +let container: Element | undefined; beforeAll(() => { jest.useFakeTimers(); @@ -15,8 +15,8 @@ beforeEach(() => { }); afterEach(() => { - container.remove(); - container = null; + container?.remove(); + container = undefined; }); const withWriteText = withClipboardMock(); diff --git a/packages/fuselage-hooks/src/useClipboard.ts b/packages/fuselage-hooks/src/useClipboard.ts index f224fbe3eb..9e6eb3b610 100644 --- a/packages/fuselage-hooks/src/useClipboard.ts +++ b/packages/fuselage-hooks/src/useClipboard.ts @@ -5,7 +5,7 @@ import { useMutableCallback } from './useMutableCallback'; type UseClipboardParams = { clearTime?: number; onCopySuccess?: (e?: Event) => void; - onCopyError?: (e?: Event) => void; + onCopyError?: (e?: Error) => void; }; export type UseClipboardReturn = { @@ -36,7 +36,12 @@ export const useClipboard = ( onCopySuccess(e); setHasCopied(true); } catch (e) { - onCopyError(e); + if (e instanceof Error) { + onCopyError(e); + return; + } + + throw e; } }); diff --git a/packages/fuselage-hooks/src/useContentBoxSize.server.spec.ts b/packages/fuselage-hooks/src/useContentBoxSize.server.spec.ts index 7e6d5cdb4d..ac054a22db 100644 --- a/packages/fuselage-hooks/src/useContentBoxSize.server.spec.ts +++ b/packages/fuselage-hooks/src/useContentBoxSize.server.spec.ts @@ -8,7 +8,7 @@ import { useRef } from 'react'; import { useContentBoxSize } from './useContentBoxSize'; it('immediately returns zero size', () => { - const { result } = renderHook(() => useContentBoxSize(useRef())); + const { result } = renderHook(() => useContentBoxSize(useRef(null))); expect(result.current.inlineSize).toStrictEqual(0); expect(result.current.blockSize).toStrictEqual(0); diff --git a/packages/fuselage-hooks/src/useContentBoxSize.spec.ts b/packages/fuselage-hooks/src/useContentBoxSize.spec.ts index 3f179c5eb2..599bb66287 100644 --- a/packages/fuselage-hooks/src/useContentBoxSize.spec.ts +++ b/packages/fuselage-hooks/src/useContentBoxSize.spec.ts @@ -31,14 +31,14 @@ const wrapRef = (ref: RefObject) => { }; it('immediately returns size', async () => { - const { result } = renderHook(() => useContentBoxSize(wrapRef(useRef()))); + const { result } = renderHook(() => useContentBoxSize(wrapRef(useRef(null)))); expect(result.current.inlineSize).toStrictEqual(40); expect(result.current.blockSize).toStrictEqual(30); }); it('gets the observed element size after resize', async () => { - const { result } = renderHook(() => useContentBoxSize(wrapRef(useRef()))); + const { result } = renderHook(() => useContentBoxSize(wrapRef(useRef(null)))); // triggers MutationObserver await act(async () => { @@ -74,7 +74,7 @@ it('debounces the observed element size', async () => { const delay = 2 * halfDelay; const { result } = renderHook(() => - useContentBoxSize(wrapRef(useRef()), { debounceDelay: delay }) + useContentBoxSize(wrapRef(useRef(null)), { debounceDelay: delay }) ); // triggers MutationObserver diff --git a/packages/fuselage-hooks/src/useContentBoxSize.ts b/packages/fuselage-hooks/src/useContentBoxSize.ts index b128703ada..b16f63601d 100644 --- a/packages/fuselage-hooks/src/useContentBoxSize.ts +++ b/packages/fuselage-hooks/src/useContentBoxSize.ts @@ -7,7 +7,9 @@ import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; export const useContentBoxSize = ( ref: RefObject, - options: { + { + debounceDelay = 0, + }: { debounceDelay?: number; } = {} ): Readonly<{ @@ -19,10 +21,7 @@ export const useContentBoxSize = ( blockSize: ref.current?.clientHeight ?? 0, }); - const setSizeWithDebounce = useDebouncedCallback( - setSize, - options.debounceDelay - ); + const setSizeWithDebounce = useDebouncedCallback(setSize, debounceDelay); useIsomorphicLayoutEffect(() => { const element = ref.current; diff --git a/packages/fuselage-hooks/src/useDebouncedCallback.ts b/packages/fuselage-hooks/src/useDebouncedCallback.ts index c95ae42eb9..7313e0c5cb 100644 --- a/packages/fuselage-hooks/src/useDebouncedCallback.ts +++ b/packages/fuselage-hooks/src/useDebouncedCallback.ts @@ -19,7 +19,7 @@ export const useDebouncedCallback =

( cancel: () => void; } => { // eslint-disable-next-line react-hooks/exhaustive-deps - const effectiveCallback = useCallback(callback, deps); + const effectiveCallback = useMemo(() => callback, deps); const timerCallbackRef = useRef<() => void>(); const timerRef = useRef>(); @@ -37,7 +37,7 @@ export const useDebouncedCallback =

( const flush = useCallback(() => { clearTimeout(timerRef.current); - timerCallbackRef.current(); + timerCallbackRef.current?.(); }, []); const cancel = useCallback(() => { diff --git a/packages/fuselage-hooks/src/useDebouncedReducer.ts b/packages/fuselage-hooks/src/useDebouncedReducer.ts index d8dc0e06b7..45095cf2c5 100644 --- a/packages/fuselage-hooks/src/useDebouncedReducer.ts +++ b/packages/fuselage-hooks/src/useDebouncedReducer.ts @@ -10,6 +10,17 @@ import { useReducer } from 'react'; import { useDebouncedUpdates } from './useDebouncedUpdates'; +/** + * Hook to create a reduced state with a debounced `dispatch()` function. + * + * @param reducer - the reducer function + * @param initialArg - the initial state value or the argument passed to the + * initial state generator function + * @param init - the initial state generator function + * @param delay - the number of milliseconds to delay the updater + * @returns a state and debounced `dispatch()` function + * @public + */ export function useDebouncedReducer>( reducer: R, initialArg: S, @@ -23,6 +34,17 @@ export function useDebouncedReducer>( } ]; +/** + * Hook to create a reduced state with a debounced `dispatch()` function. + * + * @param reducer - the reducer function + * @param initialArg - the initial state value or the argument passed to the + * initial state generator function + * @param init - the initial state generator function + * @param delay - the number of milliseconds to delay the updater + * @returns a state and debounced `dispatch()` function + * @public + */ export function useDebouncedReducer, I>( reducer: R, initialArg: I, @@ -36,18 +58,29 @@ export function useDebouncedReducer, I>( } ]; -export function useDebouncedReducer>( - reducer: R, - initialArg: S, - init: undefined, - delay: number -): [ - ReducerState, - Dispatch & { - flush: () => void; - cancel: () => void; - } -]; +// /** +// * Hook to create a reduced state with a debounced `dispatch()` function. +// * +// * @param reducer - the reducer function +// * @param initialArg - the initial state value or the argument passed to the +// * initial state generator function +// * @param init - the initial state generator function +// * @param delay - the number of milliseconds to delay the updater +// * @returns a state and debounced `dispatch()` function +// * @public +// */ +// export function useDebouncedReducer>( +// reducer: R, +// initialArg: S, +// init: undefined, +// delay: number +// ): [ +// ReducerState, +// Dispatch & { +// flush: () => void; +// cancel: () => void; +// } +// ]; /** * Hook to create a reduced state with a debounced `dispatch()` function. @@ -71,6 +104,20 @@ export function useDebouncedReducer, I>( flush: () => void; cancel: () => void; } -] { - return useDebouncedUpdates(useReducer(reducer, initialArg, init), delay); +]; + +export function useDebouncedReducer( + reducer: (prevState: unknown, action?: unknown) => unknown, + initialArg: unknown, + init: ((arg?: unknown) => unknown) | undefined, + delay: number +) { + return useDebouncedUpdates( + init !== undefined + ? // eslint-disable-next-line react-hooks/rules-of-hooks + useReducer(reducer, initialArg, init) + : // eslint-disable-next-line react-hooks/rules-of-hooks + useReducer(reducer, initialArg), + delay + ); } diff --git a/packages/fuselage-hooks/src/useDebouncedUpdates.ts b/packages/fuselage-hooks/src/useDebouncedUpdates.ts index 32a1b48061..2fe5fd41c1 100644 --- a/packages/fuselage-hooks/src/useDebouncedUpdates.ts +++ b/packages/fuselage-hooks/src/useDebouncedUpdates.ts @@ -5,13 +5,13 @@ import { useDebouncedCallback } from './useDebouncedCallback'; /** * Hook to debounce the state dispatcher function returned by hooks like `useState()` and `useReducer()`. * - * @param pair - the state and dispatcher pair which will be debounced - * @param delay - the number of milliseconds to delay the dispatcher + * @param pair the state value and dispatcher function pair + * @param delay the number of milliseconds to delay the dispatcher * @returns a state value and debounced dispatcher pair * @public */ export function useDebouncedUpdates( - [state, dispatch]: [S, DispatchWithoutAction], + pair: [state: S, dispatch: DispatchWithoutAction], delay: number ): [ S, @@ -20,8 +20,17 @@ export function useDebouncedUpdates( cancel: () => void; } ]; + +/** + * Hook to debounce the state dispatcher function returned by hooks like `useState()` and `useReducer()`. + * + * @param pair the state value and dispatcher function pair + * @param delay the number of milliseconds to delay the dispatcher + * @returns a state value and debounced dispatcher pair + * @public + */ export function useDebouncedUpdates( - [state, dispatch]: [S, Dispatch], + pair: [state: S, dispatch: Dispatch], delay: number ): [ S, @@ -30,9 +39,10 @@ export function useDebouncedUpdates( cancel: () => void; } ]; + export function useDebouncedUpdates( - [state, dispatch]: [unknown, () => unknown], + [state, dispatch]: [state: unknown, dispatch: (action?: unknown) => void], delay: number -): [unknown, unknown] { +) { return [state, useDebouncedCallback(dispatch, delay, [])]; } diff --git a/packages/fuselage-hooks/src/useOutsideClick.spec.ts b/packages/fuselage-hooks/src/useOutsideClick.spec.ts index 57d14a5da3..d38ead6503 100644 --- a/packages/fuselage-hooks/src/useOutsideClick.spec.ts +++ b/packages/fuselage-hooks/src/useOutsideClick.spec.ts @@ -1,10 +1,13 @@ import { renderHook } from '@testing-library/react-hooks'; import userEvent from '@testing-library/user-event'; +import type { MutableRefObject } from 'react'; import { useOutsideClick } from './useOutsideClick'; -it('it should call the callback when the user clicked outside the element', () => { - const ref = { current: document.createElement('div') }; +it('it should call the callback when the user clicked outside the element', async () => { + const ref: MutableRefObject = { + current: document.createElement('div'), + }; const cb = jest.fn(); renderHook(() => useOutsideClick([ref], cb)); @@ -14,13 +17,15 @@ it('it should call the callback when the user clicked outside the element', () = document.body.appendChild(sibling); expect(cb).not.toHaveBeenCalled(); - userEvent.click(sibling); + await userEvent.click(sibling); expect(cb).toHaveBeenCalled(); }); -it('it should call the callback when the user clicked outside the elements', () => { - const ref = { current: document.createElement('div') }; - const ref2 = { current: null }; +it('it should call the callback when the user clicked outside the elements', async () => { + const ref: MutableRefObject = { + current: document.createElement('div'), + }; + const ref2: MutableRefObject = { current: null }; const cb = jest.fn(); renderHook(() => useOutsideClick([ref, ref2], cb)); @@ -33,25 +38,29 @@ it('it should call the callback when the user clicked outside the elements', () document.body.appendChild(sibling); expect(cb).not.toHaveBeenCalled(); - userEvent.click(sibling); + await userEvent.click(sibling); expect(cb).toHaveBeenCalled(); }); -it('it should not call the callback when the user clicked inside the element', () => { - const ref = { current: document.createElement('div') }; +it('it should not call the callback when the user clicked inside the element', async () => { + const ref: MutableRefObject = { + current: document.createElement('div'), + }; const cb = jest.fn(); renderHook(() => useOutsideClick([ref], cb)); document.body.appendChild(ref.current); expect(cb).not.toHaveBeenCalled(); - userEvent.click(ref.current); + await userEvent.click(ref.current); expect(cb).not.toHaveBeenCalled(); }); -it('it should not call the callback when the user clicked inside the elements', () => { - const ref = { current: document.createElement('div') }; - const ref2 = { current: null }; +it('it should not call the callback when the user clicked inside the elements', async () => { + const ref: MutableRefObject = { + current: document.createElement('div'), + }; + const ref2: MutableRefObject = { current: null }; const cb = jest.fn(); renderHook(() => useOutsideClick([ref, ref2], cb)); const element2 = document.createElement('div'); @@ -61,12 +70,14 @@ it('it should not call the callback when the user clicked inside the elements', document.body.appendChild(element2); expect(cb).not.toHaveBeenCalled(); - userEvent.click(ref.current); + await userEvent.click(ref.current); expect(cb).not.toHaveBeenCalled(); }); -it('it should not call the callback when the user clicked inside the element and their children', () => { - const ref = { current: document.createElement('div') }; +it('it should not call the callback when the user clicked inside the element and their children', async () => { + const ref: MutableRefObject = { + current: document.createElement('div'), + }; const cb = jest.fn(); renderHook(() => useOutsideClick([ref], cb)); const child = document.createElement('div'); @@ -76,13 +87,15 @@ it('it should not call the callback when the user clicked inside the element and document.body.appendChild(ref.current); expect(cb).not.toHaveBeenCalled(); - userEvent.click(child); + await userEvent.click(child); expect(cb).not.toHaveBeenCalled(); }); -it('it should not call the callback when the user clicked inside of some given element and their children', () => { - const ref = { current: document.createElement('div') }; - const ref2 = { current: null }; +it('it should not call the callback when the user clicked inside of some given element and their children', async () => { + const ref: MutableRefObject = { + current: document.createElement('div'), + }; + const ref2: MutableRefObject = { current: null }; const cb = jest.fn(); renderHook(() => useOutsideClick([ref, ref2], cb)); const element2 = document.createElement('div'); @@ -94,6 +107,6 @@ it('it should not call the callback when the user clicked inside of some given e document.body.appendChild(element2); expect(cb).not.toHaveBeenCalled(); - userEvent.click(child); + await userEvent.click(child); expect(cb).not.toHaveBeenCalled(); }); diff --git a/packages/fuselage-hooks/src/usePosition.spec.ts b/packages/fuselage-hooks/src/usePosition.spec.ts deleted file mode 100644 index 6898a81bac..0000000000 --- a/packages/fuselage-hooks/src/usePosition.spec.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { withResizeObserverMock } from 'testing-utils/mocks/withResizeObserverMock'; - -import { - getPositionStyle, - getTargetBoundaries, - getVariantBoundaries, -} from './usePosition'; -// TODO: add tests targeting the hook itself - -withResizeObserverMock(); - -const container = { - bottom: 1000, - height: 1000, - left: 0, - right: 1024, - top: 0, - width: 1024, - x: 0, - y: 0, -} as DOMRect; - -const referenceBox = { - bottom: 300, - height: 100, - left: 0, - right: 100, - top: 200, - width: 100, - x: 0, - y: 200, -} as DOMRect; - -const target = { - bottom: 50, - height: 50, - left: 0, - right: 50, - top: 0, - width: 50, - x: 0, - y: 0, -} as DOMRect; - -describe('usePosition hook', () => { - describe('getTargetBoundaries', () => { - it('...', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - expect(targetBoundaries.t).toEqual(150); - expect(targetBoundaries.b).toEqual(300); - expect(targetBoundaries.r).toEqual(100); - expect(targetBoundaries.l).toEqual(-50); - }); - }); - describe('getPositionStyle function', () => { - it('returns a style for placement bottom-start', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - const variantStore = getVariantBoundaries({ referenceBox, target }); - const result = getPositionStyle({ - placement: 'bottom-start', - container, - targetBoundaries, - variantStore, - target, - }); - expect(result.style.left).toEqual('0px'); - expect(result.style.top).toEqual('300px'); - }); - it('returns a style for placement bottom-start if the element height does not fit', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - const variantStore = getVariantBoundaries({ referenceBox, target }); - const result = getPositionStyle({ - placement: 'bottom-start', - container: { - ...container, - bottom: 300, - height: 300, - }, - targetBoundaries, - variantStore, - target, - }); - expect(result.style.left).toEqual('0px'); - expect(result.style.top).toEqual('150px'); - }); - - it('returns a style for placement bottom-middle', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - - const variantStore = getVariantBoundaries({ referenceBox, target }); - - const result = getPositionStyle({ - placement: 'bottom-middle', - container, - targetBoundaries, - variantStore, - target, - }); - - expect(result.style.left).toEqual('25px'); - expect(result.style.top).toEqual('300px'); - }); - it('returns a style for placement bottom-middle if the element height does not fit', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - const variantStore = getVariantBoundaries({ referenceBox, target }); - const result = getPositionStyle({ - placement: 'bottom-middle', - container: { - ...container, - bottom: 300, - height: 300, - }, - targetBoundaries, - variantStore, - target, - }); - expect(result.style.left).toEqual('25px'); - expect(result.style.top).toEqual('150px'); - }); - - it('returns a style for placement bottom-end', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - - const variantStore = getVariantBoundaries({ referenceBox, target }); - - const result = getPositionStyle({ - placement: 'bottom-end', - container, - targetBoundaries, - variantStore, - target, - }); - - expect(result.style.left).toEqual('50px'); - expect(result.style.top).toEqual('300px'); - }); - it('returns a style for placement bottom-end if the element height does not fit', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - const variantStore = getVariantBoundaries({ referenceBox, target }); - const result = getPositionStyle({ - placement: 'bottom-end', - container: { - ...container, - bottom: 300, - height: 300, - }, - targetBoundaries, - variantStore, - target, - }); - expect(result.style.left).toEqual('50px'); - expect(result.style.top).toEqual('150px'); - }); - - it('returns a style for placement top-start', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - const variantStore = getVariantBoundaries({ referenceBox, target }); - const result = getPositionStyle({ - placement: 'top-start', - container, - targetBoundaries, - variantStore, - target, - }); - expect(result.style.left).toEqual('0px'); - expect(result.style.top).toEqual('150px'); - }); - it('returns a style for placement top-start if the element height does not fit', () => { - const box = { ...referenceBox, top: 10, y: 10, bottom: 110 }; - const targetBoundaries = getTargetBoundaries({ - referenceBox: box, - target, - }); - const variantStore = getVariantBoundaries({ referenceBox: box, target }); - const result = getPositionStyle({ - placement: 'top-start', - container, - targetBoundaries, - variantStore, - target, - }); - expect(result.style.left).toEqual('0px'); - expect(result.style.top).toEqual('110px'); - }); - it('returns a style for placement top-middle', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - - const variantStore = getVariantBoundaries({ referenceBox, target }); - - const result = getPositionStyle({ - placement: 'top-middle', - container, - targetBoundaries, - variantStore, - target, - }); - - expect(result.style.left).toEqual('25px'); - expect(result.style.top).toEqual('150px'); - }); - it('returns a style for placement top-middle if the element height does not fit', () => { - const box = { ...referenceBox, top: 10, y: 10, bottom: 110 }; - const targetBoundaries = getTargetBoundaries({ - referenceBox: box, - target, - }); - const variantStore = getVariantBoundaries({ referenceBox: box, target }); - const result = getPositionStyle({ - placement: 'top-middle', - container, - targetBoundaries, - variantStore, - target, - }); - expect(result.style.left).toEqual('25px'); - expect(result.style.top).toEqual('110px'); - }); - it('returns a style for placement top-end', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - - const variantStore = getVariantBoundaries({ referenceBox, target }); - - const result = getPositionStyle({ - placement: 'top-end', - container, - targetBoundaries, - variantStore, - target, - }); - - expect(result.style.left).toEqual('50px'); - expect(result.style.top).toEqual('150px'); - }); - it('returns a style for placement top-end if the element height does not fit', () => { - const box = { ...referenceBox, top: 10, y: 10, bottom: 110 }; - const targetBoundaries = getTargetBoundaries({ - referenceBox: box, - target, - }); - const variantStore = getVariantBoundaries({ referenceBox: box, target }); - const result = getPositionStyle({ - placement: 'top-end', - container, - targetBoundaries, - variantStore, - target, - }); - expect(result.style.left).toEqual('50px'); - expect(result.style.top).toEqual('110px'); - }); - it('returns a style for placement bottom-end if the element height does not fit container height', () => { - const targetBoundaries = getTargetBoundaries({ referenceBox, target }); - const variantStore = getVariantBoundaries({ referenceBox, target }); - const result = getPositionStyle({ - placement: 'bottom-end', - container: { - ...container, - bottom: 50, - height: 50, - }, - targetBoundaries, - variantStore, - target, - }); - expect(result.style.overflowY).toEqual('auto'); - expect(result.style.left).toEqual('50px'); - expect(result.style.top).toEqual('300px'); - }); - }); -}); diff --git a/packages/fuselage-hooks/src/usePosition.ts b/packages/fuselage-hooks/src/usePosition.ts deleted file mode 100644 index 226b066227..0000000000 --- a/packages/fuselage-hooks/src/usePosition.ts +++ /dev/null @@ -1,334 +0,0 @@ -import type { AllHTMLAttributes, RefObject } from 'react'; -import { useState, useEffect, useRef } from 'react'; - -import { useDebouncedCallback } from './useDebouncedCallback'; -import { useMutableCallback } from './useMutableCallback'; - -export type Positions = 'top' | 'left' | 'bottom' | 'right'; - -export type Placements = - | 'top-start' - | 'top-middle' - | 'top-end' - | 'bottom-start' - | 'bottom-middle' - | 'bottom-end' - | 'left-start' - | 'left-middle' - | 'left-end' - | 'right-start' - | 'right-middle' - | 'right-end' - | Positions; - -export type PositionOptions = { - margin?: number; - container?: Element; - placement?: Placements; - watch?: boolean; -}; - -export type PositionFlipOrder = { - top: string; - right: string; - bottom: string; - left: string; -}; - -type Boundaries = { - t: number; - b: number; - r: number; - l: number; -}; - -type VariantBoundaries = { - vm: number; - vs: number; - ve: number; - hs: number; - he: number; - hm: number; -}; - -type PositionResult = { - style: AllHTMLAttributes['style']; - placement?: Placements; -}; - -enum PlacementMap { - t = 'top', - b = 'bottom', - l = 'left', - r = 'right', - s = 'start', - e = 'end', - m = 'middle', -} - -const fallbackOrderVariant = { - start: 'sem', - middle: 'mse', - end: 'esm', -}; - -const fallbackOrder = { - top: 'tbrl', - bottom: 'btrl', - right: 'rltb', - left: 'lrbt', -} as const; - -const emptyStyle: PositionResult = { - style: { - position: 'fixed', - visibility: 'hidden', - }, -}; - -const getParents = function (element: Element): Array { - // Set up a parent array - const parents = []; - for (let el = element.parentNode; el && el !== document; el = el.parentNode) { - parents.push(el); - } - return parents.filter((element) => element.nodeType !== Node.TEXT_NODE); -}; - -function getScrollParents(element: Element): Array { - const parents = getParents(element); - - return parents; -} - -const useBoundingClientRect = ( - element: RefObject, - watch = false, - callback: () => void -): void => - useEffect(() => { - if (!element.current) { - return; - } - - callback(); - - if (!watch) { - return; - } - - const parents = getScrollParents(element.current); - const passive = { passive: true }; - - const observer = new ResizeObserver(() => { - if (!element.current) { - return; - } - callback(); - }); - observer.observe(element.current); - window.addEventListener('resize', callback); - parents.forEach((el) => el.addEventListener('scroll', callback, passive)); - - return () => { - observer.disconnect(); - window.removeEventListener('resize', callback); - parents.forEach((el) => el.removeEventListener('scroll', callback)); - }; - }, [watch, callback, element]); - -export const getPositionStyle = ({ - placement = 'bottom-start', - container, - targetBoundaries, - variantStore, - target, - margin = 0, -}: { - placement: Placements; - target: DOMRect; - container: DOMRect; - targetBoundaries: Boundaries; - variantStore?: VariantBoundaries; - margin?: number; -}): PositionResult => { - if (!targetBoundaries) { - return emptyStyle; - } - - const { top, left, bottom, right } = container; - - const [placementKey, variantKey = 'middle'] = placement.split('-'); - - const placementAttempts = fallbackOrder[placementKey]; - const variantsAttempts = fallbackOrderVariant[variantKey]; - - for (const placementAttempt of placementAttempts) { - const directionVertical = ['t', 'b'].includes(placementAttempt); - - const [positionKey, variantKey] = directionVertical - ? ['top', 'left'] - : ['left', 'top']; - - const point = targetBoundaries[placementAttempt]; - - const [positionBox, variantBox] = directionVertical - ? [target.height, target.width] - : [target.width, target.height]; - const [positionMaximum, variantMaximum] = directionVertical - ? [bottom, right] - : [right, bottom]; - const [positionMinimum, variantMinimum] = directionVertical - ? [top, left] - : [left, top]; - - // if the point extrapolate the container boundaries - if (point < positionMinimum || point + positionBox > positionMaximum) { - continue; - } - - for (const v of variantsAttempts) { - // The position-value, the related size value of the popper and the limit - const variantPoint = variantStore[`${directionVertical ? 'v' : 'h'}${v}`]; - - if ( - variantPoint < variantMinimum || - variantPoint + variantBox > variantMaximum - ) { - continue; - } - return { - style: { - [positionKey]: `${point}px`, - [variantKey]: `${variantPoint}px`, - position: 'fixed', - zIndex: '9999', - opacity: 1, - }, - placement: `${PlacementMap[placementAttempt]}-${PlacementMap[v]}`, - } as unknown as PositionResult; - } - } - - const placementAttempt = placementAttempts[0]; - - const directionVertical = ['t', 'b'].includes(placementAttempt); - - const point = targetBoundaries[placementAttempt]; - const variantPoint = - variantStore[`${directionVertical ? 'v' : 'h'}${variantsAttempts[0]}`]; - - return { - style: { - top: `${point}px`, - left: `${variantPoint}px`, - position: 'fixed', - ...(bottom < target.height + point && { - bottom: `${margin}px`, - overflowY: 'auto', - }), - ...({ zIndex: '9999' } as any), - }, - placement: `${PlacementMap[placementAttempt]}-${ - PlacementMap[variantsAttempts[0]] - }`, - } as PositionResult; -}; - -export const getTargetBoundaries = ({ - referenceBox, - target, - margin = 0, -}: { - referenceBox?: DOMRect; - target?: DOMRect; - margin?: number; -}): Boundaries | null => - referenceBox && - target && { - t: referenceBox.top - target.height - margin, - b: referenceBox.bottom + margin, - r: referenceBox.right + margin, - l: referenceBox.left - target.width - margin, - }; - -export const getVariantBoundaries = ({ - referenceBox, - target, -}: { - referenceBox?: DOMRect; - target?: DOMRect; -}): VariantBoundaries | null => - referenceBox && - target && { - vm: -target.width / 2 + (referenceBox.left + referenceBox.width / 2), - vs: referenceBox.left, - ve: referenceBox.left + referenceBox.width - target.width, - hs: referenceBox.bottom - referenceBox.height, - he: referenceBox.bottom - target.height, - hm: referenceBox.bottom - referenceBox.height / 2 - target.height / 2, - }; - -/** - * Hook to deal and position an element using an anchor - * @param reference - the anchor - * @param targetEl - the element to be positioned - * @param options - options to position - * @returns The style containing top and left position - * @public - */ - -export const usePosition = ( - reference: RefObject, - target: RefObject, - options: PositionOptions -): PositionResult => { - const { - margin = 8, - placement = 'bottom-start', - container: containerElement = document.body, - watch = true, - } = options; - const container = useRef(containerElement); - - const [style, setStyle] = useState(emptyStyle); - - const callback = useDebouncedCallback( - useMutableCallback(() => { - const clone = target.current.cloneNode(true) as HTMLElement; - - clone.style.bottom = ''; - clone.id = 'clone'; - target.current.parentElement.appendChild(clone); - const boundaries = clone.getBoundingClientRect(); - target.current.parentElement.removeChild(clone); - - const targetBoundaries = getTargetBoundaries({ - referenceBox: reference.current.getBoundingClientRect(), - target: boundaries, - margin, - }); - const variantStore = getVariantBoundaries({ - referenceBox: reference.current.getBoundingClientRect(), - target: boundaries, - }); - - setStyle( - getPositionStyle({ - placement, - container: container.current.getBoundingClientRect(), - targetBoundaries, - variantStore, - target: boundaries, - margin, - }) - ); - }), - 10 - ); - - useBoundingClientRect(target, watch, callback); - useBoundingClientRect(reference, watch, callback); - useBoundingClientRect(container, watch, callback); - return style; -}; diff --git a/packages/fuselage-hooks/src/usePosition/Placement.ts b/packages/fuselage-hooks/src/usePosition/Placement.ts new file mode 100644 index 0000000000..0fffbd519c --- /dev/null +++ b/packages/fuselage-hooks/src/usePosition/Placement.ts @@ -0,0 +1,4 @@ +import type { PlacementVariant } from './PlacementVariant'; +import type { Position } from './Position'; + +export type Placement = `${Position}-${PlacementVariant}` | Position; diff --git a/packages/fuselage-hooks/src/usePosition/PlacementVariant.ts b/packages/fuselage-hooks/src/usePosition/PlacementVariant.ts new file mode 100644 index 0000000000..1d6a29e800 --- /dev/null +++ b/packages/fuselage-hooks/src/usePosition/PlacementVariant.ts @@ -0,0 +1 @@ +export type PlacementVariant = 'start' | 'middle' | 'end'; diff --git a/packages/fuselage-hooks/src/usePosition/Position.ts b/packages/fuselage-hooks/src/usePosition/Position.ts new file mode 100644 index 0000000000..9d240ee20e --- /dev/null +++ b/packages/fuselage-hooks/src/usePosition/Position.ts @@ -0,0 +1 @@ +export type Position = 'top' | 'left' | 'bottom' | 'right'; diff --git a/packages/fuselage-hooks/src/usePosition/getTargetBoundaries.spec.ts b/packages/fuselage-hooks/src/usePosition/getTargetBoundaries.spec.ts new file mode 100644 index 0000000000..81bbb5fc49 --- /dev/null +++ b/packages/fuselage-hooks/src/usePosition/getTargetBoundaries.spec.ts @@ -0,0 +1,44 @@ +import { withResizeObserverMock } from 'testing-utils/mocks/withResizeObserverMock'; + +import { getTargetBoundaries } from './getTargetBoundaries'; + +withResizeObserverMock(); + +const anchorRect: DOMRect = { + bottom: 300, + height: 100, + left: 0, + right: 100, + top: 200, + width: 100, + x: 0, + y: 200, + toJSON() { + return this; + }, +}; + +const targetRect: DOMRect = { + bottom: 50, + height: 50, + left: 0, + right: 50, + top: 0, + width: 50, + x: 0, + y: 0, + toJSON() { + return this; + }, +}; + +it('should work', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect, + targetRect, + }); + expect(targetBoundaries.t).toEqual(150); + expect(targetBoundaries.b).toEqual(300); + expect(targetBoundaries.r).toEqual(100); + expect(targetBoundaries.l).toEqual(-50); +}); diff --git a/packages/fuselage-hooks/src/usePosition/getTargetBoundaries.ts b/packages/fuselage-hooks/src/usePosition/getTargetBoundaries.ts new file mode 100644 index 0000000000..8f5ebc0d2d --- /dev/null +++ b/packages/fuselage-hooks/src/usePosition/getTargetBoundaries.ts @@ -0,0 +1,23 @@ +export type TargetBoundaries = { + t: number; + b: number; + r: number; + l: number; +}; + +export function getTargetBoundaries({ + anchorRect, + targetRect, + margin = 0, +}: { + anchorRect: DOMRect; + targetRect: DOMRect; + margin?: number; +}): TargetBoundaries { + return { + t: anchorRect.top - targetRect.height - margin, + b: anchorRect.bottom + margin, + r: anchorRect.right + margin, + l: anchorRect.left - targetRect.width - margin, + }; +} diff --git a/packages/fuselage-hooks/src/usePosition/getVariantBoundaries.ts b/packages/fuselage-hooks/src/usePosition/getVariantBoundaries.ts new file mode 100644 index 0000000000..df3809081f --- /dev/null +++ b/packages/fuselage-hooks/src/usePosition/getVariantBoundaries.ts @@ -0,0 +1,25 @@ +export type VariantBoundaries = { + vm: number; + vs: number; + ve: number; + hs: number; + he: number; + hm: number; +}; + +export function getVariantBoundaries({ + anchorRect, + targetRect, +}: { + anchorRect: DOMRect; + targetRect: DOMRect; +}): VariantBoundaries { + return { + vm: -targetRect.width / 2 + (anchorRect.left + anchorRect.width / 2), + vs: anchorRect.left, + ve: anchorRect.left + anchorRect.width - targetRect.width, + hs: anchorRect.bottom - anchorRect.height, + he: anchorRect.bottom - targetRect.height, + hm: anchorRect.bottom - anchorRect.height / 2 - targetRect.height / 2, + }; +} diff --git a/packages/fuselage-hooks/src/usePosition/index.spec.ts b/packages/fuselage-hooks/src/usePosition/index.spec.ts new file mode 100644 index 0000000000..24e0dfd5f9 --- /dev/null +++ b/packages/fuselage-hooks/src/usePosition/index.spec.ts @@ -0,0 +1,335 @@ +import { withResizeObserverMock } from 'testing-utils/mocks/withResizeObserverMock'; + +import { getPositionStyle } from '.'; +import { getTargetBoundaries } from './getTargetBoundaries'; +import { getVariantBoundaries } from './getVariantBoundaries'; +// TODO: add tests targeting the hook itself + +withResizeObserverMock(); + +const container = { + bottom: 1000, + height: 1000, + left: 0, + right: 1024, + top: 0, + width: 1024, + x: 0, + y: 0, +} as DOMRect; + +const referenceBox = { + bottom: 300, + height: 100, + left: 0, + right: 100, + top: 200, + width: 100, + x: 0, + y: 200, +} as DOMRect; + +const target = { + bottom: 50, + height: 50, + left: 0, + right: 50, + top: 0, + width: 50, + x: 0, + y: 0, +} as DOMRect; + +describe('getPositionStyle function', () => { + it('returns a style for placement bottom-start', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const variantStore = getVariantBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const result = getPositionStyle({ + placement: 'bottom-start', + containerRect: container, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + expect(result.style.left).toEqual('0px'); + expect(result.style.top).toEqual('300px'); + }); + + it('returns a style for placement bottom-start if the element height does not fit', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const variantStore = getVariantBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const result = getPositionStyle({ + placement: 'bottom-start', + containerRect: { + ...container, + bottom: 300, + height: 300, + }, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + expect(result.style.left).toEqual('0px'); + expect(result.style.top).toEqual('150px'); + }); + + it('returns a style for placement bottom-middle', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + + const variantStore = getVariantBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + + const result = getPositionStyle({ + placement: 'bottom-middle', + containerRect: container, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + + expect(result.style.left).toEqual('25px'); + expect(result.style.top).toEqual('300px'); + }); + + it('returns a style for placement bottom-middle if the element height does not fit', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const variantStore = getVariantBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const result = getPositionStyle({ + placement: 'bottom-middle', + containerRect: { + ...container, + bottom: 300, + height: 300, + }, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + expect(result.style.left).toEqual('25px'); + expect(result.style.top).toEqual('150px'); + }); + + it('returns a style for placement bottom-end', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + + const variantStore = getVariantBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + + const result = getPositionStyle({ + placement: 'bottom-end', + containerRect: container, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + + expect(result.style.left).toEqual('50px'); + expect(result.style.top).toEqual('300px'); + }); + + it('returns a style for placement bottom-end if the element height does not fit', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const variantStore = getVariantBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const result = getPositionStyle({ + placement: 'bottom-end', + containerRect: { + ...container, + bottom: 300, + height: 300, + }, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + expect(result.style.left).toEqual('50px'); + expect(result.style.top).toEqual('150px'); + }); + + it('returns a style for placement top-start', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const variantStore = getVariantBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const result = getPositionStyle({ + placement: 'top-start', + containerRect: container, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + expect(result.style.left).toEqual('0px'); + expect(result.style.top).toEqual('150px'); + }); + + it('returns a style for placement top-start if the element height does not fit', () => { + const box = { ...referenceBox, top: 10, y: 10, bottom: 110 }; + const targetBoundaries = getTargetBoundaries({ + anchorRect: box, + targetRect: target, + }); + const variantStore = getVariantBoundaries({ + anchorRect: box, + targetRect: target, + }); + const result = getPositionStyle({ + placement: 'top-start', + containerRect: container, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + expect(result.style.left).toEqual('0px'); + expect(result.style.top).toEqual('110px'); + }); + + it('returns a style for placement top-middle', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + + const variantStore = getVariantBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + + const result = getPositionStyle({ + placement: 'top-middle', + containerRect: container, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + + expect(result.style.left).toEqual('25px'); + expect(result.style.top).toEqual('150px'); + }); + + it('returns a style for placement top-middle if the element height does not fit', () => { + const box = { ...referenceBox, top: 10, y: 10, bottom: 110 }; + const targetBoundaries = getTargetBoundaries({ + anchorRect: box, + targetRect: target, + }); + const variantStore = getVariantBoundaries({ + anchorRect: box, + targetRect: target, + }); + const result = getPositionStyle({ + placement: 'top-middle', + containerRect: container, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + expect(result.style.left).toEqual('25px'); + expect(result.style.top).toEqual('110px'); + }); + + it('returns a style for placement top-end', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + + const variantStore = getVariantBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + + const result = getPositionStyle({ + placement: 'top-end', + containerRect: container, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + + expect(result.style.left).toEqual('50px'); + expect(result.style.top).toEqual('150px'); + }); + + it('returns a style for placement top-end if the element height does not fit', () => { + const box = { ...referenceBox, top: 10, y: 10, bottom: 110 }; + const targetBoundaries = getTargetBoundaries({ + anchorRect: box, + targetRect: target, + }); + const variantStore = getVariantBoundaries({ + anchorRect: box, + targetRect: target, + }); + const result = getPositionStyle({ + placement: 'top-end', + containerRect: container, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + expect(result.style.left).toEqual('50px'); + expect(result.style.top).toEqual('110px'); + }); + + it('returns a style for placement bottom-end if the element height does not fit container height', () => { + const targetBoundaries = getTargetBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const variantStore = getVariantBoundaries({ + anchorRect: referenceBox, + targetRect: target, + }); + const result = getPositionStyle({ + placement: 'bottom-end', + containerRect: { + ...container, + bottom: 50, + height: 50, + }, + targetBoundaries, + variantBoundaries: variantStore, + targetRect: target, + }); + expect(result.style.overflowY).toEqual('auto'); + expect(result.style.left).toEqual('50px'); + expect(result.style.top).toEqual('300px'); + }); +}); diff --git a/packages/fuselage-hooks/src/usePosition/index.ts b/packages/fuselage-hooks/src/usePosition/index.ts new file mode 100644 index 0000000000..b7aff9c0e6 --- /dev/null +++ b/packages/fuselage-hooks/src/usePosition/index.ts @@ -0,0 +1,249 @@ +import { useEffect, useRef, useState } from 'react'; +import type { RefObject, CSSProperties } from 'react'; + +import { useDebouncedCallback } from '../useDebouncedCallback'; +import { useMutableCallback } from '../useMutableCallback'; +import { useSafely } from '../useSafely'; +import type { Placement } from './Placement'; +import type { PlacementVariant } from './PlacementVariant'; +import type { Position } from './Position'; +import type { TargetBoundaries } from './getTargetBoundaries'; +import { getTargetBoundaries } from './getTargetBoundaries'; +import type { VariantBoundaries } from './getVariantBoundaries'; +import { getVariantBoundaries } from './getVariantBoundaries'; +import { useBoundingClientRectChanges } from './useBoundingClientRectChanges'; + +export type UsePositionOptions = { + margin?: number; + container?: Element; + placement?: Placement; +}; + +export type UsePositionResult = { + style: CSSProperties; + placement?: Placement; +}; + +type TargetBoundariesKey = keyof TargetBoundaries; +type VariantBoundariesKey = keyof VariantBoundaries; +type VariantBoundariesKeyWithoutDirection = VariantBoundariesKey extends `${ + | 'h' + | 'v'}${infer U}` + ? U + : never; + +const fallbackOrder: Record = { + top: ['t', 'b', 'r', 'l'], + bottom: ['b', 't', 'r', 'l'], + right: ['r', 'l', 't', 'b'], + left: ['l', 'r', 'b', 't'], +}; + +const fallbackOrderVariant: Record< + PlacementVariant, + VariantBoundariesKeyWithoutDirection[] +> = { + start: ['s', 'e', 'm'], + middle: ['m', 's', 'e'], + end: ['e', 's', 'm'], +}; + +const keysToPlacementMap: Record & + Record = { + t: 'top', + b: 'bottom', + l: 'left', + r: 'right', + s: 'start', + e: 'end', + m: 'middle', +}; + +const emptyStyle: UsePositionResult = { + style: { + position: 'fixed', + visibility: 'hidden', + }, +}; + +const splitPlacement = (placement: Placement) => { + const [placementKey, variantKey = 'middle'] = placement.split('-'); + + return [placementKey as Position, variantKey as PlacementVariant] as const; +}; + +export function getPositionStyle({ + placement, + targetRect, + containerRect, + targetBoundaries, + variantBoundaries, + margin = 0, +}: { + placement: Placement; + targetRect: DOMRect; + containerRect: DOMRect; + targetBoundaries: TargetBoundaries; + variantBoundaries: VariantBoundaries; + margin?: number; +}): UsePositionResult { + if (!targetBoundaries) { + return emptyStyle; + } + + const { top, left, bottom, right } = containerRect; + + const [position, placementVariant] = splitPlacement(placement); + + const placementAttempts = fallbackOrder[position]; + const variantsAttempts = fallbackOrderVariant[placementVariant]; + + for (const placementAttempt of placementAttempts) { + const directionVertical = ['t', 'b'].includes(placementAttempt); + + const [positionKey, variantKey] = directionVertical + ? ['top', 'left'] + : ['left', 'top']; + + const point = targetBoundaries[placementAttempt]; + + const [positionBox, variantBox] = directionVertical + ? [targetRect.height, targetRect.width] + : [targetRect.width, targetRect.height]; + const [positionMaximum, variantMaximum] = directionVertical + ? [bottom, right] + : [right, bottom]; + const [positionMinimum, variantMinimum] = directionVertical + ? [top, left] + : [left, top]; + + // if the point extrapolate the container boundaries + if (point < positionMinimum || point + positionBox > positionMaximum) { + continue; + } + + for (const v of variantsAttempts) { + // The position-value, the related size value of the popper and the limit + const variantPoint = + variantBoundaries[`${directionVertical ? 'v' : 'h'}${v}`]; + + if ( + variantPoint < variantMinimum || + variantPoint + variantBox > variantMaximum + ) { + continue; + } + return { + style: { + [positionKey]: `${point}px`, + [variantKey]: `${variantPoint}px`, + position: 'fixed', + zIndex: 9999, + opacity: 1, + }, + placement: `${keysToPlacementMap[placementAttempt]}-${keysToPlacementMap[v]}`, + }; + } + } + + const placementAttempt = placementAttempts[0]; + + const directionVertical = ['t', 'b'].includes(placementAttempt); + + const point = targetBoundaries[placementAttempt]; + const variantPoint = + variantBoundaries[`${directionVertical ? 'v' : 'h'}${variantsAttempts[0]}`]; + + return { + style: { + top: `${point}px`, + left: `${variantPoint}px`, + position: 'fixed', + ...(bottom < targetRect.height + point && { + bottom: `${margin}px`, + overflowY: 'auto', + }), + ...({ zIndex: '9999' } as any), + }, + placement: `${keysToPlacementMap[placementAttempt]}-${ + keysToPlacementMap[variantsAttempts[0]] + }`, + } as UsePositionResult; +} + +const UPDATE_DEBOUNCE_DELAY = 30; + +/** + * Hook to deal and position an element using an anchor + * @param anchorRef - the anchor + * @param targetRef - the element to be positioned + * @param options - options to position + * @returns The style containing top and left position + * @public + */ +export function usePosition( + anchorRef: RefObject, + targetRef: RefObject, + { + margin = 8, + placement = 'bottom-start', + container = document.body, + }: UsePositionOptions = {} +): UsePositionResult { + const [style, setStyle] = useSafely(useState(emptyStyle)); + + const containerRef = useRef(container); + + useEffect(() => { + containerRef.current = container; + }, [container]); + + const handleBoundingClientRectChange = useDebouncedCallback( + useMutableCallback(() => { + const target = targetRef.current; + const anchor = anchorRef.current; + const targetParent = target?.parentElement; + + if (!target || !anchor || !targetParent) { + return; + } + + const clone = target.cloneNode(true) as HTMLElement; + clone.style.bottom = ''; + clone.id = 'clone'; + targetParent.appendChild(clone); + const targetRect = clone.getBoundingClientRect(); + targetParent.removeChild(clone); + + const anchorRect = anchor.getBoundingClientRect(); + + const targetBoundaries = getTargetBoundaries({ + anchorRect, + targetRect, + margin, + }); + + const variantBoundaries = getVariantBoundaries({ + anchorRect, + targetRect, + }); + + setStyle( + getPositionStyle({ + placement, + containerRect: container.getBoundingClientRect(), + targetBoundaries, + variantBoundaries, + targetRect, + margin, + }) + ); + }), + UPDATE_DEBOUNCE_DELAY + ); + + useBoundingClientRectChanges(targetRef, handleBoundingClientRectChange); + useBoundingClientRectChanges(anchorRef, handleBoundingClientRectChange); + useBoundingClientRectChanges(containerRef, handleBoundingClientRectChange); + return style; +} diff --git a/packages/fuselage-hooks/src/usePosition/useBoundingClientRectChanges.ts b/packages/fuselage-hooks/src/usePosition/useBoundingClientRectChanges.ts new file mode 100644 index 0000000000..4eac04a161 --- /dev/null +++ b/packages/fuselage-hooks/src/usePosition/useBoundingClientRectChanges.ts @@ -0,0 +1,55 @@ +import { useEffect } from 'react'; +import type { RefObject } from 'react'; + +function getAncestors(element: Element): Element[] { + const ancestors: Element[] = []; + for ( + let el = element.parentElement; + !!el && el !== document.documentElement; + el = el.parentElement + ) { + ancestors.push(el); + } + return ancestors; +} + +export function useBoundingClientRectChanges( + ref: RefObject, + callback: () => void +): void { + useEffect(() => { + const element = ref.current; + + if (!element) { + return; + } + + const safeCallback = () => { + if (!ref.current) { + return; + } + + callback(); + }; + + safeCallback(); + + const observer = new ResizeObserver(safeCallback); + observer.observe(element); + + window.addEventListener('resize', safeCallback); + + const ancestors = getAncestors(element); + ancestors.forEach((ancestor) => + ancestor.addEventListener('scroll', safeCallback, { passive: true }) + ); + + return () => { + observer.disconnect(); + window.removeEventListener('resize', safeCallback); + ancestors.forEach((ancestor) => + ancestor.removeEventListener('scroll', safeCallback) + ); + }; + }, [callback, ref]); +} diff --git a/packages/fuselage-hooks/src/usePrefersReducedMotion.spec.ts b/packages/fuselage-hooks/src/usePrefersReducedMotion.spec.ts index 4c57903973..e4615e92b0 100644 --- a/packages/fuselage-hooks/src/usePrefersReducedMotion.spec.ts +++ b/packages/fuselage-hooks/src/usePrefersReducedMotion.spec.ts @@ -5,7 +5,7 @@ import { usePrefersReducedMotion } from './usePrefersReducedMotion'; const setViewport = withMatchMediaMock(); -let container: HTMLDivElement; +let container: HTMLDivElement | undefined; beforeEach(() => { container = document.createElement('div'); @@ -13,8 +13,8 @@ beforeEach(() => { }); afterEach(() => { - container.remove(); - container = null; + container?.remove(); + container = undefined; }); it('returns false on the initial call', () => { diff --git a/packages/fuselage-hooks/src/useResizeObserver.ts b/packages/fuselage-hooks/src/useResizeObserver.ts index 280588b7cd..70bdd02865 100644 --- a/packages/fuselage-hooks/src/useResizeObserver.ts +++ b/packages/fuselage-hooks/src/useResizeObserver.ts @@ -22,16 +22,17 @@ type UseResizeObserverOptions = { * @public */ export const useResizeObserver = ({ - debounceDelay, + debounceDelay = 0, }: UseResizeObserverOptions = {}): { ref: RefObject; - contentBoxSize: ResizeObserverSize; - borderBoxSize: ResizeObserverSize; + contentBoxSize: Partial; + borderBoxSize: Partial; } => { - const ref = useRef(); + const ref = useRef(null); + const [{ borderBoxSize, contentBoxSize }, setSizes] = useDebouncedState<{ - borderBoxSize: ResizeObserverSize; - contentBoxSize: ResizeObserverSize; + borderBoxSize: Partial; + contentBoxSize: Partial; }>( { borderBoxSize: { diff --git a/packages/fuselage-hooks/src/useSafely.spec.ts b/packages/fuselage-hooks/src/useSafely.spec.ts index b89ea03be0..6cca510e17 100644 --- a/packages/fuselage-hooks/src/useSafely.spec.ts +++ b/packages/fuselage-hooks/src/useSafely.spec.ts @@ -1,5 +1,5 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import type { DispatchWithoutAction, Dispatch } from 'react'; +import type { Dispatch } from 'react'; import { useState } from 'react'; import { useSafely } from './useSafely'; @@ -8,9 +8,7 @@ it('returns a new dispatcher that invokes the previous one', () => { const state = Symbol(); const dispatcher = jest.fn(); - const { result } = renderHook(() => - useSafely([state, dispatcher]) - ); + const { result } = renderHook(() => useSafely([state, dispatcher])); const [, newDispatcher] = result.current; act(() => { @@ -24,9 +22,7 @@ it('returns a new dispatcher that can be called after unmount', () => { const state = Symbol(); const dispatcher = jest.fn(); - const { result, unmount } = renderHook(() => - useSafely([state, dispatcher]) - ); + const { result, unmount } = renderHook(() => useSafely([state, dispatcher])); const [, newDispatcher] = result.current; unmount(); diff --git a/packages/fuselage-hooks/src/useSafely.ts b/packages/fuselage-hooks/src/useSafely.ts index f59c11fa93..a460ba009f 100644 --- a/packages/fuselage-hooks/src/useSafely.ts +++ b/packages/fuselage-hooks/src/useSafely.ts @@ -1,7 +1,5 @@ import type { Dispatch, DispatchWithoutAction } from 'react'; -import { useEffect, useRef } from 'react'; - -import { useMutableCallback } from './useMutableCallback'; +import { useCallback, useEffect, useRef } from 'react'; /** * Hook that wraps pairs of state and dispatcher to provide a new dispatcher @@ -11,23 +9,30 @@ import { useMutableCallback } from './useMutableCallback'; * @returns a state value and safe dispatcher pair * @public */ -export const useSafely = >([ +export function useSafely>([ state, dispatcher, -]: [S, Dispatch | DispatchWithoutAction]): [S, D] => { - const dispatcherRef = useRef(dispatcher); +]: [state: S, dispatch: D]): [state: S, dispatch: D]; + +export function useSafely([state, dispatcher]: [ + state: unknown, + dispatch: (action?: unknown) => void +]) { + const dispatcherRef = useRef<((action?: unknown) => void) | undefined>( + dispatcher + ); useEffect( () => () => { - dispatcherRef.current = () => undefined; + dispatcherRef.current = undefined; }, [] ); - const safeDispatcher = useMutableCallback((action?: A) => { + const safeDispatcher = useCallback((action) => { const dispatcher = dispatcherRef.current; - dispatcher(action); - }) as D; + dispatcher?.(action); + }, []); return [state, safeDispatcher]; -}; +} diff --git a/packages/fuselage-hooks/src/useStableArray.ts b/packages/fuselage-hooks/src/useStableArray.ts index 5ccb95349a..a08229c9b9 100644 --- a/packages/fuselage-hooks/src/useStableArray.ts +++ b/packages/fuselage-hooks/src/useStableArray.ts @@ -47,6 +47,6 @@ export const useStableArray = ( array: T, compare: (a: T, b: T) => boolean = Object.is ): T => { - const ref = useRef(Array.isArray(array) ? array : ([] as T)); + const ref = useRef(array); return getCurrentArray(ref, array, compare); }; diff --git a/packages/fuselage-hooks/src/useStorage.ts b/packages/fuselage-hooks/src/useStorage.ts index 7e3331b010..8e710a9ffe 100644 --- a/packages/fuselage-hooks/src/useStorage.ts +++ b/packages/fuselage-hooks/src/useStorage.ts @@ -1,12 +1,12 @@ import { Emitter } from '@rocket.chat/emitter'; import type { Dispatch, SetStateAction } from 'react'; -import { useState, useEffect, useCallback } from 'react'; +import { useRef, useState, useEffect, useCallback } from 'react'; -const makeStorage = ( +function makeStorageHook( storageFactory: Storage | (() => Storage), name: string -): ((key: string, initialValue: T) => [T, Dispatch>]) => { - let storage: Storage = null; +): (key: string, initialValue: T) => [T, Dispatch>] { + let storage: Storage | undefined = undefined; if (typeof window !== 'undefined') { storage = @@ -17,10 +17,13 @@ const makeStorage = ( const ee = new Emitter(); - return function useGenericStorage( + return ( key: string, initialValue: T - ): [T, Dispatch>] { + ): [T, Dispatch>] => { + const initialValueRef = useRef(initialValue); + initialValueRef.current = initialValue; + const [storedValue, setStoredValue] = useState(() => { if (!storage) { return initialValue; @@ -31,11 +34,11 @@ const makeStorage = ( }); const setValue: Dispatch> = useCallback( - (value: T extends unknown ? SetStateAction : never): void => { + (value) => { setStoredValue((prevValue: T) => { const valueToStore: T = - typeof value === 'function' ? value(prevValue) : value; - storage.setItem(getKey(key), JSON.stringify(valueToStore)); + value instanceof Function ? value(prevValue) : value; + storage?.setItem(getKey(key), JSON.stringify(valueToStore)); ee.emit(key, valueToStore); return valueToStore; }); @@ -49,7 +52,9 @@ const makeStorage = ( return; } - setStoredValue(JSON.parse(event.newValue)); + setStoredValue( + event.newValue ? JSON.parse(event.newValue) : initialValueRef.current + ); }; const handleSyntheticEvent = (value: T): void => { @@ -65,7 +70,7 @@ const makeStorage = ( return [storedValue, setValue]; }; -}; +} /** * Hook to deal with localStorage @@ -74,7 +79,7 @@ const makeStorage = ( * @returns a state and a setter function * @public */ -export const useLocalStorage = makeStorage( +export const useLocalStorage = makeStorageHook( () => window.localStorage, 'localStorage' ); @@ -86,7 +91,7 @@ export const useLocalStorage = makeStorage( * @returns a state and a setter function * @public */ -export const useSessionStorage = makeStorage( +export const useSessionStorage = makeStorageHook( () => window.sessionStorage, 'sessionStorage' ); diff --git a/packages/fuselage-hooks/tsconfig.build.json b/packages/fuselage-hooks/tsconfig.build.json new file mode 100644 index 0000000000..d7d529fb9a --- /dev/null +++ b/packages/fuselage-hooks/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["./src/**/*.spec.ts"] +} diff --git a/packages/fuselage-hooks/tsconfig.json b/packages/fuselage-hooks/tsconfig.json index 7b425bd73d..aadf6c69c7 100644 --- a/packages/fuselage-hooks/tsconfig.json +++ b/packages/fuselage-hooks/tsconfig.json @@ -1,29 +1,15 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "./src", "module": "ESNext", - "target": "ES5", "lib": ["DOM", "ES2015"], - "sourceMap": true, - "allowJs": false, - "jsx": "react", - "declaration": true, "declarationDir": "./dist", "outDir": "./dist", "moduleResolution": "node", - "forceConsistentCasingInFileNames": true, - // "noEmit": true, - // "noImplicitReturns": true, - // "noImplicitThis": true, - // "noImplicitAny": true, - // "strictNullChecks": true, - "suppressImplicitAnyIndexErrors": true, - "noUnusedLocals": true, - "noUnusedParameters": true, "esModuleInterop": true, - "resolveJsonModule": true, - "importsNotUsedAsValues": "error" + "resolveJsonModule": true }, "include": ["src"], - "exclude": ["dist", "node_modules", "src/*.spec.ts"] + "exclude": ["dist", "node_modules"] } diff --git a/packages/fuselage-polyfills/index.d.ts b/packages/fuselage-polyfills/index.d.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/fuselage-polyfills/index.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/fuselage-polyfills/package.json b/packages/fuselage-polyfills/package.json index d12c8a7226..d16ca4e74f 100644 --- a/packages/fuselage-polyfills/package.json +++ b/packages/fuselage-polyfills/package.json @@ -15,6 +15,7 @@ "url": "https://github.com/RocketChat/fuselage/issues" }, "main": "index.js", + "types": "index.d.ts", "publishConfig": { "access": "public" }, @@ -36,9 +37,9 @@ "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", "bump": "workspace:~", - "eslint": "~8.26.0", + "eslint": "~8.38.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", - "prettier": "~2.7.1" + "lint-staged": "~13.2.1", + "prettier": "~2.8.7" } } diff --git a/packages/fuselage-toastbar/.eslintignore b/packages/fuselage-toastbar/.eslintignore index d4b5909e36..968aff0559 100644 --- a/packages/fuselage-toastbar/.eslintignore +++ b/packages/fuselage-toastbar/.eslintignore @@ -1,3 +1,4 @@ /dist /node_modules /storybook-static +!/.storybook diff --git a/packages/fuselage-toastbar/.storybook/main.ts b/packages/fuselage-toastbar/.storybook/main.ts index 554b7c71c2..6ad476e8f2 100644 --- a/packages/fuselage-toastbar/.storybook/main.ts +++ b/packages/fuselage-toastbar/.storybook/main.ts @@ -1,7 +1,14 @@ -module.exports = { - addons: ['@storybook/addon-essentials', 'storybook-dark-mode/register'], - stories: ['../src/**/*.stories.tsx', '../src/**/stories.tsx'], +import type { StorybookConfig } from '@storybook/react/types'; + +const config: StorybookConfig = { + core: { + builder: 'webpack5', + }, features: { postcss: false, }, + addons: ['@storybook/addon-essentials', 'storybook-dark-mode'], + stories: ['../src/**/*.stories.tsx', '../src/**/stories.tsx'], }; + +export default config; diff --git a/packages/fuselage-toastbar/.storybook/manager.ts b/packages/fuselage-toastbar/.storybook/manager.ts deleted file mode 100644 index 194ef4014c..0000000000 --- a/packages/fuselage-toastbar/.storybook/manager.ts +++ /dev/null @@ -1,17 +0,0 @@ -import colorTokens from '@rocket.chat/fuselage-tokens/colors.json'; -import { addons } from '@storybook/addons'; -import { create } from '@storybook/theming'; - -import manifest from '../package.json'; -import logo from './logo.svg'; - -addons.setConfig({ - theme: create({ - base: 'light', - brandTitle: manifest.name, - brandImage: logo, - brandUrl: manifest.homepage, - colorPrimary: colorTokens.n500, - colorSecondary: colorTokens.p500, - }), -}); diff --git a/packages/fuselage-toastbar/.storybook/preview.tsx b/packages/fuselage-toastbar/.storybook/preview.tsx index f07bfdb5dd..7a7b60356f 100644 --- a/packages/fuselage-toastbar/.storybook/preview.tsx +++ b/packages/fuselage-toastbar/.storybook/preview.tsx @@ -1,16 +1,20 @@ +import { DarkModeProvider } from '@rocket.chat/layout'; import { DocsPage, DocsContainer } from '@storybook/addon-docs'; -import type { DecoratorFunction } from '@storybook/addons'; -import { addParameters } from '@storybook/react'; -import '@rocket.chat/icons/dist/rocketchat.css'; -import '@rocket.chat/fuselage-polyfills'; -import type { ElementType, ReactElement } from 'react'; +import type { Parameters } from '@storybook/addons'; +import type { DecoratorFn } from '@storybook/react'; +import { themes } from '@storybook/theming'; import { Suspense } from 'react'; import { useDarkMode } from 'storybook-dark-mode'; + +import manifest from '../package.json'; import ToastBarProvider from '../src/ToastBarProvider'; -import { DarkModeProvider } from '@rocket.chat/layout'; -import React from 'react'; +import logo from './logo.svg'; -addParameters({ +import '@rocket.chat/fuselage/dist/fuselage.css'; +import '@rocket.chat/icons/dist/rocketchat.css'; +import '@rocket.chat/fuselage-polyfills'; + +export const parameters: Parameters = { backgrounds: { grid: { cellSize: 4, @@ -25,10 +29,24 @@ addParameters({ options: { storySort: ([, a], [, b]) => a.kind.localeCompare(b.kind), }, -}); + darkMode: { + dark: { + ...themes.dark, + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + }, + light: { + ...themes.normal, + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + }, + }, +}; -export const decorators: DecoratorFunction[] = [ - (Story: ElementType): ReactElement => { +export const decorators: DecoratorFn[] = [ + (Story) => { const dark = useDarkMode(); return ( diff --git a/packages/fuselage-toastbar/package.json b/packages/fuselage-toastbar/package.json index 7e2ae7b741..403f413a4a 100644 --- a/packages/fuselage-toastbar/package.json +++ b/packages/fuselage-toastbar/package.json @@ -53,26 +53,27 @@ "@rocket.chat/layout": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", "@rocket.chat/styled": "workspace:~", - "@storybook/addon-essentials": "~6.5.15", - "@storybook/addons": "~6.5.15", - "@storybook/react": "~6.5.15", - "@storybook/source-loader": "~6.5.15", - "@storybook/theming": "~6.5.15", - "@types/jest": "~27.4.1", - "@types/react": "~17.0.53", - "@types/react-dom": "^17.0.18", + "@storybook/addon-essentials": "~6.5.16", + "@storybook/addons": "~6.5.16", + "@storybook/builder-webpack5": "~6.5.16", + "@storybook/manager-webpack5": "~6.5.16", + "@storybook/react": "~6.5.16", + "@storybook/theming": "~6.5.16", + "@types/jest": "~29.5.0", + "@types/react": "~17.0.57", + "@types/react-dom": "^17.0.19", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "rimraf": "~3.0.2", "storybook-dark-mode": "~1.1.2", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "peerDependencies": { "@rocket.chat/fuselage": "*", @@ -95,15 +96,7 @@ "testMatch": [ "/src/**/*.spec.[jt]s?(x)" ], - "testEnvironment": "jsdom", - "globals": { - "ts-jest": { - "tsconfig": { - "noUnusedLocals": false, - "noUnusedParameters": false - } - } - } + "testEnvironment": "jsdom" }, "volta": { "extends": "../../package.json" diff --git a/packages/fuselage-toastbar/src/ToastBarProvider.tsx b/packages/fuselage-toastbar/src/ToastBarProvider.tsx index 92ba282881..d3c047314e 100644 --- a/packages/fuselage-toastbar/src/ToastBarProvider.tsx +++ b/packages/fuselage-toastbar/src/ToastBarProvider.tsx @@ -1,5 +1,5 @@ import type { ReactNode, ReactElement } from 'react'; -import React, { useState, memo } from 'react'; +import { useState, memo } from 'react'; import type { ToastBarPayload } from './ToastBarContext'; import { ToastBarContext } from './ToastBarContext'; diff --git a/packages/fuselage-toastbar/src/ToastBarTimed.tsx b/packages/fuselage-toastbar/src/ToastBarTimed.tsx index c2ee1686ec..9b9879d702 100644 --- a/packages/fuselage-toastbar/src/ToastBarTimed.tsx +++ b/packages/fuselage-toastbar/src/ToastBarTimed.tsx @@ -1,6 +1,6 @@ import { ToastBar } from '@rocket.chat/fuselage'; import type { ReactElement } from 'react'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import type { ToastBarPayload } from './ToastBarContext'; import { useToastBarDismiss } from './ToastBarContext'; diff --git a/packages/fuselage-toastbar/src/ToastBarZone.tsx b/packages/fuselage-toastbar/src/ToastBarZone.tsx index e34ee719ba..4938c22404 100644 --- a/packages/fuselage-toastbar/src/ToastBarZone.tsx +++ b/packages/fuselage-toastbar/src/ToastBarZone.tsx @@ -1,6 +1,5 @@ import styled from '@rocket.chat/styled'; import type { ReactNode, ReactElement } from 'react'; -import React from 'react'; import type { ToastBarPayload } from './ToastBarContext'; diff --git a/packages/fuselage-toastbar/tsconfig-cjs.json b/packages/fuselage-toastbar/tsconfig-cjs.json index b80edf7f45..0b4e6ec415 100644 --- a/packages/fuselage-toastbar/tsconfig-cjs.json +++ b/packages/fuselage-toastbar/tsconfig-cjs.json @@ -1,8 +1,10 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "module": "commonjs", + "module": "CommonJS", + "rootDir": "./src", "outDir": "./dist/cjs" }, + "include": ["./src"], "exclude": ["**/*.spec.ts"] } diff --git a/packages/fuselage-toastbar/tsconfig-esm.json b/packages/fuselage-toastbar/tsconfig-esm.json index 8c0f74bb02..d6567ecc56 100644 --- a/packages/fuselage-toastbar/tsconfig-esm.json +++ b/packages/fuselage-toastbar/tsconfig-esm.json @@ -1,4 +1,10 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "rootDir": "./src", + "outDir": "./dist/esm" + }, + "include": ["./src"], "exclude": ["**/*.spec.ts"] } diff --git a/packages/fuselage-toastbar/tsconfig.json b/packages/fuselage-toastbar/tsconfig.json index 29fa137f6f..e54f00d0b1 100644 --- a/packages/fuselage-toastbar/tsconfig.json +++ b/packages/fuselage-toastbar/tsconfig.json @@ -1,19 +1,16 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "./src", - "target": "es5", - "module": "ESNext", - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "./dist/esm", - "strict": true, + "rootDirs": ["./src", "./.storybook"], + "target": "ES5", + "module": "CommonJS", + "outDir": "./dist", "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "resolveJsonModule": true, - "jsx": "react-jsx", - "importsNotUsedAsValues": "error" - } + "jsx": "react-jsx" + }, + "include": ["./src", "./.storybook/**/*"], + "exclude": ["./dist"] } diff --git a/packages/fuselage-tokens/.stylelintrc b/packages/fuselage-tokens/.stylelintrc index 67428e417d..d1c42d5122 100644 --- a/packages/fuselage-tokens/.stylelintrc +++ b/packages/fuselage-tokens/.stylelintrc @@ -25,22 +25,14 @@ "declaration-block-no-redundant-longhand-properties": true, "declaration-block-no-shorthand-property-overrides": true, "declaration-block-single-line-max-declarations": 1, - "declaration-block-trailing-semicolon": "always", "font-family-no-duplicate-names": true, "function-linear-gradient-no-nonstandard-direction": true, - "function-max-empty-lines": 0, "function-name-case": "lower", "keyframe-declaration-no-important": true, "length-zero-no-unit": true, - "max-empty-lines": 1, - "media-feature-name-case": "lower", "media-feature-name-no-unknown": true, "no-duplicate-selectors": true, "no-empty-source": true, - "no-extra-semicolons": true, - "number-leading-zero": "always", - "number-no-trailing-zeros": true, - "property-case": "lower", "property-no-unknown": true, "rule-empty-line-before": [ "always", @@ -56,8 +48,6 @@ "scss/at-mixin-pattern": "^-?([a-z][a-z0-9]+-)*[a-z][a-z0-9]+$", "scss/dollar-variable-pattern": "^-?([a-z][a-z0-9]+-)*[a-z][a-z0-9]+$", "scss/no-duplicate-mixins": true, - "selector-max-empty-lines": 0, - "selector-pseudo-class-case": "lower", "selector-pseudo-class-no-unknown": [ true, { @@ -66,15 +56,12 @@ ] } ], - "selector-pseudo-element-case": "lower", "selector-pseudo-element-colon-notation": "double", "selector-pseudo-element-no-unknown": true, "selector-type-case": "lower", "selector-type-no-unknown": true, "shorthand-property-no-redundant-values": true, - "unit-case": "lower", "unit-no-unknown": true, - "value-list-max-empty-lines": 1, "order/properties-order": [ [ { diff --git a/packages/fuselage-tokens/colors.js b/packages/fuselage-tokens/colors.js index 580e505cba..821b46eeca 100644 --- a/packages/fuselage-tokens/colors.js +++ b/packages/fuselage-tokens/colors.js @@ -8,6 +8,7 @@ module.exports = { 'd500': '#EC0D2A', 'd550': '#F5455C', 'd600': '#D40C26', + 'd650': '#DA1F37', 'd700': '#BB0B21', 'd800': '#9B1325', 'd900': '#8B0719', @@ -48,6 +49,7 @@ module.exports = { 's400': '#6CE9C0', 's500': '#2DE0A5', 's600': '#1ECB92', + 's650': '#158D65', 's700': '#19AC7C', 's800': '#148660', 's900': '#106D4F', @@ -71,11 +73,13 @@ module.exports = { 's2-800': '#4A105D', 's2-900': '#350B42', 'w100': '#FFF6D6', + 'w150': '#FFF8E0', 'w200': '#FFECAD', 'w300': '#FFE383', 'w400': '#FFD95A', 'w500': '#FFD031', 'w600': '#F3BE08', + 'w650': '#AC892F', 'w700': '#DFAC00', 'w800': '#B68D00', 'w900': '#8E6300', diff --git a/packages/fuselage-tokens/colors.json b/packages/fuselage-tokens/colors.json index b4a3228118..f52c578747 100644 --- a/packages/fuselage-tokens/colors.json +++ b/packages/fuselage-tokens/colors.json @@ -6,6 +6,7 @@ "d500": "#EC0D2A", "d550": "#F5455C", "d600": "#D40C26", + "d650": "#DA1F37", "d700": "#BB0B21", "d800": "#9B1325", "d900": "#8B0719", @@ -46,6 +47,7 @@ "s400": "#6CE9C0", "s500": "#2DE0A5", "s600": "#1ECB92", + "s650": "#158D65", "s700": "#19AC7C", "s800": "#148660", "s900": "#106D4F", @@ -69,11 +71,13 @@ "s2-800": "#4A105D", "s2-900": "#350B42", "w100": "#FFF6D6", + "w150": "#FFF8E0", "w200": "#FFECAD", "w300": "#FFE383", "w400": "#FFD95A", "w500": "#FFD031", "w600": "#F3BE08", + "w650": "#AC892F", "w700": "#DFAC00", "w800": "#B68D00", "w900": "#8E6300", diff --git a/packages/fuselage-tokens/colors.mjs b/packages/fuselage-tokens/colors.mjs index 59a330103b..fb41bdc54a 100644 --- a/packages/fuselage-tokens/colors.mjs +++ b/packages/fuselage-tokens/colors.mjs @@ -6,6 +6,7 @@ export default { 'd500': '#EC0D2A', 'd550': '#F5455C', 'd600': '#D40C26', + 'd650': '#DA1F37', 'd700': '#BB0B21', 'd800': '#9B1325', 'd900': '#8B0719', @@ -46,6 +47,7 @@ export default { 's400': '#6CE9C0', 's500': '#2DE0A5', 's600': '#1ECB92', + 's650': '#158D65', 's700': '#19AC7C', 's800': '#148660', 's900': '#106D4F', @@ -69,11 +71,13 @@ export default { 's2-800': '#4A105D', 's2-900': '#350B42', 'w100': '#FFF6D6', + 'w150': '#FFF8E0', 'w200': '#FFECAD', 'w300': '#FFE383', 'w400': '#FFD95A', 'w500': '#FFD031', 'w600': '#F3BE08', + 'w650': '#AC892F', 'w700': '#DFAC00', 'w800': '#B68D00', 'w900': '#8E6300', diff --git a/packages/fuselage-tokens/colors.scss b/packages/fuselage-tokens/colors.scss index 1fce9428e1..6eb29e2052 100644 --- a/packages/fuselage-tokens/colors.scss +++ b/packages/fuselage-tokens/colors.scss @@ -6,6 +6,7 @@ $colors: ( d500: #ec0d2a, d550: #f5455c, d600: #d40c26, + d650: #da1f37, d700: #bb0b21, d800: #9b1325, d900: #8b0719, @@ -46,6 +47,7 @@ $colors: ( s400: #6ce9c0, s500: #2de0a5, s600: #1ecb92, + s650: #158d65, s700: #19ac7c, s800: #148660, s900: #106d4f, @@ -69,11 +71,13 @@ $colors: ( s2-800: #4a105d, s2-900: #350b42, w100: #fff6d6, + w150: #fff8e0, w200: #ffecad, w300: #ffe383, w400: #ffd95a, w500: #ffd031, w600: #f3be08, + w650: #ac892f, w700: #dfac00, w800: #b68d00, w900: #8e6300, diff --git a/packages/fuselage-tokens/package.json b/packages/fuselage-tokens/package.json index 2c03e3914b..d1b9c577c7 100644 --- a/packages/fuselage-tokens/package.json +++ b/packages/fuselage-tokens/package.json @@ -7,8 +7,6 @@ "name": "Rocket.Chat", "url": "https://rocket.chat/" }, - "module": "dist/esm/index.js", - "types": "dist/esm/index.d.ts", "license": "MIT", "repository": { "type": "git", @@ -40,8 +38,6 @@ ".:build": "node ./build --config ./config.js", ".:build:legacy": "build-design-tokens", ".:build:clean": "rimraf dist", - ".:build:esm": "tsc -p tsconfig.json", - ".:build:cjs": "tsc -p tsconfig-cjs.json", "bump-next": "bump-next" }, "devDependencies": { @@ -49,21 +45,20 @@ "@rocket.chat/prettier-config": "workspace:~", "build-design-tokens": "workspace:~", "bump": "workspace:~", - "eslint": "~8.26.0", - "eslint-config-prettier": "~8.5.0", + "eslint": "~8.38.0", + "eslint-config-prettier": "~8.8.0", "eslint-plugin-import": "~2.26.0", "eslint-plugin-prettier": "~4.2.1", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", "postcss-scss": "~4.0.6", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "rimraf": "^3.0.2", "style-dictionary": "~3.7.2", - "stylelint": "~14.14.1", - "stylelint-order": "~5.0.0", - "stylelint-prettier": "~2.0.0", - "stylelint-scss": "~4.3.0", - "typescript": "~4.9.4" + "stylelint": "~15.4.0", + "stylelint-order": "~6.0.3", + "stylelint-prettier": "~3.0.0", + "stylelint-scss": "~4.6.0" } } diff --git a/packages/fuselage-tokens/src/colors.jsonc b/packages/fuselage-tokens/src/colors.jsonc index b4a3228118..f52c578747 100644 --- a/packages/fuselage-tokens/src/colors.jsonc +++ b/packages/fuselage-tokens/src/colors.jsonc @@ -6,6 +6,7 @@ "d500": "#EC0D2A", "d550": "#F5455C", "d600": "#D40C26", + "d650": "#DA1F37", "d700": "#BB0B21", "d800": "#9B1325", "d900": "#8B0719", @@ -46,6 +47,7 @@ "s400": "#6CE9C0", "s500": "#2DE0A5", "s600": "#1ECB92", + "s650": "#158D65", "s700": "#19AC7C", "s800": "#148660", "s900": "#106D4F", @@ -69,11 +71,13 @@ "s2-800": "#4A105D", "s2-900": "#350B42", "w100": "#FFF6D6", + "w150": "#FFF8E0", "w200": "#FFECAD", "w300": "#FFE383", "w400": "#FFD95A", "w500": "#FFD031", "w600": "#F3BE08", + "w650": "#AC892F", "w700": "#DFAC00", "w800": "#B68D00", "w900": "#8E6300", diff --git a/packages/fuselage-tokens/src/colors/base.json b/packages/fuselage-tokens/src/colors/base.json index 8f1191ac4f..0d18e6d083 100644 --- a/packages/fuselage-tokens/src/colors/base.json +++ b/packages/fuselage-tokens/src/colors/base.json @@ -7,6 +7,7 @@ "d500": { "value": "#EC0D2A", "group": "colors" }, "d550": { "value": "#F5455C", "group": "colors" }, "d600": { "value": "#D40C26", "group": "colors" }, + "d650": { "value": "#DA1F37", "group": "colors" }, "d700": { "value": "#BB0B21", "group": "colors" }, "d800": { "value": "#9B1325", "group": "colors" }, "d900": { "value": "#8B0719", "group": "colors" }, @@ -47,6 +48,7 @@ "s400": { "value": "#6CE9C0", "group": "colors" }, "s500": { "value": "#2DE0A5", "group": "colors" }, "s600": { "value": "#1ECB92", "group": "colors" }, + "s650": { "value": "#158D65", "group": "colors" }, "s700": { "value": "#19AC7C", "group": "colors" }, "s800": { "value": "#148660", "group": "colors" }, "s900": { "value": "#106D4F", "group": "colors" }, @@ -70,11 +72,13 @@ "s2-800": { "value": "#4A105D", "group": "colors" }, "s2-900": { "value": "#350B42", "group": "colors" }, "w100": { "value": "#FFF6D6", "group": "colors" }, + "w150": { "value": "#FFF8E0", "group": "colors" }, "w200": { "value": "#FFECAD", "group": "colors" }, "w300": { "value": "#FFE383", "group": "colors" }, "w400": { "value": "#FFD95A", "group": "colors" }, "w500": { "value": "#FFD031", "group": "colors" }, "w600": { "value": "#F3BE08", "group": "colors" }, + "w650": { "value": "#AC892F", "group": "colors" }, "w700": { "value": "#DFAC00", "group": "colors" }, "w800": { "value": "#B68D00", "group": "colors" }, "w900": { "value": "#8E6300", "group": "colors" }, diff --git a/packages/fuselage-tokens/tsconfig.json b/packages/fuselage-tokens/tsconfig.json deleted file mode 100644 index 2dab93babd..0000000000 --- a/packages/fuselage-tokens/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "ES5", - "module": "ES2020", - "lib": ["ES2020"], - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "./dist/esm/", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "importsNotUsedAsValues": "error" - }, - "exclude": ["dist/", "src/**/*.spec.ts"] -} diff --git a/packages/fuselage-ui-kit/.babelrc.json b/packages/fuselage-ui-kit/.babelrc.json deleted file mode 100644 index 1320b9a327..0000000000 --- a/packages/fuselage-ui-kit/.babelrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["@babel/preset-env"] -} diff --git a/packages/fuselage-ui-kit/.eslintignore b/packages/fuselage-ui-kit/.eslintignore deleted file mode 100644 index a02f1c5a67..0000000000 --- a/packages/fuselage-ui-kit/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -/dist -/storybook-static -/node_modules -!/.eslintrc.js -!/.storybook diff --git a/packages/fuselage-ui-kit/.eslintrc.js b/packages/fuselage-ui-kit/.eslintrc.js deleted file mode 100644 index cb81eef4f6..0000000000 --- a/packages/fuselage-ui-kit/.eslintrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: '@rocket.chat/eslint-config-alt/react', -}; diff --git a/packages/fuselage-ui-kit/.gitignore b/packages/fuselage-ui-kit/.gitignore deleted file mode 100644 index 4e50609e79..0000000000 --- a/packages/fuselage-ui-kit/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/dist/* -/storybook-static/ -/bundle-report.html -/src/version.ts diff --git a/packages/fuselage-ui-kit/.prettierignore b/packages/fuselage-ui-kit/.prettierignore deleted file mode 100644 index d4b5909e36..0000000000 --- a/packages/fuselage-ui-kit/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -/dist -/node_modules -/storybook-static diff --git a/packages/fuselage-ui-kit/.prettierrc.js b/packages/fuselage-ui-kit/.prettierrc.js deleted file mode 100644 index b57f474edb..0000000000 --- a/packages/fuselage-ui-kit/.prettierrc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@rocket.chat/prettier-config/fuselage'); diff --git a/packages/fuselage-ui-kit/.storybook/logo.svg b/packages/fuselage-ui-kit/.storybook/logo.svg deleted file mode 100644 index 9f73265787..0000000000 --- a/packages/fuselage-ui-kit/.storybook/logo.svg +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/packages/fuselage-ui-kit/.storybook/main.ts b/packages/fuselage-ui-kit/.storybook/main.ts deleted file mode 100644 index 28e2f062c5..0000000000 --- a/packages/fuselage-ui-kit/.storybook/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - addons: ['@storybook/addon-essentials'], - stories: ['../src/**/*.stories.tsx', '../src/**/stories.tsx'], - features: { - postcss: false, - }, -}; diff --git a/packages/fuselage-ui-kit/.storybook/manager.ts b/packages/fuselage-ui-kit/.storybook/manager.ts deleted file mode 100644 index 194ef4014c..0000000000 --- a/packages/fuselage-ui-kit/.storybook/manager.ts +++ /dev/null @@ -1,17 +0,0 @@ -import colorTokens from '@rocket.chat/fuselage-tokens/colors.json'; -import { addons } from '@storybook/addons'; -import { create } from '@storybook/theming'; - -import manifest from '../package.json'; -import logo from './logo.svg'; - -addons.setConfig({ - theme: create({ - base: 'light', - brandTitle: manifest.name, - brandImage: logo, - brandUrl: manifest.homepage, - colorPrimary: colorTokens.n500, - colorSecondary: colorTokens.p500, - }), -}); diff --git a/packages/fuselage-ui-kit/.storybook/preview.tsx b/packages/fuselage-ui-kit/.storybook/preview.tsx deleted file mode 100644 index 01f8446858..0000000000 --- a/packages/fuselage-ui-kit/.storybook/preview.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { DocsPage, DocsContainer } from '@storybook/addon-docs'; -import { addParameters } from '@storybook/react'; -import '@rocket.chat/icons/dist/rocketchat.css'; -import '@rocket.chat/fuselage-polyfills'; -import 'normalize.css/normalize.css'; - -addParameters({ - backgrounds: { - grid: { - cellSize: 4, - cellAmount: 4, - opacity: 0.5, - }, - }, - docs: { - container: DocsContainer, - page: DocsPage, - }, - options: { - storySort: ([, a], [, b]) => a.kind.localeCompare(b.kind), - }, -}); diff --git a/packages/fuselage-ui-kit/CHANGELOG.md b/packages/fuselage-ui-kit/CHANGELOG.md deleted file mode 100644 index 9b5733937e..0000000000 --- a/packages/fuselage-ui-kit/CHANGELOG.md +++ /dev/null @@ -1,322 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# [0.31.0](https://github.com/RocketChat/fuselage/compare/v0.30.1...v0.31.0) (2021-12-28) - -### Bug Fixes - -- **fuselage-ui-kit:** Initial Value being ignored on text input fields upon modal update ([#600](https://github.com/RocketChat/fuselage/issues/600)) ([d9bd704](https://github.com/RocketChat/fuselage/commit/d9bd704848eb9f2d9dd8b45f33cb8992ea39f9e2)) -- docker image version ([8f181cf](https://github.com/RocketChat/fuselage/commit/8f181cf5a96084d7abd9ea94efd46cc50840c798)) - -### Features - -- Message Preview ([#587](https://github.com/RocketChat/fuselage/issues/587)) ([e69dad3](https://github.com/RocketChat/fuselage/commit/e69dad3a6619e98ff70bcd1cb68567a159187336)) -- **fuselage:** Replace typography of Message's user name and Banner's title ([#577](https://github.com/RocketChat/fuselage/issues/577)) ([6af2dba](https://github.com/RocketChat/fuselage/commit/6af2dbabc90d2e2f1598cbbd113ecc3ea82adfc0)) -- New hooks for element size tracking ([#413](https://github.com/RocketChat/fuselage/issues/413)) ([8ca682c](https://github.com/RocketChat/fuselage/commit/8ca682c636d2e4813f7d346cb881513382be63cf)) -- **fuselage:** Message preview component ([#553](https://github.com/RocketChat/fuselage/issues/553)) ([f8bd0ad](https://github.com/RocketChat/fuselage/commit/f8bd0ad637c0d2edad47b3c2384dac9c84e8d4fd)) - -## [0.30.1](https://github.com/RocketChat/fuselage/compare/v0.30.0...v0.30.1) (2021-10-20) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.30.0](https://github.com/RocketChat/fuselage/compare/v0.29.0...v0.30.0) (2021-10-06) - -### Bug Fixes - -- **fuselage:** Remove Field margin ([#543](https://github.com/RocketChat/fuselage/issues/543)) ([0cc10e1](https://github.com/RocketChat/fuselage/commit/0cc10e1b86bcf14a9ae590537a3d8e460b39b167)) - -# [0.29.0](https://github.com/RocketChat/fuselage/compare/v0.28.0...v0.29.0) (2021-08-31) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.28.0](https://github.com/RocketChat/fuselage/compare/v0.27.0...v0.28.0) (2021-07-30) - -### Bug Fixes - -- **fuselage-ui-kit:** Implements missing url prop for buttons ([#488](https://github.com/RocketChat/fuselage/issues/488)) ([bb19344](https://github.com/RocketChat/fuselage/commit/bb193441804c9b20174e2586d22c4b2845a486c3)) - -### Features - -- **onboarding-ui:** Administrator information form and Organization information form ([#489](https://github.com/RocketChat/fuselage/issues/489)) ([b289f68](https://github.com/RocketChat/fuselage/commit/b289f68676954b91c792d8d97680314178bf2c60)) -- styled API; monorepo grooming ([#482](https://github.com/RocketChat/fuselage/issues/482)) ([1b6b70c](https://github.com/RocketChat/fuselage/commit/1b6b70cf67ec16927b1566adc2350295a8927223)) - -# [0.27.0](https://github.com/RocketChat/fuselage/compare/v0.26.0...v0.27.0) (2021-06-28) - -### Features - -- ui-kit-unified ([#392](https://github.com/RocketChat/fuselage/issues/392)) ([ce48ca9](https://github.com/RocketChat/fuselage/commit/ce48ca9d9806283bba8be5df7c29c7aa8c1e716f)) - -# [0.26.0](https://github.com/RocketChat/fuselage/compare/v0.25.0...v0.26.0) (2021-05-28) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.25.0](https://github.com/RocketChat/fuselage/compare/v0.24.0...v0.25.0) (2021-05-19) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.24.0](https://github.com/RocketChat/fuselage/compare/v0.23.0...v0.24.0) (2021-04-28) - -### Features - -- [@rocket](https://github.com/rocket).chat/string-helpers ([#431](https://github.com/RocketChat/fuselage/issues/431)) ([2509d6a](https://github.com/RocketChat/fuselage/commit/2509d6acdbe5ec8b216e8d4430373797c5f5dfe2)) - -# [0.23.0](https://github.com/RocketChat/fuselage/compare/v0.22.0...v0.23.0) (2021-04-01) - -### Bug Fixes - -- **npm:** Wrong paths in "files" field of package.json ([6d3c811](https://github.com/RocketChat/fuselage/commit/6d3c811f6fd747de7f47aff145902d88476272ee)) - -### Features - -- New icons ([#407](https://github.com/RocketChat/fuselage/issues/407)) ([9e708b4](https://github.com/RocketChat/fuselage/commit/9e708b42a0a3003669e1c5e76dce84b8ef563e21)) - -# [0.22.0](https://github.com/RocketChat/fuselage/compare/v0.21.0...v0.22.0) (2021-02-26) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.21.0](https://github.com/RocketChat/fuselage/compare/v0.20.3...v0.21.0) (2021-01-31) - -### Bug Fixes - -- Banner surface adjustments ([#362](https://github.com/RocketChat/fuselage/issues/362)) ([2d59b7c](https://github.com/RocketChat/fuselage/commit/2d59b7c41962f24aa13face91a9b9f0ea8f1718a)) -- Pass appId and blockId from blocks to elements ([#366](https://github.com/RocketChat/fuselage/issues/366)) ([8a1b552](https://github.com/RocketChat/fuselage/commit/8a1b552f8dbc3a9b321d888f4a7e9dc9af2922cf)) - -### Features - -- Built modules for design tokens ([#356](https://github.com/RocketChat/fuselage/issues/356)) ([f9c3449](https://github.com/RocketChat/fuselage/commit/f9c344953b8161a4385cab3a3dcc8b6a7210446f)) -- linear_scale element ([#365](https://github.com/RocketChat/fuselage/issues/365)) ([43a4c54](https://github.com/RocketChat/fuselage/commit/43a4c54ed10d096ef2259ddcd30c3bbd97ae866a)) - -## [0.20.3](https://github.com/RocketChat/fuselage/compare/v0.20.2...v0.20.3) (2021-01-29) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -## [0.20.2](https://github.com/RocketChat/fuselage/compare/v0.20.1...v0.20.2) (2021-01-27) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -## [0.20.1](https://github.com/RocketChat/fuselage/compare/v0.20.0...v0.20.1) (2020-12-22) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.20.0](https://github.com/RocketChat/fuselage/compare/v0.19.0...v0.20.0) (2020-12-21) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.19.0](https://github.com/RocketChat/fuselage/compare/v0.18.0...v0.19.0) (2020-11-28) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.18.0](https://github.com/RocketChat/fuselage/compare/v0.17.3...v0.18.0) (2020-11-16) - -### Bug Fixes - -- Set a conservative output.environment on Webpack bundles ([#330](https://github.com/RocketChat/fuselage/issues/330)) ([62bf728](https://github.com/RocketChat/fuselage/commit/62bf728d3541d8d7ee72420347f2351359fb5df7)) - -## [0.17.3](https://github.com/RocketChat/fuselage/compare/v0.17.2...v0.17.3) (2020-11-16) - -### Bug Fixes - -- Set a conservative output.environment on Webpack bundles ([#330](https://github.com/RocketChat/fuselage/issues/330)) ([85d4a3a](https://github.com/RocketChat/fuselage/commit/85d4a3a5fd6881b07e97fb690d31baef405cfa69)) - -## [0.17.2](https://github.com/RocketChat/fuselage/compare/v0.17.1...v0.17.2) (2020-10-28) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -## [0.17.1](https://github.com/RocketChat/fuselage/compare/v0.17.0...v0.17.1) (2020-10-26) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.17.0](https://github.com/RocketChat/fuselage/compare/v0.16.0...v0.17.0) (2020-10-25) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.16.0](https://github.com/RocketChat/fuselage/compare/v0.15.1...v0.16.0) (2020-09-30) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -## [0.15.1](https://github.com/RocketChat/fuselage/compare/v0.15.0...v0.15.1) (2020-09-22) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.15.0](https://github.com/RocketChat/fuselage/compare/v0.14.1...v0.15.0) (2020-09-17) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -## [0.14.1](https://github.com/RocketChat/fuselage/compare/v0.14.0...v0.14.1) (2020-08-22) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.14.0](https://github.com/RocketChat/fuselage/compare/v0.13.2...v0.14.0) (2020-08-18) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -## [0.13.2](https://github.com/RocketChat/fuselage/compare/v0.13.1...v0.13.2) (2020-07-24) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -## [0.13.1](https://github.com/RocketChat/fuselage/compare/v0.13.0...v0.13.1) (2020-07-17) - -### Bug Fixes - -- Select mutations and ui-kit alerts ([#263](https://github.com/RocketChat/fuselage/issues/263)) ([661398d](https://github.com/RocketChat/fuselage/commit/661398dfdeaf827dadc46d24a7382d69f43f9742)) - -# [0.13.0](https://github.com/RocketChat/fuselage/compare/v0.12.0...v0.13.0) (2020-07-14) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.12.0](https://github.com/RocketChat/fuselage/compare/v0.11.0...v0.12.0) (2020-07-14) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.11.0](https://github.com/RocketChat/fuselage/compare/v0.10.0...v0.11.0) (2020-07-11) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.10.0](https://github.com/RocketChat/fuselage/compare/v0.9.0...v0.10.0) (2020-06-20) - -### Bug Fixes - -- Missing legacy icons ([#238](https://github.com/RocketChat/fuselage/issues/238)) ([1d74390](https://github.com/RocketChat/fuselage/commit/1d74390)) - -# [0.9.0](https://github.com/RocketChat/fuselage/compare/v0.8.0...v0.9.0) (2020-05-21) - -### Bug Fixes - -- Deprecation warnings ([#227](https://github.com/RocketChat/fuselage/issues/227)) ([46322a8](https://github.com/RocketChat/fuselage/commit/46322a8e2781b4293adc7fdf4fcffae20f0d170f)) - -### Features - -- New Box props ([#213](https://github.com/RocketChat/fuselage/issues/213)) ([b593875](https://github.com/RocketChat/fuselage/commit/b593875f3561e334412f9d7e2fbe81007ed8098e)) - -# [0.8.0](https://github.com/RocketChat/fuselage/compare/v0.7.1...v0.8.0) (2020-04-22) - -### Features - -- Refactor static styles ([#201](https://github.com/RocketChat/fuselage/issues/201)) ([e1bdc65](https://github.com/RocketChat/fuselage/commit/e1bdc655a0dfe0f88261d3fab84c2e42c2ad581e)) - -## [0.7.1](https://github.com/RocketChat/fuselage/compare/v0.7.0...v0.7.1) (2020-04-01) - -### Bug Fixes - -- Fix ui kit babel config ([#191](https://github.com/RocketChat/fuselage/issues/191)) ([47295d8](https://github.com/RocketChat/fuselage/commit/47295d87740bc0ceb5e2bbb1901c2340cab860b6)) - -# [0.7.0](https://github.com/RocketChat/fuselage/compare/v0.6.2...v0.7.0) (2020-04-01) - -### Bug Fixes - -- Production build of [@rocket](https://github.com/rocket).chat/fuselage-ui-kit ([#190](https://github.com/RocketChat/fuselage/issues/190)) ([15a975a](https://github.com/RocketChat/fuselage/commit/15a975ab58688b29bfa93cd110bdc3d7e10de4ef)) - -### Features - -- Custom prop types and more props to Box ([#181](https://github.com/RocketChat/fuselage/issues/181)) ([119e815](https://github.com/RocketChat/fuselage/commit/119e815ac9a0b85c1649c53bab62390b25aae4b3)) - -## [0.6.2](https://github.com/RocketChat/fuselage/compare/v0.6.1...v0.6.2) (2020-03-31) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -## [0.6.1](https://github.com/RocketChat/fuselage/compare/v0.6.0...v0.6.1) (2020-03-24) - -### Bug Fixes - -- Spacing and markup in Ui Kit inputs ([#176](https://github.com/RocketChat/fuselage/issues/176)) ([e6df266](https://github.com/RocketChat/fuselage/commit/e6df266c1297e75c9de21149e98d23c919f76f79)) - -# [0.6.0](https://github.com/RocketChat/fuselage/compare/v0.5.0...v0.6.0) (2020-03-20) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.5.0](https://github.com/RocketChat/fuselage/compare/v0.4.1...v0.5.0) (2020-03-20) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -## [0.4.1](https://github.com/RocketChat/fuselage/compare/v0.4.0...v0.4.1) (2020-03-16) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.4.0](https://github.com/RocketChat/fuselage/compare/v0.3.0...v0.4.0) (2020-03-10) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.3.0](https://github.com/RocketChat/fuselage/compare/v0.2.0...v0.3.0) (2020-02-17) - -### Bug Fixes - -- actionId on action hooks ([#149](https://github.com/RocketChat/fuselage/issues/149)) ([c305eb2](https://github.com/RocketChat/fuselage/commit/c305eb2171463589ed24460d05d648daf984d267)) - -# [0.2.0](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.30...v0.2.0) (2020-02-13) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.2.0-alpha.30](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.29...v0.2.0-alpha.30) (2020-02-12) - -### Bug Fixes - -- Safari not triggering buttons anchors ([#146](https://github.com/RocketChat/fuselage/issues/146)) ([2cb5aaa](https://github.com/RocketChat/fuselage/commit/2cb5aaaf4c71e277a0a8ea556a8b9efbb3e58620)) - -# [0.2.0-alpha.29](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.28...v0.2.0-alpha.29) (2020-02-10) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.2.0-alpha.28](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.27...v0.2.0-alpha.28) (2020-02-10) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.2.0-alpha.27](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.26...v0.2.0-alpha.27) (2020-02-10) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.2.0-alpha.26](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.25...v0.2.0-alpha.26) (2020-02-10) - -### Bug Fixes - -- Select autofocus ([#141](https://github.com/RocketChat/fuselage/issues/141)) ([25849ee](https://github.com/RocketChat/fuselage/commit/25849eed55e4edbf26f54ae4a72c71c1d528e850)) - -# [0.2.0-alpha.25](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.24...v0.2.0-alpha.25) (2020-02-10) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.2.0-alpha.24](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.23...v0.2.0-alpha.24) (2020-02-09) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.2.0-alpha.23](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.22...v0.2.0-alpha.23) (2020-02-07) - -### Bug Fixes - -- Section with Overflow visibility ([#138](https://github.com/RocketChat/fuselage/issues/138)) ([d0da3cd](https://github.com/RocketChat/fuselage/commit/d0da3cd7db31880c812519b7799f04555776167f)) - -# [0.2.0-alpha.22](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.21...v0.2.0-alpha.22) (2020-02-07) - -### Bug Fixes - -- Select/MultiSelect focus submitting form ([#134](https://github.com/RocketChat/fuselage/issues/134)) ([630e622](https://github.com/RocketChat/fuselage/commit/630e622d8535fcaa06a8a736c6e0d273b450b739)) -- UIKit stopPropagation ([#137](https://github.com/RocketChat/fuselage/issues/137)) ([86939ea](https://github.com/RocketChat/fuselage/commit/86939eaae182d6554879468745780d83bc977b9e)) -- uikit using react components ([#135](https://github.com/RocketChat/fuselage/issues/135)) ([52fcedb](https://github.com/RocketChat/fuselage/commit/52fcedb0efbc33ad4240c83fd92a325fcc9f0f4c)) - -# [0.2.0-alpha.21](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.20...v0.2.0-alpha.21) (2020-02-05) - -**Note:** Version bump only for package @rocket.chat/fuselage-ui-kit - -# [0.2.0-alpha.20](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.19...v0.2.0-alpha.20) (2020-02-01) - -### Bug Fixes - -- ui-kit margins warnings and unique ids ([#130](https://github.com/RocketChat/fuselage/issues/130)) ([cdaa358](https://github.com/RocketChat/fuselage/commit/cdaa3580516aa652d7a318c6d076065954a3e289)) - -### Features - -- ui-kit initial value ([#131](https://github.com/RocketChat/fuselage/issues/131)) ([3c6cab4](https://github.com/RocketChat/fuselage/commit/3c6cab4bd7d6ba459841f6f847ef42229b097294)) -- UiKit error states ([#133](https://github.com/RocketChat/fuselage/issues/133)) ([d6b3842](https://github.com/RocketChat/fuselage/commit/d6b38429597963e1e437189c64a7e2be5e8715c0)) - -# [0.2.0-alpha.19](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.18...v0.2.0-alpha.19) (2020-01-13) - -### Features - -- Position, Modal, Options components ([#116](https://github.com/RocketChat/fuselage/issues/116)) ([af1916a](https://github.com/RocketChat/fuselage/commit/af1916a22c677939adda04fe417dafe406292762)), closes [#117](https://github.com/RocketChat/fuselage/issues/117) - -# [0.2.0-alpha.18](https://github.com/RocketChat/fuselage/compare/v0.2.0-alpha.17...v0.2.0-alpha.18) (2019-12-31) - -### Features - -- UI Kit ([#95](https://github.com/RocketChat/fuselage/issues/95)) ([6d4162b](https://github.com/RocketChat/fuselage/commit/6d4162bb8c121b1e89f8c818e7106bce49f09c27)), closes [#94](https://github.com/RocketChat/fuselage/issues/94) [#109](https://github.com/RocketChat/fuselage/issues/109) [#108](https://github.com/RocketChat/fuselage/issues/108) [#111](https://github.com/RocketChat/fuselage/issues/111) diff --git a/packages/fuselage-ui-kit/README.md b/packages/fuselage-ui-kit/README.md deleted file mode 100644 index fc5243aae4..0000000000 --- a/packages/fuselage-ui-kit/README.md +++ /dev/null @@ -1,102 +0,0 @@ - - -

- - Rocket.Chat - -

- -# `@rocket.chat/fuselage-ui-kit` - -> UiKit elements for Rocket.Chat Apps built under Fuselage design system - ---- - -[![npm@latest](https://img.shields.io/npm/v/@rocket.chat/fuselage-ui-kit/latest?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-ui-kit/v/latest) [![npm@next](https://img.shields.io/npm/v/@rocket.chat/fuselage-ui-kit/next?style=flat-square)](https://www.npmjs.com/package/@rocket.chat/fuselage-ui-kit/v/next) ![react version](https://img.shields.io/npm/dependency-version/@rocket.chat/fuselage-ui-kit/peer/react?style=flat-square) [![Storybook](https://cdn.jsdelivr.net/gh/storybookjs/brand@master/badge/badge-storybook.svg)](https://rocketchat.github.io/fuselage/fuselage-ui-kit) ![npm downloads](https://img.shields.io/npm/dw/@rocket.chat/fuselage-ui-kit?style=flat-square) ![License: MIT](https://img.shields.io/npm/l/@rocket.chat/fuselage-ui-kit?style=flat-square) - -![deps](https://img.shields.io/librariesio/release/npm/@rocket.chat/fuselage-ui-kit?style=flat-square) ![npm bundle size](https://img.shields.io/bundlephobia/min/@rocket.chat/fuselage-ui-kit?style=flat-square) - - - -## Install - - - -Firstly, install the peer dependencies (prerequisites): - -```sh -npm i @rocket.chat/fuselage @rocket.chat/fuselage-hooks @rocket.chat/fuselage-polyfills @rocket.chat/icons @rocket.chat/styled react react-dom - -# or, if you are using yarn: - -yarn add @rocket.chat/fuselage @rocket.chat/fuselage-hooks @rocket.chat/fuselage-polyfills @rocket.chat/icons @rocket.chat/styled react react-dom -``` - -Add `@rocket.chat/fuselage-ui-kit` as a dependency: - -```sh -npm i @rocket.chat/fuselage-ui-kit - -# or, if you are using yarn: - -yarn add @rocket.chat/fuselage-ui-kit -``` - - - -## Contributing - - - -Contributions, issues, and feature requests are welcome!
-Feel free to check the [issues](https://github.com/RocketChat/fuselage/issues). - - - -### Building - -As this package dependends on others in this monorepo, before anything run the following at the root directory: - - - -```sh -yarn build -``` - - - -### Linting - -To ensure the source is matching our coding style, we perform [linting](). -Before commiting, check if your code fits our style by running: - - - -```sh -yarn lint -``` - - - -Some linter warnings and errors can be automatically fixed: - - - -```sh -yarn lint-and-fix -``` - - - -### Component stories - -We develop and describe our visual components in the form of stories, manage by a tool called [Storybook](https://storybook.js.org/). -To start developing with Storybook, run: - - - -```sh -yarn storybook -``` - - diff --git a/packages/fuselage-ui-kit/package.json b/packages/fuselage-ui-kit/package.json deleted file mode 100644 index 2b8bf80d57..0000000000 --- a/packages/fuselage-ui-kit/package.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "name": "@rocket.chat/fuselage-ui-kit", - "version": "0.31.22", - "description": "UiKit elements for Rocket.Chat Apps built under Fuselage design system", - "homepage": "https://rocketchat.github.io/Rocket.Chat.Fuselage/", - "author": { - "name": "Rocket.Chat", - "url": "https://rocket.chat/" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/RocketChat/fuselage.git", - "directory": "packages/fuselage-ui-kit" - }, - "bugs": { - "url": "https://github.com/RocketChat/fuselage/issues" - }, - "main": "dist/cjs/index.js", - "module": "dist/esm/index.js", - "types": "dist/esm/index.d.ts", - "files": [ - "/dist" - ], - "publishConfig": { - "access": "public" - }, - "scripts": { - "build": "run-s .:build:clean .:build:esm .:build:cjs", - ".:build:clean": "rimraf dist", - ".:build:esm": "tsc -p tsconfig-esm.json", - ".:build:cjs": "tsc -p tsconfig-cjs.json", - "lint": "lint", - "lint-and-fix": "lint-and-fix", - "lint-staged": "lint-staged", - "docs": "cross-env NODE_ENV=production build-storybook -o ../../static/fuselage-ui-kit", - "storybook": "start-storybook -p 6006", - "build-storybook": "cross-env NODE_ENV=production build-storybook", - "bump-next": "bump-next" - }, - "peerDependencies": { - "@rocket.chat/fuselage": "*", - "@rocket.chat/fuselage-hooks": "*", - "@rocket.chat/fuselage-polyfills": "*", - "@rocket.chat/icons": "*", - "@rocket.chat/styled": "*", - "react": "^17.0.2", - "react-dom": "^17.0.2" - }, - "devDependencies": { - "@rocket.chat/apps-engine": "~1.30.0", - "@rocket.chat/eslint-config-alt": "workspace:~", - "@rocket.chat/fuselage": "workspace:~", - "@rocket.chat/fuselage-hooks": "workspace:~", - "@rocket.chat/fuselage-polyfills": "workspace:~", - "@rocket.chat/icons": "workspace:~", - "@rocket.chat/prettier-config": "workspace:~", - "@rocket.chat/styled": "workspace:~", - "@storybook/addon-essentials": "~6.5.15", - "@storybook/addons": "~6.5.15", - "@storybook/builder-webpack5": "~6.5.15", - "@storybook/manager-webpack5": "~6.5.15", - "@storybook/react": "~6.5.15", - "@storybook/source-loader": "~6.5.15", - "@storybook/theming": "~6.5.15", - "@types/react": "~17.0.53", - "@types/react-dom": "^17.0.18", - "babel-loader": "~8.2.5", - "bump": "workspace:~", - "cross-env": "^7.0.3", - "eslint": "~8.26.0", - "lint-all": "workspace:~", - "lint-staged": "~12.3.8", - "normalize.css": "^8.0.1", - "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "rimraf": "^3.0.2", - "typescript": "~4.9.4", - "webpack": "~5.76.0", - "write-version-module": "workspace:~" - }, - "dependencies": { - "@rocket.chat/ui-kit": "workspace:~", - "tslib": "^2.3.1" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/packages/fuselage-ui-kit/src/blocks/ActionsBlock.Action.tsx b/packages/fuselage-ui-kit/src/blocks/ActionsBlock.Action.tsx deleted file mode 100644 index fddca585db..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/ActionsBlock.Action.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React from 'react'; - -type ActionProps = { - element: UiKit.ActionsBlock['elements'][number]; - parser: UiKit.SurfaceRenderer; - index: number; -}; - -const Action = ({ - element, - parser, - index, -}: ActionProps): ReactElement | null => { - const renderedElement = parser.renderActionsBlockElement(element, index); - - if (!renderedElement) { - return null; - } - - return ( - - {renderedElement} - - ); -}; - -export default Action; diff --git a/packages/fuselage-ui-kit/src/blocks/ActionsBlock.tsx b/packages/fuselage-ui-kit/src/blocks/ActionsBlock.tsx deleted file mode 100644 index c750935fe3..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/ActionsBlock.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Box, Button } from '@rocket.chat/fuselage'; -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo, useCallback, useMemo, useState } from 'react'; - -import { useSurfaceType } from '../contexts/SurfaceContext'; -import type { BlockProps } from '../utils/BlockProps'; -import Action from './ActionsBlock.Action'; - -type ActionsBlockProps = BlockProps; - -const ActionsBlock = ({ - className, - block, - surfaceRenderer, -}: ActionsBlockProps): ReactElement => { - const surfaceType = useSurfaceType(); - - const [showMoreVisible, setShowMoreVisible] = useState( - () => block.elements.length > 5 && surfaceType !== 'banner' - ); - - const handleShowMoreClick = useCallback(() => { - setShowMoreVisible(false); - }, []); - - const actionElements = useMemo( - () => - (showMoreVisible ? block.elements.slice(0, 5) : block.elements).map( - (element) => ({ - ...element, - appId: element.appId ?? block.appId, - blockId: element.blockId ?? block.blockId, - }) - ), - [block.appId, block.blockId, block.elements, showMoreVisible] - ); - - return ( - - {actionElements.map((element, i) => ( - - ))} - {showMoreVisible && ( - - - - )} - - ); -}; - -export default memo(ActionsBlock); diff --git a/packages/fuselage-ui-kit/src/blocks/ContextBlock.Item.tsx b/packages/fuselage-ui-kit/src/blocks/ContextBlock.Item.tsx deleted file mode 100644 index 005c939e9f..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/ContextBlock.Item.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React from 'react'; - -type ItemProps = { - block: UiKit.ContextBlock['elements'][number]; - surfaceRenderer: UiKit.SurfaceRenderer; - index: number; -}; - -const Item = ({ - block: element, - surfaceRenderer: parser, - index, -}: ItemProps): ReactElement | null => { - const renderedElement = parser.renderContextBlockElement(element, index); - - if (!renderedElement) { - return null; - } - - switch (element.type) { - case UiKit.TextObjectType.PLAIN_TEXT: - case UiKit.TextObjectType.MARKDOWN: - return ( - - {renderedElement} - - ); - - default: - return renderedElement; - } -}; - -export default Item; diff --git a/packages/fuselage-ui-kit/src/blocks/ContextBlock.tsx b/packages/fuselage-ui-kit/src/blocks/ContextBlock.tsx deleted file mode 100644 index cce7fdcd62..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/ContextBlock.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo, useMemo } from 'react'; - -import type { BlockProps } from '../utils/BlockProps'; -import Item from './ContextBlock.Item'; - -type ContextBlockProps = BlockProps; - -const ContextBlock = ({ - className, - block, - surfaceRenderer, -}: ContextBlockProps): ReactElement => { - const itemElements = useMemo( - () => - block.elements.map((element) => ({ - ...element, - appId: block.appId, - blockId: block.blockId, - })), - [block.appId, block.blockId, block.elements] - ); - - return ( - - {itemElements.map((element, i) => ( - - ))} - - ); -}; - -export default memo(ContextBlock); diff --git a/packages/fuselage-ui-kit/src/blocks/DividerBlock.tsx b/packages/fuselage-ui-kit/src/blocks/DividerBlock.tsx deleted file mode 100644 index 8134070e47..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/DividerBlock.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Divider } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo } from 'react'; - -import type { BlockProps } from '../utils/BlockProps'; - -type DividerBlockProps = BlockProps; - -const DividerBlock = ({ className }: DividerBlockProps): ReactElement => ( - -); - -export default memo(DividerBlock); diff --git a/packages/fuselage-ui-kit/src/blocks/ImageBlock.styles.tsx b/packages/fuselage-ui-kit/src/blocks/ImageBlock.styles.tsx deleted file mode 100644 index 060b15ee91..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/ImageBlock.styles.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import styled from '@rocket.chat/styled'; - -const filterImageProps = ({ - imageUrl: _imageUrl, - width: _width, - height: _height, - ...props -}: { - imageUrl: string; - width: number; - height: number; -}) => props; - -export const Image = styled('div', filterImageProps)` - box-shadow: 0 0 0px 1px rgba(204, 204, 204, 38%); - background-repeat: no-repeat; - background-position: 50%; - background-size: cover; - background-color: rgba(204, 204, 204, 38%); - background-image: url(${(props) => props.imageUrl}); - width: ${(props) => String(props.width)}px; - height: ${(props) => String(props.height)}px; - overflow: hidden; -`; diff --git a/packages/fuselage-ui-kit/src/blocks/ImageBlock.tsx b/packages/fuselage-ui-kit/src/blocks/ImageBlock.tsx deleted file mode 100644 index 1c615ba2f1..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/ImageBlock.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { Box, Skeleton } from '@rocket.chat/fuselage'; -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo, useEffect, useState } from 'react'; - -import { useSurfaceType } from '../contexts/SurfaceContext'; -import type { BlockProps } from '../utils/BlockProps'; -import { Image } from './ImageBlock.styles'; - -const maxSize = 360; - -const fetchImageState = (img: HTMLImageElement) => { - if (!img.complete) { - return { - loading: true, - width: maxSize, - height: (maxSize * 9) / 21, - }; - } - - const { naturalWidth, naturalHeight } = img; - - const scaleRatio = - naturalWidth > naturalHeight - ? Math.min(naturalWidth, maxSize) / naturalWidth - : Math.min(naturalHeight, maxSize) / naturalHeight; - - return { - loading: false, - width: naturalWidth * scaleRatio, - height: naturalHeight * scaleRatio, - }; -}; - -type ImageBlockProps = BlockProps; - -const ImageBlock = ({ - className, - block, - surfaceRenderer, -}: ImageBlockProps): ReactElement => { - const surface = useSurfaceType(); - - const alignment = - surface === 'banner' || surface === 'message' ? 'flex-start' : 'center'; - - const [{ loading, width, height }, setState] = useState(() => { - const img = document.createElement('img'); - img.src = block.imageUrl; - return fetchImageState(img); - }); - - useEffect(() => { - const img = document.createElement('img'); - - const handleLoad = () => { - setState(fetchImageState(img)); - }; - - img.addEventListener('load', handleLoad); - img.src = block.imageUrl; - - if (img.complete) { - img.removeEventListener('load', handleLoad); - setState(fetchImageState(img)); - } - - return () => { - img.removeEventListener('load', handleLoad); - }; - }, [block.imageUrl]); - - return ( - - - {block.title && ( - - {surfaceRenderer.renderTextObject( - block.title, - 0, - UiKit.BlockContext.NONE - )} - - )} - {loading ? ( - - ) : ( - - )} - - - ); -}; - -export default memo(ImageBlock); diff --git a/packages/fuselage-ui-kit/src/blocks/InputBlock.tsx b/packages/fuselage-ui-kit/src/blocks/InputBlock.tsx deleted file mode 100644 index 11331e020f..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/InputBlock.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Field } from '@rocket.chat/fuselage'; -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo, useMemo } from 'react'; - -import { useUiKitState } from '../hooks/useUiKitState'; -import type { BlockProps } from '../utils/BlockProps'; - -type InputBlockProps = BlockProps; - -const InputBlock = ({ - className, - block, - surfaceRenderer, - context, -}: InputBlockProps): ReactElement => { - const inputElement = useMemo( - () => ({ - ...block.element, - appId: block.element.appId ?? block.appId, - blockId: block.element.blockId ?? block.blockId, - }), - [block.element, block.appId, block.blockId] - ); - - const [{ error }] = useUiKitState(inputElement, context); - - return ( - - {block.label && ( - - {surfaceRenderer.renderTextObject( - block.label, - 0, - UiKit.BlockContext.NONE - )} - - )} - - {surfaceRenderer.renderInputBlockElement(inputElement, 0)} - - {error && {error}} - {block.hint && {block.hint}} - - ); -}; - -export default memo(InputBlock); diff --git a/packages/fuselage-ui-kit/src/blocks/PreviewBlock.tsx b/packages/fuselage-ui-kit/src/blocks/PreviewBlock.tsx deleted file mode 100644 index 1eca469e43..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/PreviewBlock.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { - MessageGenericPreview, - MessageGenericPreviewContent, - MessageGenericPreviewDescription, - MessageGenericPreviewImage, - MessageGenericPreviewTitle, - MessageGenericPreviewFooter, - MessageGenericPreviewThumb, - Box, -} from '@rocket.chat/fuselage'; -import * as UiKit from '@rocket.chat/ui-kit'; -import { - isPreviewBlockWithThumb, - isPreviewBlockWithPreview, -} from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo } from 'react'; - -import type { BlockProps } from '../utils/BlockProps'; -import ContextBlock from './ContextBlock'; - -type PreviewBlockProps = BlockProps; - -const PreviewBlock = ({ - block, - surfaceRenderer, -}: PreviewBlockProps): ReactElement => ( - - - {isPreviewBlockWithPreview(block) && block.preview?.dimensions && ( - - )} - - - - ) : undefined - } - > - {Array.isArray(block.title) ? ( - - {block.title.map((title) => - surfaceRenderer.renderTextObject( - title, - 0, - UiKit.BlockContext.NONE - ) - )} - - ) : null} - {Array.isArray(block.description) ? ( - - {block.description.map((description) => - surfaceRenderer.renderTextObject( - description, - 0, - UiKit.BlockContext.NONE - ) - )} - - ) : null} - {block.footer && ( - - - - )} - - - -); - -export default memo(PreviewBlock); diff --git a/packages/fuselage-ui-kit/src/blocks/SectionBlock.Fields.tsx b/packages/fuselage-ui-kit/src/blocks/SectionBlock.Fields.tsx deleted file mode 100644 index 00be3f42e3..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/SectionBlock.Fields.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Grid } from '@rocket.chat/fuselage'; -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React from 'react'; - -const breakpoints = { - xs: 4, - sm: 4, - md: 4, - lg: 6, - xl: 6, -} as const; - -type FieldsProps = { - fields: readonly UiKit.TextObject[]; - surfaceRenderer: UiKit.SurfaceRenderer; -}; - -const Fields = ({ fields, surfaceRenderer }: FieldsProps): ReactElement => ( - - {fields.map((field, i) => ( - - {surfaceRenderer.renderTextObject(field, 0, UiKit.BlockContext.NONE)} - - ))} - -); - -export default Fields; diff --git a/packages/fuselage-ui-kit/src/blocks/SectionBlock.tsx b/packages/fuselage-ui-kit/src/blocks/SectionBlock.tsx deleted file mode 100644 index 773fb97d1f..0000000000 --- a/packages/fuselage-ui-kit/src/blocks/SectionBlock.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Box, Flex, Grid } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo, useMemo } from 'react'; - -import type { BlockProps } from '../utils/BlockProps'; -import Fields from './SectionBlock.Fields'; - -type SectionBlockProps = BlockProps; - -const SectionBlock = ({ - className, - block, - surfaceRenderer, -}: SectionBlockProps): ReactElement => { - const { text, fields } = block; - - const accessoryElement = useMemo( - () => - block.accessory - ? { - appId: block.appId, - blockId: block.blockId, - ...block.accessory, - } - : undefined, - [block.appId, block.blockId, block.accessory] - ); - - return ( - - - {text && ( - - {surfaceRenderer.text(text)} - - )} - {fields && } - - {block.accessory && ( - - - {accessoryElement - ? surfaceRenderer.renderSectionAccessoryBlockElement( - accessoryElement, - 0 - ) - : null} - - - )} - - ); -}; - -export default memo(SectionBlock); diff --git a/packages/fuselage-ui-kit/src/contexts/SurfaceContext.ts b/packages/fuselage-ui-kit/src/contexts/SurfaceContext.ts deleted file mode 100644 index 62bd57bd4b..0000000000 --- a/packages/fuselage-ui-kit/src/contexts/SurfaceContext.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createContext, useContext } from 'react'; - -type SurfaceContextValue = 'attachment' | 'banner' | 'message' | 'modal'; - -export const SurfaceContext = createContext('message'); - -export const useSurfaceType = (): SurfaceContextValue => - useContext(SurfaceContext); diff --git a/packages/fuselage-ui-kit/src/contexts/kitContext.ts b/packages/fuselage-ui-kit/src/contexts/kitContext.ts deleted file mode 100644 index b0cb4141b1..0000000000 --- a/packages/fuselage-ui-kit/src/contexts/kitContext.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { InputElementDispatchAction } from '@rocket.chat/ui-kit'; -import { createContext, useContext } from 'react'; - -type ActionParams = { - blockId: string; - appId: string; - actionId: string; - value: unknown; - viewId?: string; - dispatchActionConfig?: InputElementDispatchAction[]; -}; - -type UiKitContext = { - action: ( - state: ActionParams, - event: Parameters>[0] - ) => Promise | void; - state: ( - state: ActionParams, - event: Parameters>[0] - ) => Promise | void; - appId: string; - errors?: Record; - values: Record; - viewId?: string; -}; - -export const defaultContext = { - action: console.log, - state: console.log, - appId: 'core', - errors: {}, - values: {}, -}; - -export const kitContext = createContext(defaultContext); - -export const useUiKitContext = () => useContext(kitContext); - -export const useUiKitStateValue = ( - actionId: string, - initialValue: T -): { - value: T; - error: string | undefined; -} => { - const { values, errors } = useUiKitContext(); - - return { - value: (values && (values[actionId]?.value as T)) ?? initialValue, - error: errors && errors[actionId], - }; -}; diff --git a/packages/fuselage-ui-kit/src/elements/ButtonElement.tsx b/packages/fuselage-ui-kit/src/elements/ButtonElement.tsx deleted file mode 100644 index cfb620aff2..0000000000 --- a/packages/fuselage-ui-kit/src/elements/ButtonElement.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Button, Throbber } from '@rocket.chat/fuselage'; -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React from 'react'; - -import { useUiKitState } from '../hooks/useUiKitState'; -import type { BlockProps } from '../utils/BlockProps'; - -type ButtonElementProps = BlockProps; - -const ButtonElement = ({ - block, - context, - surfaceRenderer, -}: ButtonElementProps): ReactElement => { - const [{ loading }, action] = useUiKitState(block, context); - - if (block.url) { - return ( - - ); - } - - return ( - - ); -}; - -export default ButtonElement; diff --git a/packages/fuselage-ui-kit/src/elements/ContextElement/ContextElement.tsx b/packages/fuselage-ui-kit/src/elements/ContextElement/ContextElement.tsx deleted file mode 100644 index 898a3abb9e..0000000000 --- a/packages/fuselage-ui-kit/src/elements/ContextElement/ContextElement.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { FC } from 'react'; -import React from 'react'; - -import type { BlockProps } from '../../utils/BlockProps'; -import { ContextElementItem } from './ContextElementItem'; - -type ContextElementProps = BlockProps; - -export const ContextElement: FC = ({ - block, - surfaceRenderer, - className, -}) => ( - - {block.elements.map((element, i) => ( - - ))} - -); diff --git a/packages/fuselage-ui-kit/src/elements/ContextElement/ContextElementItem.tsx b/packages/fuselage-ui-kit/src/elements/ContextElement/ContextElementItem.tsx deleted file mode 100644 index c50044e45c..0000000000 --- a/packages/fuselage-ui-kit/src/elements/ContextElement/ContextElementItem.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import { BlockContext, ElementType } from '@rocket.chat/ui-kit'; -import type { FC } from 'react'; -import React from 'react'; - -import type { BlockProps } from '../../utils/BlockProps'; - -type ContextElementProps = BlockProps; - -export const ContextElementItem: FC<{ - element: ContextElementProps['block']['elements'][number]; - surfaceRenderer: ContextElementProps['surfaceRenderer']; - index: number; -}> = ({ element, surfaceRenderer, index }) => { - const renderedElement = surfaceRenderer.renderContext( - element, - BlockContext.CONTEXT, - undefined, - index - ); - - if (!renderedElement) { - return null; - } - - switch (element.type) { - case ElementType.PLAIN_TEXT: - case ElementType.MARKDOWN: - return ( - - {renderedElement} - - ); - - default: - return <>{renderedElement}; - } -}; diff --git a/packages/fuselage-ui-kit/src/elements/ContextElement/index.tsx b/packages/fuselage-ui-kit/src/elements/ContextElement/index.tsx deleted file mode 100644 index 65f71e66c9..0000000000 --- a/packages/fuselage-ui-kit/src/elements/ContextElement/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './ContextElement'; diff --git a/packages/fuselage-ui-kit/src/elements/DatePickerElement.tsx b/packages/fuselage-ui-kit/src/elements/DatePickerElement.tsx deleted file mode 100644 index ba05bedf9b..0000000000 --- a/packages/fuselage-ui-kit/src/elements/DatePickerElement.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { InputBox } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React from 'react'; - -import { useUiKitState } from '../hooks/useUiKitState'; -import type { BlockProps } from '../utils/BlockProps'; -import { fromTextObjectToString } from '../utils/fromTextObjectToString'; - -type DatePickerElementProps = BlockProps; - -const DatePickerElement = ({ - block, - context, - surfaceRenderer, -}: DatePickerElementProps): ReactElement => { - const [{ loading, value, error }, action] = useUiKitState(block, context); - const { actionId, placeholder } = block; - - return ( - - ); -}; - -export default DatePickerElement; diff --git a/packages/fuselage-ui-kit/src/elements/ImageElement.styles.tsx b/packages/fuselage-ui-kit/src/elements/ImageElement.styles.tsx deleted file mode 100644 index d3c28516ad..0000000000 --- a/packages/fuselage-ui-kit/src/elements/ImageElement.styles.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import styled from '@rocket.chat/styled'; - -const filterElementProps = ({ - imageUrl: _imageUrl, - size: _size, - ...props -}: { - imageUrl: string; - size: number; -}) => props; - -export const Element = styled('div', filterElementProps)` - box-shadow: 0 0 0px 1px rgba(204, 204, 204, 38%); - background-repeat: no-repeat; - background-position: 50%; - background-size: cover; - background-color: rgba(204, 204, 204, 38%); - background-image: url(${(props) => props.imageUrl}); - width: ${(props) => String(props.size)}px; - height: ${(props) => String(props.size)}px; - border-radius: 4px; - overflow: hidden; - margin-inline-start: 4px; -`; diff --git a/packages/fuselage-ui-kit/src/elements/ImageElement.tsx b/packages/fuselage-ui-kit/src/elements/ImageElement.tsx deleted file mode 100644 index ec56a868c3..0000000000 --- a/packages/fuselage-ui-kit/src/elements/ImageElement.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React from 'react'; - -import type { BlockProps } from '../utils/BlockProps'; -import { Element } from './ImageElement.styles'; - -type ImageElementProps = BlockProps; - -const ImageElement = ({ - block, - context, -}: ImageElementProps): ReactElement | null => { - const size = - (context === UiKit.BlockContext.SECTION && 88) || - (context === UiKit.BlockContext.CONTEXT && 20) || - undefined; - - if (!size) { - return null; - } - - return ; -}; - -export default ImageElement; diff --git a/packages/fuselage-ui-kit/src/elements/LinearScaleElement.tsx b/packages/fuselage-ui-kit/src/elements/LinearScaleElement.tsx deleted file mode 100644 index e35ea819db..0000000000 --- a/packages/fuselage-ui-kit/src/elements/LinearScaleElement.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { Box, Button, ButtonGroup } from '@rocket.chat/fuselage'; -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo, useMemo } from 'react'; - -import { useUiKitState } from '../hooks/useUiKitState'; -import type { BlockProps } from '../utils/BlockProps'; - -type LinearScaleElementProps = BlockProps; - -const LinearScaleElement = ({ - className, - block, - context, - surfaceRenderer, -}: LinearScaleElementProps): ReactElement => { - const { - minValue = 0, - maxValue = 10, - initialValue, - preLabel, - postLabel, - } = block; - - const [{ loading, value = initialValue, error }, action] = useUiKitState( - block, - context - ); - - const points = useMemo( - () => - Array.from({ length: Math.max(maxValue - minValue + 1, 1) }, (_, i) => - String(minValue + i) - ), - [maxValue, minValue] - ); - - return ( - - {preLabel && ( - - {surfaceRenderer.renderTextObject( - preLabel, - 0, - UiKit.BlockContext.NONE - )} - - )} - - - {points.map((point, i) => ( - - ))} - - - {postLabel && ( - - {surfaceRenderer.renderTextObject( - postLabel, - 0, - UiKit.BlockContext.NONE - )} - - )} - - ); -}; - -export default memo(LinearScaleElement); diff --git a/packages/fuselage-ui-kit/src/elements/MultiStaticSelectElement.tsx b/packages/fuselage-ui-kit/src/elements/MultiStaticSelectElement.tsx deleted file mode 100644 index e7c4ed49bd..0000000000 --- a/packages/fuselage-ui-kit/src/elements/MultiStaticSelectElement.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import type { SelectOption } from '@rocket.chat/fuselage'; -import { MultiSelectFiltered } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo, useCallback, useMemo } from 'react'; - -import { useUiKitState } from '../hooks/useUiKitState'; -import type { BlockProps } from '../utils/BlockProps'; -import { fromTextObjectToString } from '../utils/fromTextObjectToString'; - -type MultiStaticSelectElementProps = BlockProps; - -const MultiStaticSelectElement = ({ - block, - context, - surfaceRenderer, -}: MultiStaticSelectElementProps): ReactElement => { - const [{ loading, value, error }, action] = useUiKitState(block, context); - - const options = useMemo( - () => - block.options.map(({ value, text }, i) => [ - value, - fromTextObjectToString(surfaceRenderer, text, i) ?? '', - ]), - [block.options, surfaceRenderer] - ); - - const handleChange = useCallback( - (value) => { - action({ target: { value } }); - }, - [action] - ); - - return ( - - ); -}; - -export default memo(MultiStaticSelectElement); diff --git a/packages/fuselage-ui-kit/src/elements/OverflowElement.tsx b/packages/fuselage-ui-kit/src/elements/OverflowElement.tsx deleted file mode 100644 index 400feeec6c..0000000000 --- a/packages/fuselage-ui-kit/src/elements/OverflowElement.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import type { OptionType } from '@rocket.chat/fuselage'; -import { - IconButton, - PositionAnimated, - Options, - useCursor, -} from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { useRef, useCallback, useMemo } from 'react'; - -import { useUiKitState } from '../hooks/useUiKitState'; -import type { BlockProps } from '../utils/BlockProps'; -import { fromTextObjectToString } from '../utils/fromTextObjectToString'; - -type OverflowElementProps = BlockProps; - -const OverflowElement = ({ - block, - context, - surfaceRenderer, -}: OverflowElementProps): ReactElement => { - const [{ loading }, action] = useUiKitState(block, context); - - const fireChange = useCallback( - ([value]: [UiKit.ActionOf, string]) => - action({ target: { value } }), - [action] - ); - - const options = useMemo( - () => - block.options.map(({ value, text, url }: UiKit.Option, i) => [ - value, - fromTextObjectToString(surfaceRenderer, text, i) ?? '', - undefined, - undefined, - undefined, - url, - ]), - [block.options, surfaceRenderer] - ); - - const [cursor, handleKeyDown, handleKeyUp, reset, [visible, hide, show]] = - useCursor(-1, options, (selectedOption, [, hide]) => { - fireChange([selectedOption[0] as string, selectedOption[1] as string]); - reset(); - hide(); - }); - - const ref = useRef(null); - const onClick = useCallback(() => { - ref.current?.focus(); - show(); - }, [show]); - - const handleSelection = useCallback( - ([value, _label, _selected, _type, url]: OptionType) => { - if (url) { - window.open(url); - } - action({ target: { value: String(value) } }); - reset(); - hide(); - }, - [action, hide, reset] - ); - - return ( - <> - - - - - - ); -}; - -export default OverflowElement; diff --git a/packages/fuselage-ui-kit/src/elements/PlainTextInputElement.tsx b/packages/fuselage-ui-kit/src/elements/PlainTextInputElement.tsx deleted file mode 100644 index 7394b605ed..0000000000 --- a/packages/fuselage-ui-kit/src/elements/PlainTextInputElement.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { TextAreaInput, TextInput } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo } from 'react'; - -import { useUiKitState } from '../hooks/useUiKitState'; -import type { BlockProps } from '../utils/BlockProps'; -import { fromTextObjectToString } from '../utils/fromTextObjectToString'; - -type PlainTextInputElementProps = BlockProps; - -const PlainTextInputElement = ({ - block, - context, - surfaceRenderer, -}: PlainTextInputElementProps): ReactElement => { - const [{ loading, value, error }, action] = useUiKitState(block, context); - - if (block.multiline) { - return ( - - ); - } - - return ( - - ); -}; - -export default memo(PlainTextInputElement); diff --git a/packages/fuselage-ui-kit/src/elements/StaticSelectElement.tsx b/packages/fuselage-ui-kit/src/elements/StaticSelectElement.tsx deleted file mode 100644 index 797e33c4f4..0000000000 --- a/packages/fuselage-ui-kit/src/elements/StaticSelectElement.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { SelectFiltered } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { memo, useCallback, useMemo } from 'react'; - -import { useUiKitState } from '../hooks/useUiKitState'; -import type { BlockProps } from '../utils/BlockProps'; -import { fromTextObjectToString } from '../utils/fromTextObjectToString'; - -type StaticSelectElementProps = BlockProps; - -const StaticSelectElement = ({ - block, - context, - surfaceRenderer, -}: StaticSelectElementProps): ReactElement => { - const [{ loading, value, error }, action] = useUiKitState(block, context); - - const options = useMemo<[string, string][]>( - () => - block.options.map((option, i) => [ - option.value, - fromTextObjectToString(surfaceRenderer, option.text, i) ?? '', - ]), - [block.options, surfaceRenderer] - ); - - const handleChange = useCallback( - (value) => { - action({ target: { value } }); - }, - [action] - ); - - return ( - - ); -}; - -export default memo(StaticSelectElement); diff --git a/packages/fuselage-ui-kit/src/hooks/useUiKitState.ts b/packages/fuselage-ui-kit/src/hooks/useUiKitState.ts deleted file mode 100644 index 5c05a1e448..0000000000 --- a/packages/fuselage-ui-kit/src/hooks/useUiKitState.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { useMutableCallback, useSafely } from '@rocket.chat/fuselage-hooks'; -import * as UiKit from '@rocket.chat/ui-kit'; -import { useContext, useMemo, useState } from 'react'; - -import { kitContext, useUiKitStateValue } from '../contexts/kitContext'; - -type UiKitState< - TElement extends UiKit.ActionableElement = UiKit.ActionableElement -> = { - loading: boolean; - setLoading: (loading: boolean) => void; - error?: string; - value: UiKit.ActionOf; -}; - -const hasInitialValue = ( - element: TElement -): element is TElement & { initialValue: number | string } => - 'initialValue' in element; - -const hasInitialOption = ( - element: TElement -): element is TElement & { initialOption: UiKit.Option } => - 'initialOption' in element; - -export const useUiKitState: ( - element: TElement, - context: UiKit.BlockContext -) => [ - state: UiKitState, - action: ( - pseudoEvent?: - | Event - | { target: EventTarget } - | { target: { value: UiKit.ActionOf } } - ) => void -] = (rest, context) => { - const { blockId, actionId, appId, dispatchActionConfig } = rest; - const { - action, - appId: appIdFromContext, - viewId, - state, - } = useContext(kitContext); - - const initialValue = - (hasInitialValue(rest) && rest.initialValue) || - (hasInitialOption(rest) && rest.initialOption.value) || - undefined; - - const { value: _value, error } = useUiKitStateValue(actionId, initialValue); - const [value, setValue] = useSafely(useState(_value)); - const [loading, setLoading] = useSafely(useState(false)); - - const actionFunction = useMutableCallback(async (e) => { - const { - target: { value }, - } = e; - setLoading(true); - setValue(value); - state && (await state({ blockId, appId, actionId, value, viewId }, e)); - await action( - { - blockId, - appId: appId || appIdFromContext, - actionId, - value, - viewId, - }, - e - ); - setLoading(false); - }); - - // Used for triggering actions on text inputs. Removing the load state - // makes the text input field remain focused after running the action - const noLoadStateActionFunction = useMutableCallback(async (e) => { - const { - target: { value }, - } = e; - setValue(value); - state && (await state({ blockId, appId, actionId, value, viewId }, e)); - await action( - { - blockId, - appId: appId || appIdFromContext, - actionId, - value, - viewId, - dispatchActionConfig, - }, - e - ); - }); - - const stateFunction = useMutableCallback(async (e) => { - const { - target: { value }, - } = e; - setValue(value); - await state( - { - blockId, - appId: appId || appIdFromContext, - actionId, - value, - viewId, - }, - e - ); - }); - - const result: UiKitState = useMemo( - () => ({ loading, setLoading, error, value }), - [loading, setLoading, error, value] - ); - - if ( - rest.type === 'plain_text_input' && - Array.isArray(rest?.dispatchActionConfig) && - rest.dispatchActionConfig.includes('on_character_entered') - ) { - return [result, noLoadStateActionFunction]; - } - - if ( - (context && - [UiKit.BlockContext.SECTION, UiKit.BlockContext.ACTION].includes( - context - )) || - (Array.isArray(rest?.dispatchActionConfig) && - rest.dispatchActionConfig.includes('on_item_selected')) - ) { - return [result, actionFunction]; - } - - return [result, stateFunction]; -}; diff --git a/packages/fuselage-ui-kit/src/index.ts b/packages/fuselage-ui-kit/src/index.ts deleted file mode 100644 index 534dbfbdb0..0000000000 --- a/packages/fuselage-ui-kit/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './hooks/useUiKitState'; -export * from './contexts/kitContext'; -export * from './surfaces'; -export { UiKitComponent } from './utils/UiKitComponent'; diff --git a/packages/fuselage-ui-kit/src/stories/Banner.stories.tsx b/packages/fuselage-ui-kit/src/stories/Banner.stories.tsx deleted file mode 100644 index 956e29238b..0000000000 --- a/packages/fuselage-ui-kit/src/stories/Banner.stories.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* eslint-disable new-cap */ -import { Banner, Icon } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import { action } from '@storybook/addon-actions'; -import React from 'react'; - -import { kitContext, UiKitBanner } from '..'; -import * as payloads from './payloads'; - -export default { - title: 'Surfaces/Banner', - argTypes: { - blocks: { control: 'object' }, - type: { - control: { - type: 'radio', - }, - options: ['neutral', 'info', 'success', 'warning', 'danger'], - defaultValue: 'neutral', - }, - errors: { control: 'object' }, - }, -}; - -const createStory = (blocks: readonly UiKit.LayoutBlock[], errors = {}) => { - const story = ({ - blocks, - type, - errors, - }: { - blocks: readonly UiKit.LayoutBlock[]; - type: 'neutral' | 'info' | 'success' | 'warning' | 'danger'; - errors: Record; - }) => ( - - } closeable variant={type}> - {UiKitBanner(blocks)} - - - ); - story.args = { - blocks, - errors, - }; - - return story; -}; - -export const Divider = createStory(payloads.divider); - -export const SectionWithPlainText = createStory(payloads.sectionWithPlainText); - -export const SectionWithMrkdwn = createStory(payloads.sectionWithMrkdwn); - -export const SectionWithTextFields = createStory( - payloads.sectionWithTextFields -); - -export const SectionWithButtonAccessory = createStory( - payloads.sectionWithButtonAccessory -); - -export const SectionWithImageAccessory = createStory( - payloads.sectionWithImageAccessory -); - -export const SectionWithOverflowMenuAccessory = createStory( - payloads.sectionWithOverflowMenuAccessory -); - -export const SectionWithDatePickerAccessory = createStory( - payloads.sectionWithDatePickerAccessory -); - -export const ImageWithTitle = createStory(payloads.imageWithTitle); - -export const ImageWithoutTitle = createStory(payloads.imageWithoutTitle); - -export const ActionsWithAllSelects = createStory( - payloads.actionsWithAllSelects -); - -export const ActionsWithFilteredConversationsSelect = createStory( - payloads.actionsWithFilteredConversationsSelect -); - -export const ActionsWithInitializedSelects = createStory( - payloads.actionsWithInitializedSelects -); - -export const ActionsWithButton = createStory(payloads.actionsWithButton); - -export const ActionsWithButtonAsLink = createStory( - payloads.actionsWithButtonAsLink -); - -export const ActionsWithDatePicker = createStory( - payloads.actionsWithDatePicker -); - -export const ContextWithPlainText = createStory(payloads.contextWithPlainText); - -export const ContextWithMrkdwn = createStory(payloads.contextWithMrkdwn); - -export const ContextWithTextAndImages = createStory( - payloads.contextWithTextAndImages -); - -export const InputWithMultilinePlainTextInput = createStory( - payloads.inputWithMultilinePlainTextInput, - { - 'input-0': 'Error', - } -); - -export const InputWithPlainTextInput = createStory( - payloads.inputWithPlainTextInput, - { - 'input-0': 'Error', - } -); - -export const InputWithMultiUsersSelect = createStory( - payloads.inputWithMultiUsersSelect, - { - 'input-0': 'Error', - } -); - -export const InputWithStaticSelect = createStory( - payloads.inputWithStaticSelect, - { - 'input-0': 'Error', - } -); - -export const InputWithDatePicker = createStory(payloads.inputWithDatePicker, { - 'input-0': 'Error', -}); - -export const InputWithLinearScale = createStory(payloads.inputWithLinearScale, { - 'input-0': 'Error', -}); - -export const Conditional = createStory(payloads.conditional); diff --git a/packages/fuselage-ui-kit/src/stories/Message.stories.tsx b/packages/fuselage-ui-kit/src/stories/Message.stories.tsx deleted file mode 100644 index 0bd61a052a..0000000000 --- a/packages/fuselage-ui-kit/src/stories/Message.stories.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/* eslint-disable new-cap */ -import { Message, Avatar } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import { action } from '@storybook/addon-actions'; -import React from 'react'; - -import { kitContext, UiKitMessage } from '..'; -import * as payloads from './payloads'; - -export default { - title: 'Surfaces/Message', - argTypes: { - blocks: { control: 'object' }, - }, -}; - -const createStory = (blocks: readonly UiKit.LayoutBlock[]) => { - const story = ({ - blocks, - errors, - }: { - blocks: readonly UiKit.LayoutBlock[]; - errors: Record; - }) => ( - - - - - - - - Haylie George{' '} - @haylie.george - - Admin - User - Owner - 12:00 PM - - - - {UiKitMessage(blocks)} - - - - - - - - - - - - ); - story.args = { - blocks, - }; - - return story; -}; - -export const Divider = createStory(payloads.divider); - -export const SectionWithPlainText = createStory(payloads.sectionWithPlainText); - -export const SectionWithMrkdwn = createStory(payloads.sectionWithMrkdwn); - -export const SectionWithTextFields = createStory( - payloads.sectionWithTextFields -); - -export const SectionWithButtonAccessory = createStory( - payloads.sectionWithButtonAccessory -); - -export const SectionWithImageAccessory = createStory( - payloads.sectionWithImageAccessory -); - -export const SectionWithOverflowMenuAccessory = createStory( - payloads.sectionWithOverflowMenuAccessory -); - -export const SectionWithDatePickerAccessory = createStory( - payloads.sectionWithDatePickerAccessory -); - -export const ImageWithTitle = createStory(payloads.imageWithTitle); - -export const ImageWithoutTitle = createStory(payloads.imageWithoutTitle); - -export const ActionsWithAllSelects = createStory( - payloads.actionsWithAllSelects -); - -export const ActionsWithFilteredConversationsSelect = createStory( - payloads.actionsWithFilteredConversationsSelect -); - -export const ActionsWithInitializedSelects = createStory( - payloads.actionsWithInitializedSelects -); - -export const ActionsWithButton = createStory(payloads.actionsWithButton); - -export const ActionsWithButtonAsLink = createStory( - payloads.actionsWithButtonAsLink -); - -export const ActionsWithDatePicker = createStory( - payloads.actionsWithDatePicker -); - -export const ContextWithPlainText = createStory(payloads.contextWithPlainText); - -export const ContextWithMrkdwn = createStory(payloads.contextWithMrkdwn); - -export const ContextWithTextAndImages = createStory( - payloads.contextWithTextAndImages -); - -export const Conditional = createStory(payloads.conditional); - -export const Preview = createStory(payloads.preview); -export const PreviewWithExternalUrl = createStory( - payloads.previewWithExternalUrl -); diff --git a/packages/fuselage-ui-kit/src/stories/Modal.stories.tsx b/packages/fuselage-ui-kit/src/stories/Modal.stories.tsx deleted file mode 100644 index ab4f63d95e..0000000000 --- a/packages/fuselage-ui-kit/src/stories/Modal.stories.tsx +++ /dev/null @@ -1,194 +0,0 @@ -/* eslint-disable new-cap */ -import { - AnimatedVisibility, - Button, - ButtonGroup, - Modal, - ModalHeader, - ModalThumb, - ModalContent, - ModalTitle, - ModalFooter, - ModalClose, -} from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import { action } from '@storybook/addon-actions'; -import type { ReactNode } from 'react'; -import React from 'react'; - -import { kitContext, UiKitModal } from '..'; -import * as payloads from './payloads'; - -type VisibilityType = 'hidden' | 'visible' | 'hiding' | 'unhiding' | undefined; - -const DemoModal = ({ - children, - visible, -}: { - children?: ReactNode; - visible: boolean; -}) => ( - - - - - Modal Header - - - {children} - - - - - - - - -); - -export default { - title: 'Surfaces/Modal', - argTypes: { - blocks: { control: 'object' }, - errors: { control: 'object' }, - visible: { control: 'boolean', defaultValue: true }, - }, -}; - -const createStory = (blocks: readonly UiKit.LayoutBlock[], errors = {}) => { - const story = ({ - blocks, - errors, - visible, - }: { - blocks: readonly UiKit.LayoutBlock[]; - errors: Record; - visible: boolean; - }) => ( - - - {UiKitModal(blocks)} - - - ); - story.args = { - blocks, - errors, - }; - - return story; -}; - -export const Divider = createStory(payloads.divider); - -export const SectionWithPlainText = createStory(payloads.sectionWithPlainText); - -export const SectionWithMrkdwn = createStory(payloads.sectionWithMrkdwn); - -export const SectionWithTextFields = createStory( - payloads.sectionWithTextFields -); - -export const SectionWithButtonAccessory = createStory( - payloads.sectionWithButtonAccessory -); - -export const SectionWithImageAccessory = createStory( - payloads.sectionWithImageAccessory -); - -export const SectionWithOverflowMenuAccessory = createStory( - payloads.sectionWithOverflowMenuAccessory -); - -export const SectionWithDatePickerAccessory = createStory( - payloads.sectionWithDatePickerAccessory -); - -export const ImageWithTitle = createStory(payloads.imageWithTitle); - -export const ImageWithoutTitle = createStory(payloads.imageWithoutTitle); - -export const ActionsWithAllSelects = createStory( - payloads.actionsWithAllSelects -); - -export const ActionsWithFilteredConversationsSelect = createStory( - payloads.actionsWithFilteredConversationsSelect -); - -export const ActionsWithInitializedSelects = createStory( - payloads.actionsWithInitializedSelects -); - -export const ActionsWithButton = createStory(payloads.actionsWithButton); - -export const ActionsWithButtonAsLink = createStory( - payloads.actionsWithButtonAsLink -); - -export const ActionsWithDatePicker = createStory( - payloads.actionsWithDatePicker -); - -export const ContextWithPlainText = createStory(payloads.contextWithPlainText); - -export const ContextWithMrkdwn = createStory(payloads.contextWithMrkdwn); - -export const ContextWithTextAndImages = createStory( - payloads.contextWithTextAndImages -); - -export const InputWithMultilinePlainTextInput = createStory( - payloads.inputWithMultilinePlainTextInput, - { - 'input-0': 'Error', - } -); - -export const InputWithPlainTextInput = createStory( - payloads.inputWithPlainTextInput, - { - 'input-0': 'Error', - } -); - -export const InputWithMultiUsersSelect = createStory( - payloads.inputWithMultiUsersSelect, - { - 'input-0': 'Error', - } -); - -export const InputWithStaticSelect = createStory( - payloads.inputWithStaticSelect, - { - 'input-0': 'Error', - } -); - -export const InputWithDatePicker = createStory(payloads.inputWithDatePicker, { - 'input-0': 'Error', -}); - -export const InputWithLinearScale = createStory(payloads.inputWithLinearScale, { - 'input-0': 'Error', -}); - -export const Conditional = createStory(payloads.conditional); diff --git a/packages/fuselage-ui-kit/src/stories/payloads/actions.ts b/packages/fuselage-ui-kit/src/stories/payloads/actions.ts deleted file mode 100644 index 2b248412cc..0000000000 --- a/packages/fuselage-ui-kit/src/stories/payloads/actions.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type * as UiKit from '@rocket.chat/ui-kit'; - -export const actionsWithAllSelects: readonly UiKit.LayoutBlock[] = [ - { - type: 'actions', - elements: [ - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'conversations_select', - // placeholder: { - // type: 'plain_text', - // text: 'Select a conversation', - // emoji: true, - // }, - }, - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'channels_select', - // placeholder: { - // type: 'plain_text', - // text: 'Select a channel', - // emoji: true, - // }, - }, - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'users_select', - // placeholder: { - // type: 'plain_text', - // text: 'Select a user', - // emoji: true, - // }, - }, - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'static_select', - placeholder: { - type: 'plain_text', - text: 'Select an item', - emoji: true, - }, - options: [ - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-0', - }, - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-1', - }, - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-2', - }, - ], - }, - ], - }, -] as const; - -export const actionsWithFilteredConversationsSelect: readonly UiKit.LayoutBlock[] = - [ - { - type: 'actions', - elements: [ - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'conversations_select', - // placeholder: { - // type: 'plain_text', - // text: 'Select private conversation', - // emoji: true, - // }, - // filter: { - // include: ['private'], - // }, - }, - ], - }, - ] as const; - -export const actionsWithInitializedSelects: readonly UiKit.LayoutBlock[] = [ - { - type: 'actions', - elements: [ - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'conversations_select', - // placeholder: { - // type: 'plain_text', - // text: 'Select a conversation', - // emoji: true, - // }, - // initialConversation: 'D123', - }, - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'users_select', - // placeholder: { - // type: 'plain_text', - // text: 'Select a user', - // emoji: true, - // }, - // initialUser: 'U123', - }, - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'channels_select', - // placeholder: { - // type: 'plain_text', - // text: 'Select a channel', - // emoji: true, - // }, - // initialChannel: 'C123', - }, - ], - }, -] as const; - -export const actionsWithButton: readonly UiKit.LayoutBlock[] = [ - { - type: 'actions', - elements: [ - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'button', - text: { - type: 'plain_text', - text: 'Click Me', - emoji: true, - }, - value: 'click_me_123', - }, - ], - }, -] as const; - -export const actionsWithButtonAsLink: readonly UiKit.LayoutBlock[] = [ - { - type: 'actions', - elements: [ - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'button', - text: { - type: 'plain_text', - text: 'Click Me', - emoji: true, - }, - url: 'https://rocket.chat', - value: 'click_me_123', - }, - ], - }, -] as const; - -export const actionsWithDatePicker: readonly UiKit.LayoutBlock[] = [ - { - type: 'actions', - elements: [ - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'datepicker', - initialDate: '1990-04-28', - placeholder: { - type: 'plain_text', - text: 'Select a date', - emoji: true, - }, - }, - { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'datepicker', - initialDate: '1990-04-28', - placeholder: { - type: 'plain_text', - text: 'Select a date', - emoji: true, - }, - }, - ], - }, -] as const; diff --git a/packages/fuselage-ui-kit/src/stories/payloads/conditional.ts b/packages/fuselage-ui-kit/src/stories/payloads/conditional.ts deleted file mode 100644 index 4694ddfdf5..0000000000 --- a/packages/fuselage-ui-kit/src/stories/payloads/conditional.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type * as UiKit from '@rocket.chat/ui-kit'; - -export const conditional: readonly UiKit.LayoutBlock[] = [ - { - type: 'conditional', - when: { - engine: ['rocket.chat'], - }, - render: [ - { - type: 'section', - text: { - type: 'plain_text', - text: 'This is a plain text section block.', - emoji: true, - }, - }, - ], - }, -] as const; diff --git a/packages/fuselage-ui-kit/src/stories/payloads/context.ts b/packages/fuselage-ui-kit/src/stories/payloads/context.ts deleted file mode 100644 index d639c6caef..0000000000 --- a/packages/fuselage-ui-kit/src/stories/payloads/context.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type * as UiKit from '@rocket.chat/ui-kit'; - -export const contextWithPlainText: readonly UiKit.LayoutBlock[] = [ - { - type: 'context', - elements: [ - { - type: 'plain_text', - text: 'Author: K A Applegate', - emoji: true, - }, - ], - }, -] as const; - -export const contextWithMrkdwn: readonly UiKit.LayoutBlock[] = [ - { - type: 'context', - elements: [ - { - type: 'image', - imageUrl: - 'data:image/jpeg;base64,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', - altText: 'cute cat', - }, - { - type: 'mrkdwn', - text: '*Cat* has approved this message.', - }, - ], - }, -] as const; - -export const contextWithTextAndImages: readonly UiKit.LayoutBlock[] = [ - { - type: 'context', - elements: [ - { - type: 'mrkdwn', - text: '*This* is :smile: markdown', - }, - { - type: 'image', - imageUrl: - 'data:image/jpeg;base64,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', - altText: 'cute cat', - }, - { - type: 'image', - imageUrl: - 'data:image/jpeg;base64,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', - altText: 'cute cat', - }, - { - type: 'image', - imageUrl: - 'data:image/jpeg;base64,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', - altText: 'cute cat', - }, - { - type: 'plain_text', - text: 'Author: K A Applegate', - emoji: true, - }, - ], - }, -] as const; diff --git a/packages/fuselage-ui-kit/src/stories/payloads/divider.ts b/packages/fuselage-ui-kit/src/stories/payloads/divider.ts deleted file mode 100644 index df5fa7f2dc..0000000000 --- a/packages/fuselage-ui-kit/src/stories/payloads/divider.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type * as UiKit from '@rocket.chat/ui-kit'; - -export const divider: readonly UiKit.LayoutBlock[] = [ - { - type: 'divider', - }, -] as const; diff --git a/packages/fuselage-ui-kit/src/stories/payloads/image.ts b/packages/fuselage-ui-kit/src/stories/payloads/image.ts deleted file mode 100644 index 9dffb86a0b..0000000000 --- a/packages/fuselage-ui-kit/src/stories/payloads/image.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type * as UiKit from '@rocket.chat/ui-kit'; - -export const imageWithTitle: readonly UiKit.LayoutBlock[] = [ - { - type: 'image', - title: { - type: 'plain_text', - text: 'I Need a Marg', - emoji: true, - }, - imageUrl: - 'data:image/jpeg;base64,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', - altText: 'marg', - }, -]; - -export const imageWithoutTitle: readonly UiKit.LayoutBlock[] = [ - { - type: 'image', - imageUrl: - 'data:image/jpeg;base64,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', - altText: 'inspiration', - }, -]; diff --git a/packages/fuselage-ui-kit/src/stories/payloads/img.ts b/packages/fuselage-ui-kit/src/stories/payloads/img.ts deleted file mode 100644 index bd5926679d..0000000000 --- a/packages/fuselage-ui-kit/src/stories/payloads/img.ts +++ /dev/null @@ -1,3 +0,0 @@ -const img = - 'data:image/jpeg;base64,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'; -export default img; diff --git a/packages/fuselage-ui-kit/src/stories/payloads/index.ts b/packages/fuselage-ui-kit/src/stories/payloads/index.ts deleted file mode 100644 index 2de52ceac4..0000000000 --- a/packages/fuselage-ui-kit/src/stories/payloads/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './actions'; -export * from './conditional'; -export * from './context'; -export * from './divider'; -export * from './image'; -export * from './input'; -export * from './section'; -export * from './preview'; diff --git a/packages/fuselage-ui-kit/src/stories/payloads/input.ts b/packages/fuselage-ui-kit/src/stories/payloads/input.ts deleted file mode 100644 index b52911d3b1..0000000000 --- a/packages/fuselage-ui-kit/src/stories/payloads/input.ts +++ /dev/null @@ -1,149 +0,0 @@ -import type * as UiKit from '@rocket.chat/ui-kit'; - -export const inputWithMultilinePlainTextInput: readonly UiKit.LayoutBlock[] = [ - { - type: 'input', - element: { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - type: 'plain_text_input', - multiline: true, - actionId: 'input-0', - }, - label: { - type: 'plain_text', - text: 'Label', - emoji: true, - }, - }, -] as const; - -export const inputWithPlainTextInput: readonly UiKit.LayoutBlock[] = [ - { - type: 'input', - element: { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - type: 'plain_text_input', - actionId: 'input-0', - }, - label: { - type: 'plain_text', - text: 'Label', - emoji: true, - }, - }, -] as const; - -export const inputWithMultiUsersSelect: readonly UiKit.LayoutBlock[] = [ - { - type: 'input', - element: { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - type: 'multi_users_select', - // placeholder: { - // type: 'plain_text', - // text: 'Select users', - // emoji: true, - // }, - actionId: 'input-0', - }, - label: { - type: 'plain_text', - text: 'Label', - emoji: true, - }, - }, -] as const; - -export const inputWithStaticSelect: readonly UiKit.LayoutBlock[] = [ - { - type: 'input', - element: { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - type: 'static_select', - placeholder: { - type: 'plain_text', - text: 'Select an item', - emoji: true, - }, - options: [ - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-0', - }, - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-1', - }, - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-2', - }, - ], - actionId: 'input-0', - }, - label: { - type: 'plain_text', - text: 'Label', - emoji: true, - }, - }, -] as const; - -export const inputWithDatePicker: readonly UiKit.LayoutBlock[] = [ - { - type: 'input', - element: { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - type: 'datepicker', - initialDate: '1990-04-28', - placeholder: { - type: 'plain_text', - text: 'Select a date', - emoji: true, - }, - actionId: 'input-0', - }, - label: { - type: 'plain_text', - text: 'Label', - emoji: true, - }, - }, -] as const; - -export const inputWithLinearScale: readonly UiKit.LayoutBlock[] = [ - { - type: 'input', - element: { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - type: 'linear_scale', - minValue: 0, - maxValue: 10, - initialValue: 7, - actionId: 'input-0', - }, - label: { - type: 'plain_text', - text: 'Label', - emoji: true, - }, - }, -] as const; diff --git a/packages/fuselage-ui-kit/src/stories/payloads/preview.ts b/packages/fuselage-ui-kit/src/stories/payloads/preview.ts deleted file mode 100644 index ef95f91c45..0000000000 --- a/packages/fuselage-ui-kit/src/stories/payloads/preview.ts +++ /dev/null @@ -1,66 +0,0 @@ -// import * as UiKit from '@rocket.chat/ui-kit'; -import type { PreviewBlock } from '@rocket.chat/ui-kit'; - -import img from './img'; - -export const preview: PreviewBlock[] = [ - { - type: 'preview', - title: [ - { - type: 'plain_text', - text: 'I Need a Marg', - emoji: true, - }, - ], - description: [ - { - type: 'plain_text', - text: 'I Need a Description', - emoji: true, - }, - ], - thumb: { url: img }, - footer: { - type: 'context', - elements: [ - { - type: 'plain_text', - text: 'google.com', - }, - ], - }, - }, -]; - -export const previewWithExternalUrl: PreviewBlock[] = [ - { - type: 'preview', - title: [ - { - type: 'plain_text', - text: 'I Need a Marg', - emoji: true, - }, - ], - description: [ - { - type: 'plain_text', - text: 'I Need a Description', - emoji: true, - }, - ], - // thumb: { url: img }, - footer: { - type: 'context', - elements: [ - { - type: 'plain_text', - text: 'google.com', - }, - ], - }, - externalUrl: - 'https://rocketchat.github.io/Rocket.Chat.Fuselage/?path=/story/*', - }, -]; diff --git a/packages/fuselage-ui-kit/src/stories/payloads/section.ts b/packages/fuselage-ui-kit/src/stories/payloads/section.ts deleted file mode 100644 index 8187ade0c3..0000000000 --- a/packages/fuselage-ui-kit/src/stories/payloads/section.ts +++ /dev/null @@ -1,173 +0,0 @@ -import type * as UiKit from '@rocket.chat/ui-kit'; - -export const sectionWithPlainText: readonly UiKit.LayoutBlock[] = [ - { - type: 'section', - text: { - type: 'plain_text', - text: 'This is a plain text section block.', - emoji: true, - }, - }, -] as const; - -export const sectionWithMrkdwn: readonly UiKit.LayoutBlock[] = [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'This is a mrkdwn section block :ghost: *this is bold*, and ~this is crossed out~, and ', - }, - }, -] as const; - -export const sectionWithTextFields: readonly UiKit.LayoutBlock[] = [ - { - type: 'section', - fields: [ - { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - ], - }, -] as const; - -export const sectionWithButtonAccessory: readonly UiKit.LayoutBlock[] = [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'This is a section block with a button.', - }, - accessory: { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'button', - text: { - type: 'plain_text', - text: 'Click Me', - emoji: true, - }, - value: 'click_me_123', - }, - }, -] as const; - -export const sectionWithImageAccessory: readonly UiKit.LayoutBlock[] = [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'This is a section block with an accessory image.', - }, - accessory: { - type: 'image', - imageUrl: - 'data:image/jpeg;base64,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', - altText: 'cute cat', - }, - }, -] as const; - -export const sectionWithOverflowMenuAccessory: readonly UiKit.LayoutBlock[] = [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'This is a section block with an overflow menu.', - }, - accessory: { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'overflow', - options: [ - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-0', - }, - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-1', - }, - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-2', - }, - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-3', - }, - { - text: { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true, - }, - value: 'value-4', - }, - ], - }, - }, -] as const; - -export const sectionWithDatePickerAccessory: readonly UiKit.LayoutBlock[] = [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'Pick a date for the deadline.', - }, - accessory: { - appId: 'dummy-app-id', - blockId: 'dummy-block-id', - actionId: 'dummy-action-id', - type: 'datepicker', - initialDate: '1990-04-28', - placeholder: { - type: 'plain_text', - text: 'Select a date', - emoji: true, - }, - }, - }, -] as const; diff --git a/packages/fuselage-ui-kit/src/surfaces/BannerSurface.tsx b/packages/fuselage-ui-kit/src/surfaces/BannerSurface.tsx deleted file mode 100644 index a1df576be0..0000000000 --- a/packages/fuselage-ui-kit/src/surfaces/BannerSurface.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Margins } from '@rocket.chat/fuselage'; -import type { ReactElement, ReactNode } from 'react'; -import React from 'react'; - -import { Surface } from './Surface'; - -type BannerSurfaceProps = { - children?: ReactNode; -}; - -const BannerSurface = ({ children }: BannerSurfaceProps): ReactElement => ( - - {children} - -); - -export default BannerSurface; diff --git a/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx b/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx deleted file mode 100644 index f548f63ac6..0000000000 --- a/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx +++ /dev/null @@ -1,344 +0,0 @@ -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import React, { Fragment } from 'react'; - -import ActionsBlock from '../blocks/ActionsBlock'; -import ContextBlock from '../blocks/ContextBlock'; -import DividerBlock from '../blocks/DividerBlock'; -import ImageBlock from '../blocks/ImageBlock'; -import InputBlock from '../blocks/InputBlock'; -import PreviewBlock from '../blocks/PreviewBlock'; -import SectionBlock from '../blocks/SectionBlock'; -import ButtonElement from '../elements/ButtonElement'; -import DatePickerElement from '../elements/DatePickerElement'; -import ImageElement from '../elements/ImageElement'; -import LinearScaleElement from '../elements/LinearScaleElement'; -import MultiStaticSelectElement from '../elements/MultiStaticSelectElement'; -import OverflowElement from '../elements/OverflowElement'; -import PlainTextInputElement from '../elements/PlainTextInputElement'; -import StaticSelectElement from '../elements/StaticSelectElement'; - -export class FuselageSurfaceRenderer extends UiKit.SurfaceRenderer { - public constructor() { - super([ - 'actions', - 'context', - 'divider', - 'image', - 'input', - 'section', - 'preview', - ]); - } - - public plain_text( - { text = '' }: UiKit.PlainText, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return null; - } - - return text ? {text} : null; - } - - public mrkdwn( - { text = '' }: UiKit.Markdown, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return null; - } - - return text ? {text} : null; - } - - actions( - block: UiKit.ActionsBlock, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return ( - - ); - } - - return null; - } - - preview( - block: UiKit.PreviewBlock, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context !== UiKit.BlockContext.BLOCK) { - return null; - } - return ( - - ); - } - - context( - block: UiKit.ContextBlock, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return ( - - ); - } - - return null; - } - - divider( - block: UiKit.DividerBlock, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return ( - - ); - } - - return null; - } - - image( - block: UiKit.ImageBlock | UiKit.ImageElement, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return ( - - ); - } - - return ( - - ); - } - - input( - block: UiKit.InputBlock, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return ( - - ); - } - - return null; - } - - section( - block: UiKit.SectionBlock, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return ( - - ); - } - - return null; - } - - button( - block: UiKit.ButtonElement, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return null; - } - - return ( - - ); - } - - datepicker( - block: UiKit.DatePickerElement, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return null; - } - - return ( - - ); - } - - static_select( - block: UiKit.StaticSelectElement, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return null; - } - - return ( - - ); - } - - multi_static_select( - block: UiKit.MultiStaticSelectElement, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return null; - } - - return ( - - ); - } - - overflow( - block: UiKit.OverflowElement, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return null; - } - - return ( - - ); - } - - plain_text_input( - block: UiKit.PlainTextInputElement, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return null; - } - - return ( - - ); - } - - linear_scale( - block: UiKit.LinearScaleElement, - context: UiKit.BlockContext, - index: number - ): ReactElement | null { - if (context === UiKit.BlockContext.BLOCK) { - return null; - } - - return ( - - ); - } -} diff --git a/packages/fuselage-ui-kit/src/surfaces/MessageSurface.tsx b/packages/fuselage-ui-kit/src/surfaces/MessageSurface.tsx deleted file mode 100644 index af08af1090..0000000000 --- a/packages/fuselage-ui-kit/src/surfaces/MessageSurface.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Margins } from '@rocket.chat/fuselage'; -import type { ReactElement, ReactNode } from 'react'; -import React from 'react'; - -import { Surface } from './Surface'; - -type MessageSurfaceProps = { - children?: ReactNode; -}; - -const MessageSurface = ({ children }: MessageSurfaceProps): ReactElement => ( - - {children} - -); - -export default MessageSurface; diff --git a/packages/fuselage-ui-kit/src/surfaces/ModalSurface.tsx b/packages/fuselage-ui-kit/src/surfaces/ModalSurface.tsx deleted file mode 100644 index 7be41b4c5e..0000000000 --- a/packages/fuselage-ui-kit/src/surfaces/ModalSurface.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Margins } from '@rocket.chat/fuselage'; -import type { ReactElement, ReactNode } from 'react'; -import React from 'react'; - -import { Surface } from './Surface'; - -type ModalSurfaceProps = { - children?: ReactNode; -}; - -const ModalSurface = ({ children }: ModalSurfaceProps): ReactElement => ( - - {children} - -); - -export default ModalSurface; diff --git a/packages/fuselage-ui-kit/src/surfaces/Surface.tsx b/packages/fuselage-ui-kit/src/surfaces/Surface.tsx deleted file mode 100644 index 1ce5d01743..0000000000 --- a/packages/fuselage-ui-kit/src/surfaces/Surface.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { ContextType, ReactElement, ReactNode } from 'react'; -import React from 'react'; - -import { SurfaceContext } from '../contexts/SurfaceContext'; - -type SurfaceProps = { - children: ReactNode; - type: ContextType; -}; - -export const Surface = ({ children, type }: SurfaceProps): ReactElement => ( - {children} -); diff --git a/packages/fuselage-ui-kit/src/surfaces/SurfaceContext.tsx b/packages/fuselage-ui-kit/src/surfaces/SurfaceContext.tsx deleted file mode 100644 index 4529022064..0000000000 --- a/packages/fuselage-ui-kit/src/surfaces/SurfaceContext.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type { FC } from 'react'; -import React, { createContext, useContext } from 'react'; - -export type SurfaceContextValue = { - type: 'message' | 'modal' | 'banner'; -}; - -export const SurfaceContext = createContext( - undefined -); - -export const Surface: FC<{ value: SurfaceContextValue }> = (props) => ( - -); - -export const useSurface = (): SurfaceContextValue => { - const context = useContext(SurfaceContext); - if (!context) { - throw new Error('Invalid Surface Content'); - } - return context; -}; - -export const useSurfaceType = (): SurfaceContextValue['type'] => { - const context = useSurface(); - return context.type; -}; diff --git a/packages/fuselage-ui-kit/src/surfaces/createSurfaceRenderer.tsx b/packages/fuselage-ui-kit/src/surfaces/createSurfaceRenderer.tsx deleted file mode 100644 index c17b42002f..0000000000 --- a/packages/fuselage-ui-kit/src/surfaces/createSurfaceRenderer.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ComponentType, ReactElement } from 'react'; -import React from 'react'; - -export const createSurfaceRenderer = < - S extends UiKit.SurfaceRenderer ->( - SurfaceComponent: ComponentType, - surfaceRenderer: S -) => - function Surface( - blocks: readonly UiKit.LayoutBlock[], - conditions: UiKit.Conditions = {} - ): ReactElement { - return ( - - {surfaceRenderer.render(blocks, { - engine: 'rocket.chat', - ...conditions, - })} - - ); - }; diff --git a/packages/fuselage-ui-kit/src/surfaces/index.ts b/packages/fuselage-ui-kit/src/surfaces/index.ts deleted file mode 100644 index dd29183435..0000000000 --- a/packages/fuselage-ui-kit/src/surfaces/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import BannerSurface from './BannerSurface'; -import { FuselageSurfaceRenderer } from './FuselageSurfaceRenderer'; -import MessageSurface from './MessageSurface'; -import ModalSurface from './ModalSurface'; -import { createSurfaceRenderer } from './createSurfaceRenderer'; - -// export const attachmentParser = new FuselageSurfaceRenderer(); -export const bannerParser = new FuselageSurfaceRenderer(); -export const messageParser = new FuselageSurfaceRenderer(); -export const modalParser = new FuselageSurfaceRenderer(); - -// export const UiKitAttachment = createSurfaceRenderer(AttachmentSurface, attachmentParser); -export const UiKitBanner = createSurfaceRenderer(BannerSurface, bannerParser); -export const UiKitMessage = createSurfaceRenderer( - MessageSurface, - messageParser -); -export const UiKitModal = createSurfaceRenderer(ModalSurface, modalParser); diff --git a/packages/fuselage-ui-kit/src/utils/BlockProps.ts b/packages/fuselage-ui-kit/src/utils/BlockProps.ts deleted file mode 100644 index d11e8577e5..0000000000 --- a/packages/fuselage-ui-kit/src/utils/BlockProps.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Box } from '@rocket.chat/fuselage'; -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ComponentProps, ReactElement } from 'react'; - -export type BlockProps = { - className?: ComponentProps['className']; - block: B; - context: UiKit.BlockContext; - index: number; - surfaceRenderer: UiKit.SurfaceRenderer; -}; diff --git a/packages/fuselage-ui-kit/src/utils/UiKitComponent.tsx b/packages/fuselage-ui-kit/src/utils/UiKitComponent.tsx deleted file mode 100644 index f43057a6ab..0000000000 --- a/packages/fuselage-ui-kit/src/utils/UiKitComponent.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; - -import type { UiKitBanner, UiKitMessage, UiKitModal } from '../surfaces'; - -type UiKitComponentProps = { - render: typeof UiKitBanner | typeof UiKitMessage | typeof UiKitModal; - blocks: UiKit.LayoutBlock[]; -}; - -export const UiKitComponent = ({ - render, - blocks, -}: UiKitComponentProps): ReactElement | null => render(blocks); diff --git a/packages/fuselage-ui-kit/src/utils/fromTextObjectToString.ts b/packages/fuselage-ui-kit/src/utils/fromTextObjectToString.ts deleted file mode 100644 index adc624986c..0000000000 --- a/packages/fuselage-ui-kit/src/utils/fromTextObjectToString.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as UiKit from '@rocket.chat/ui-kit'; -import type { ReactElement } from 'react'; -import { renderToStaticMarkup } from 'react-dom/server'; - -export const fromTextObjectToString = ( - surfaceRenderer: UiKit.SurfaceRenderer, - textObject: UiKit.TextObject, - index: number -): string | undefined => { - const element = surfaceRenderer.renderTextObject( - textObject, - index, - UiKit.BlockContext.NONE - ); - - if (!element) { - return undefined; - } - - return renderToStaticMarkup(element); -}; diff --git a/packages/fuselage-ui-kit/tsconfig-cjs.json b/packages/fuselage-ui-kit/tsconfig-cjs.json deleted file mode 100644 index 44b902831e..0000000000 --- a/packages/fuselage-ui-kit/tsconfig-cjs.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "commonjs", - "outDir": "./dist/cjs" - } -} diff --git a/packages/fuselage-ui-kit/tsconfig-esm.json b/packages/fuselage-ui-kit/tsconfig-esm.json deleted file mode 100644 index 48e68b0620..0000000000 --- a/packages/fuselage-ui-kit/tsconfig-esm.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "ESNext", - "outDir": "./dist/esm" - } -} diff --git a/packages/fuselage-ui-kit/tsconfig.json b/packages/fuselage-ui-kit/tsconfig.json deleted file mode 100644 index 20234a7be1..0000000000 --- a/packages/fuselage-ui-kit/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "rootDir": "./src", - "module": "ESNext", - "target": "es5", - "lib": ["dom", "es6"], - "sourceMap": true, - "allowJs": false, - "jsx": "react", - "declaration": true, - "outDir": "./dist", - "moduleResolution": "node", - "forceConsistentCasingInFileNames": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noImplicitAny": true, - "strict": true, - "strictNullChecks": true, - "suppressImplicitAnyIndexErrors": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "esModuleInterop": true, - "skipLibCheck": true, - "importsNotUsedAsValues": "error" - }, - "exclude": ["node_modules", "dist"] -} diff --git a/packages/fuselage/babel.config.js b/packages/fuselage/.babelrc.js similarity index 95% rename from packages/fuselage/babel.config.js rename to packages/fuselage/.babelrc.js index 7777ca16b2..a73fff9c22 100644 --- a/packages/fuselage/babel.config.js +++ b/packages/fuselage/.babelrc.js @@ -14,6 +14,7 @@ module.exports = (api) => ({ }, }, ], + '@babel/preset-typescript', [ '@babel/preset-react', { diff --git a/packages/fuselage/.eslintignore b/packages/fuselage/.eslintignore index 9a16c0176f..a0c5a8304a 100644 --- a/packages/fuselage/.eslintignore +++ b/packages/fuselage/.eslintignore @@ -1,6 +1,6 @@ /dist /node_modules /storybook-static +!/.babelrc.js !/.jest !/.storybook -/.storybook/jest-results.json diff --git a/packages/fuselage/.eslintrc.js b/packages/fuselage/.eslintrc.js index f63e529f1e..225f17d426 100644 --- a/packages/fuselage/.eslintrc.js +++ b/packages/fuselage/.eslintrc.js @@ -16,19 +16,9 @@ module.exports = { overrides: [ { files: ['*.mdx'], - extends: [ - '@rocket.chat/eslint-config-alt/react', - 'plugin:mdx/recommended', - ], - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, + extends: ['plugin:mdx/recommended'], rules: { - 'new-cap': 'off', - 'prefer-arrow-callback': 'off', - 'semi': 'off', + 'react/self-closing-comp': 'off', }, }, ], diff --git a/packages/fuselage/.gitignore b/packages/fuselage/.gitignore index 198b84fae8..ab3ea39928 100644 --- a/packages/fuselage/.gitignore +++ b/packages/fuselage/.gitignore @@ -1,4 +1,4 @@ /dist/ /storybook-static/ -/bundle-report.html -/coverage/ \ No newline at end of file +/bundle-report*.html +/coverage/ diff --git a/packages/fuselage/.prettierignore b/packages/fuselage/.prettierignore index c6841e29e9..e7253e50b8 100644 --- a/packages/fuselage/.prettierignore +++ b/packages/fuselage/.prettierignore @@ -1,5 +1,4 @@ /dist /node_modules /storybook-static -/.storybook/jest-results.json /**/*.mdx diff --git a/packages/fuselage/.storybook/helpers.js b/packages/fuselage/.storybook/helpers.tsx similarity index 87% rename from packages/fuselage/.storybook/helpers.js rename to packages/fuselage/.storybook/helpers.tsx index 1007f40742..16e78ed2a8 100644 --- a/packages/fuselage/.storybook/helpers.js +++ b/packages/fuselage/.storybook/helpers.tsx @@ -1,17 +1,25 @@ +import type { ComponentProps, ComponentType } from 'react'; import React from 'react'; import { Box, Icon } from '../src'; -export function PropsVariationSection({ +type PropsVariationSectionProps> = { + component: TComponent; + common: ComponentProps; + xAxis?: Record>>; + yAxis?: Record>>; +}; + +export function PropsVariationSection>({ component: Component, - common = {}, + common, xAxis = {}, yAxis = {}, -}) { +}: PropsVariationSectionProps) { return ( @@ -36,11 +44,15 @@ export function PropsVariationSection({ key={x} is='td' margin='none' - paddingBlock='x8' - paddingInline='x16' + paddingBlock={8} + paddingInline={16} > - + ))} @@ -112,7 +124,7 @@ export const menuOptions = { makeAdmin: { label: ( - + Make Admin ), @@ -121,7 +133,7 @@ export const menuOptions = { delete: { label: ( - + Delete ), diff --git a/packages/fuselage-ui-kit/.storybook/logo.svg.d.ts b/packages/fuselage/.storybook/logo.svg.d.ts similarity index 100% rename from packages/fuselage-ui-kit/.storybook/logo.svg.d.ts rename to packages/fuselage/.storybook/logo.svg.d.ts diff --git a/packages/fuselage/.storybook/main.js b/packages/fuselage/.storybook/main.js deleted file mode 100644 index 0985719ebb..0000000000 --- a/packages/fuselage/.storybook/main.js +++ /dev/null @@ -1,59 +0,0 @@ -const webpack = require('webpack'); - -module.exports = { - core: { - builder: 'webpack5', - }, - features: { - postcss: false, - }, - addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'], - stories: ['../src/**/*.stories.{mdx,js,tsx}'], - webpackFinal: (config) => { - config.module.rules.push({ - test: /\.scss$/, - use: [ - { - loader: 'style-loader', - options: { - injectType: 'lazySingletonStyleTag', - }, - }, - { - loader: 'css-loader', - options: { - importLoaders: 2, - }, - }, - { - loader: 'postcss-loader', - options: { - postcssOptions: { - plugins: [ - require('postcss-svg')(), - require('postcss-custom-properties')(), - require('postcss-logical')({ preserve: true }), - require('postcss-dir-pseudo-class')({ dir: 'ltr' }), - require('autoprefixer')(), - ], - }, - }, - }, - 'sass-loader', - ], - }); - - config.plugins.push(new webpack.EnvironmentPlugin(['NODE_ENV'])); - - // Workaround for @storybook/addon-jest on Webpack 5 - config.resolve = { - ...config.resolve, - alias: { - ...config.resolve.alias, - path: require.resolve('path-browserify'), - }, - }; - - return config; - }, -}; diff --git a/packages/fuselage/.storybook/main.ts b/packages/fuselage/.storybook/main.ts new file mode 100644 index 0000000000..a285cfd087 --- /dev/null +++ b/packages/fuselage/.storybook/main.ts @@ -0,0 +1,28 @@ +import type { StorybookConfig } from '@storybook/react/types'; + +const config: StorybookConfig = { + core: { + builder: 'webpack5', + }, + features: { + postcss: false, + }, + addons: [ + '@storybook/addon-a11y', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + { + name: '@storybook/addon-styling', + options: { + sass: { + implementation: require('sass'), + }, + }, + }, + 'storybook-dark-mode', + ], + framework: '@storybook/react', + stories: ['../src/**/*.stories.{mdx,js,tsx}'], +}; + +export default config; diff --git a/packages/fuselage/.storybook/manager.ts b/packages/fuselage/.storybook/manager.ts deleted file mode 100644 index 6c85216e41..0000000000 --- a/packages/fuselage/.storybook/manager.ts +++ /dev/null @@ -1,17 +0,0 @@ -import colorTokens from '@rocket.chat/fuselage-tokens/colors.json'; -import { addons } from '@storybook/addons'; -import { create } from '@storybook/theming/create'; - -import manifest from '../package.json'; -import logo from './logo.svg'; - -addons.setConfig({ - theme: create({ - base: 'light', - brandTitle: manifest.name, - brandImage: logo, - brandUrl: manifest.homepage, - colorPrimary: colorTokens.n500, - colorSecondary: colorTokens.p500, - }), -}); diff --git a/packages/fuselage/.storybook/preview.ts b/packages/fuselage/.storybook/preview.ts index bb463309c7..65c44abd21 100644 --- a/packages/fuselage/.storybook/preview.ts +++ b/packages/fuselage/.storybook/preview.ts @@ -1,12 +1,17 @@ import breakpointTokens from '@rocket.chat/fuselage-tokens/breakpoints.json'; import { DocsPage, DocsContainer } from '@storybook/addon-docs'; -import { addParameters } from '@storybook/react'; +import type { Parameters } from '@storybook/addons'; +import { themes } from '@storybook/theming'; + +import manifest from '../package.json'; +import logo from './logo.svg'; import 'normalize.css/normalize.css'; import '@rocket.chat/icons/dist/rocketchat.css'; +import '../src/index.scss'; import '@rocket.chat/fuselage-polyfills'; -addParameters({ +export const parameters: Parameters = { backgrounds: { grid: { cellSize: 4, @@ -48,4 +53,18 @@ addParameters({ {} ), }, -}); + darkMode: { + dark: { + ...themes.dark, + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + }, + light: { + ...themes.normal, + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + }, + }, +}; diff --git a/packages/fuselage/.stylelintignore b/packages/fuselage/.stylelintignore index 22e28b0dab..d3353e6962 100644 --- a/packages/fuselage/.stylelintignore +++ b/packages/fuselage/.stylelintignore @@ -1,2 +1,3 @@ /dist /src/styles/colorPalette.scss +/storybook-static diff --git a/packages/fuselage/.stylelintrc b/packages/fuselage/.stylelintrc index 67428e417d..d1c42d5122 100644 --- a/packages/fuselage/.stylelintrc +++ b/packages/fuselage/.stylelintrc @@ -25,22 +25,14 @@ "declaration-block-no-redundant-longhand-properties": true, "declaration-block-no-shorthand-property-overrides": true, "declaration-block-single-line-max-declarations": 1, - "declaration-block-trailing-semicolon": "always", "font-family-no-duplicate-names": true, "function-linear-gradient-no-nonstandard-direction": true, - "function-max-empty-lines": 0, "function-name-case": "lower", "keyframe-declaration-no-important": true, "length-zero-no-unit": true, - "max-empty-lines": 1, - "media-feature-name-case": "lower", "media-feature-name-no-unknown": true, "no-duplicate-selectors": true, "no-empty-source": true, - "no-extra-semicolons": true, - "number-leading-zero": "always", - "number-no-trailing-zeros": true, - "property-case": "lower", "property-no-unknown": true, "rule-empty-line-before": [ "always", @@ -56,8 +48,6 @@ "scss/at-mixin-pattern": "^-?([a-z][a-z0-9]+-)*[a-z][a-z0-9]+$", "scss/dollar-variable-pattern": "^-?([a-z][a-z0-9]+-)*[a-z][a-z0-9]+$", "scss/no-duplicate-mixins": true, - "selector-max-empty-lines": 0, - "selector-pseudo-class-case": "lower", "selector-pseudo-class-no-unknown": [ true, { @@ -66,15 +56,12 @@ ] } ], - "selector-pseudo-element-case": "lower", "selector-pseudo-element-colon-notation": "double", "selector-pseudo-element-no-unknown": true, "selector-type-case": "lower", "selector-type-no-unknown": true, "shorthand-property-no-redundant-values": true, - "unit-case": "lower", "unit-no-unknown": true, - "value-list-max-empty-lines": 1, "order/properties-order": [ [ { diff --git a/packages/fuselage/jest.config.js b/packages/fuselage/jest.config.js index c4550e8d88..71ef7a11c3 100644 --- a/packages/fuselage/jest.config.js +++ b/packages/fuselage/jest.config.js @@ -7,15 +7,6 @@ module.exports = { errorOnDeprecated: true, testMatch: ['/src/**/*.spec.{ts,tsx}'], testEnvironment: 'jsdom', - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - allowJs: true, - }, - }, - }, moduleNameMapper: { '\\.scss$': 'testing-utils/lazySingletonStyleTagModule', }, diff --git a/packages/fuselage/package.json b/packages/fuselage/package.json index c6c24d58ab..0a1d39b40c 100644 --- a/packages/fuselage/package.json +++ b/packages/fuselage/package.json @@ -27,7 +27,7 @@ }, "scripts": { "start": "webpack --watch --mode development", - "storybook": "start-storybook -p 6006", + "storybook": "start-storybook -p 6006 --no-version-updates", "build": "run-s .:build:clean .:build:dev .:build:prod", ".:build:clean": "rimraf dist", ".:build:prod": "run-s .:build:prod:bundle .:build:prod:check", @@ -60,80 +60,89 @@ "@rocket.chat/memo": "workspace:~", "@rocket.chat/styled": "workspace:~", "invariant": "^2.2.4", - "react-aria": "~3.19.0", + "react-aria": "~3.23.1", "react-keyed-flatten-children": "^1.3.0", "react-stately": "~3.17.0" }, "devDependencies": { - "@babel/core": "~7.19.6", - "@babel/eslint-parser": "~7.19.1", - "@babel/plugin-transform-runtime": "~7.19.6", - "@babel/preset-env": "~7.19.4", + "@babel/core": "~7.21.4", + "@babel/eslint-parser": "~7.21.3", + "@babel/plugin-transform-runtime": "~7.21.4", + "@babel/preset-env": "~7.21.4", "@babel/preset-react": "~7.18.6", "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/fuselage-hooks": "workspace:~", "@rocket.chat/fuselage-polyfills": "workspace:~", "@rocket.chat/icons": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@storybook/addon-essentials": "~6.5.15", - "@storybook/addon-interactions": "~6.5.15", - "@storybook/addon-links": "~6.5.15", - "@storybook/addons": "~6.5.15", - "@storybook/builder-webpack5": "~6.5.15", - "@storybook/client-api": "~6.5.15", + "@storybook/addon-a11y": "~6.5.16", + "@storybook/addon-essentials": "~6.5.16", + "@storybook/addon-interactions": "~6.5.16", + "@storybook/addon-links": "~6.5.16", + "@storybook/addon-styling": "~0.3.2", + "@storybook/addons": "~6.5.16", + "@storybook/builder-webpack5": "~6.5.16", + "@storybook/client-api": "~6.5.16", "@storybook/jest": "~0.0.10", - "@storybook/manager-webpack5": "~6.5.15", - "@storybook/react": "~6.5.15", - "@storybook/source-loader": "~6.5.15", + "@storybook/manager-webpack5": "~6.5.16", + "@storybook/react": "~6.5.16", "@storybook/testing-library": "~0.0.13", "@storybook/testing-react": "~1.3.0", - "@storybook/theming": "~6.5.15", + "@storybook/theming": "~6.5.16", "@testing-library/jest-dom": "~5.16.5", - "@testing-library/react": "^12.1.5", + "@testing-library/react": "release-12.x", + "@testing-library/user-event": "~14.4.3", "@types/invariant": "^2.2.35", - "@types/jest": "~27.4.1", - "autoprefixer": "~10.4.13", - "babel-loader": "~8.2.5", + "@types/jest": "~29.5.0", + "@types/jest-axe": "~3.5.5", + "autoprefixer": "~10.4.14", + "babel-loader": "~9.1.2", "bump": "workspace:~", - "caniuse-lite": "~1.0.30001446", + "caniuse-lite": "~1.0.30001477", "cross-env": "^7.0.3", - "css-loader": "~6.6.0", + "css-loader": "~6.7.3", "cssnano": "~5.0.17", - "es-check": "~7.0.1", - "eslint": "~8.26.0", - "eslint-plugin-mdx": "~1.17.1", - "jest": "~27.5.1", + "es-check": "~7.1.1", + "eslint": "~8.38.0", + "eslint-mdx": "~2.0.5", + "eslint-plugin-mdx": "~2.0.5", + "jest": "~29.5.0", + "jest-axe": "~8.0.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", + "mini-css-extract-plugin": "~2.7.6", "normalize.css": "^8.0.1", "npm-run-all": "^4.1.5", "path-browserify": "^1.0.1", "postcss": "~8.4.21", "postcss-custom-properties": "~12.1.11", "postcss-dir-pseudo-class": "~6.0.5", - "postcss-loader": "~6.2.1", + "postcss-loader": "~7.2.4", "postcss-logical": "~5.0.4", "postcss-scss": "~4.0.6", "postcss-svg": "~3.0.0", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "react": "^17.0.2", "react-dom": "^17.0.2", "react-virtuoso": "~3.1.5", + "resolve-url-loader": "~5.0.0", "rimraf": "^3.0.2", - "sass": "~1.49.11", - "sass-loader": "~12.4.0", - "style-loader": "~3.3.1", - "stylelint": "~14.14.1", - "stylelint-order": "~5.0.0", - "stylelint-prettier": "~2.0.0", - "stylelint-scss": "~4.3.0", + "sass": "~1.62.0", + "sass-loader": "~13.2.2", + "storybook-dark-mode": "~3.0.1", + "style-loader": "~3.3.2", + "stylelint": "~15.4.0", + "stylelint-order": "~6.0.3", + "stylelint-prettier": "~3.0.0", + "stylelint-scss": "~4.6.0", "testing-utils": "workspace:~", - "ts-jest": "~27.1.5", + "ts-jest": "~29.1.0", "ts-loader": "~9.4.2", - "typescript": "~4.9.4", - "webpack": "~5.76.0", - "webpack-bundle-analyzer": "~4.7.0", - "webpack-cli": "~4.10.0" + "typescript": "~5.0.4", + "webpack": "~5.78.0", + "webpack-bundle-analyzer": "~4.8.0", + "webpack-cli": "~5.0.1", + "wrapper-webpack-plugin": "~2.2.2" }, "volta": { "extends": "../../package.json" diff --git a/packages/fuselage/src/.storybook/PropsVariation.tsx b/packages/fuselage/src/.storybook/PropsVariation.tsx index 5fc35013f0..e3c25b4065 100644 --- a/packages/fuselage/src/.storybook/PropsVariation.tsx +++ b/packages/fuselage/src/.storybook/PropsVariation.tsx @@ -15,7 +15,7 @@ function PropsVariation({ return ( @@ -40,8 +40,8 @@ function PropsVariation({ key={x} is='td' margin='none' - paddingBlock='x8' - paddingInline='x16' + paddingBlock={8} + paddingInline={16} > diff --git a/packages/fuselage/src/Theme.ts b/packages/fuselage/src/Theme.ts index 0636525b34..9ba09d6611 100644 --- a/packages/fuselage/src/Theme.ts +++ b/packages/fuselage/src/Theme.ts @@ -83,6 +83,7 @@ const success = { const warning = { w100: new Var('warning-100', tokenColors.w100), + w150: new Var('warning-150', tokenColors.w150), w200: new Var('warning-200', tokenColors.w200), w300: new Var('warning-300', tokenColors.w300), w400: new Var('warning-400', tokenColors.w400), @@ -184,7 +185,7 @@ export const statusBackgroundColors = { 'status-background-success': success.s200.theme('status-background-success'), 'status-background-danger': danger.d200.theme('status-background-danger'), 'status-background-warning': warning.w200.theme('status-background-warning'), - 'status-background-warning-2': warning.w100.theme( + 'status-background-warning-2': warning.w150.theme( 'status-background-warning-2' ), 'status-background-service-1': service1['200'].theme( @@ -210,6 +211,7 @@ export const statusColors = { type StatusColors = keyof typeof statusColors; export const badgeBackgroundColors = { + 'badge-background-level-0': neutral.n400.theme('badge-background-level-0'), 'badge-background-level-1': neutral.n600.theme('badge-background-level-1'), 'badge-background-level-2': primary.p550.theme('badge-background-level-2'), 'badge-background-level-3': service1[500].theme('badge-background-level-3'), diff --git a/packages/fuselage/src/components/Accordion/Accordion.stories.tsx b/packages/fuselage/src/components/Accordion/Accordion.stories.tsx index c12dc49161..e78a10b471 100644 --- a/packages/fuselage/src/components/Accordion/Accordion.stories.tsx +++ b/packages/fuselage/src/components/Accordion/Accordion.stories.tsx @@ -19,17 +19,17 @@ export default { const Template: ComponentStory = () => ( - + Content #1 - + Content #2 - + Content #3 diff --git a/packages/fuselage/src/components/Accordion/AccordionItem.tsx b/packages/fuselage/src/components/Accordion/AccordionItem.tsx index fbf72361d1..fddc217c0d 100644 --- a/packages/fuselage/src/components/Accordion/AccordionItem.tsx +++ b/packages/fuselage/src/components/Accordion/AccordionItem.tsx @@ -96,12 +96,13 @@ export const AccordionItem = function Item({ {title && ( - + {title} {!noncollapsible && ( diff --git a/packages/fuselage/src/components/AnimatedVisibility/AnimatedVisibility.stories.tsx b/packages/fuselage/src/components/AnimatedVisibility/AnimatedVisibility.stories.tsx index 58914b8f09..c4d73a1a1f 100644 --- a/packages/fuselage/src/components/AnimatedVisibility/AnimatedVisibility.stories.tsx +++ b/packages/fuselage/src/components/AnimatedVisibility/AnimatedVisibility.stories.tsx @@ -25,7 +25,7 @@ export const example: ComponentStory = ({ visibility, }) => ( - Visible + Visible ); example.args = { diff --git a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx new file mode 100644 index 0000000000..51306195cb --- /dev/null +++ b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx @@ -0,0 +1,40 @@ +import { + Title, + Subtitle, + Description, + Primary as PrimaryStory, + ArgsTable, + Stories, + PRIMARY_STORY, +} from '@storybook/addon-docs'; +import type { ComponentMeta } from '@storybook/react'; +import React from 'react'; + +import { AudioPlayer } from '../..'; + +export default { + title: 'Media/AudioPlayer', + component: AudioPlayer, + parameters: { + docs: { + description: { + component: 'A fuselage`s custom AudioPlayer.', + }, + page: () => ( + <> + + <Subtitle /> + <Description /> + <PrimaryStory /> + <Stories title={''} /> + <ArgsTable story={PRIMARY_STORY} /> + </> + ), + }, + }, +} as ComponentMeta<typeof AudioPlayer>; + +const AUDIO_URL = + 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-17.mp3'; + +export const AudioPlayerDefault = () => <AudioPlayer src={AUDIO_URL} />; diff --git a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx new file mode 100644 index 0000000000..5c9260f1df --- /dev/null +++ b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx @@ -0,0 +1,194 @@ +import { useMergedRefs, useResizeObserver } from '@rocket.chat/fuselage-hooks'; +import React, { useState, useRef, forwardRef } from 'react'; + +import { Box, Button, IconButton, Margins } from '../..'; +import { Slider } from '../Slider'; + +const getMaskTime = (durationTime: number) => + new Date(durationTime * 1000) + .toISOString() + .slice(durationTime > 60 * 60 ? 11 : 14, 19); + +function forceDownload(url: string, fileName?: string) { + const xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'blob'; + xhr.onload = function () { + const urlCreator = window.URL || window.webkitURL; + const imageUrl = urlCreator.createObjectURL(this.response); + const tag = document.createElement('a'); + tag.href = imageUrl; + if (fileName) { + tag.download = fileName; + } + document.body.appendChild(tag); + tag.click(); + document.body.removeChild(tag); + }; + xhr.send(); +} + +export const AudioPlayer = forwardRef< + HTMLAudioElement, + { + src: string; + type?: string; + maxPlaybackSpeed?: number; + minPlaybackSpeed?: number; + playbackSpeedStep?: number; + download?: boolean; + playLabel?: string; + pauseLabel?: string; + audioPlaybackRangeLabel?: string; + changePlaybackSpeedLabel?: string; + downloadAudioFileLabel?: string; + } +>( + ( + { + src, + type = 'audio/mpeg', + maxPlaybackSpeed = 2, + minPlaybackSpeed = 0.5, + playbackSpeedStep = 0.25, + download = false, + playLabel = 'Play', + pauseLabel = 'Pause', + audioPlaybackRangeLabel = 'Audio Playback Range', + changePlaybackSpeedLabel = 'Change Playback Speed', + downloadAudioFileLabel = 'Download Audio File', + }, + ref + ) => { + const audioRef = useRef<HTMLAudioElement>(null); + const refs = useMergedRefs(ref, audioRef); + const [isPlaying, setIsPlaying] = useState(false); + const [currentTime, setCurrentTime] = useState(0); + const [durationTime, setDurationTime] = useState(0); + const [playbackSpeed, setPlaybackSpeed] = useState(1); + const { ref: containerRef } = useResizeObserver(); + + const handlePlay = () => { + const isPlaying = audioRef.current?.paused; + + if (isPlaying) { + audioRef.current?.play(); + } else { + audioRef.current?.pause(); + } + }; + + const handlePlaybackSpeed = (mod: 1 | -1) => { + if (audioRef.current) { + audioRef.current.playbackRate += playbackSpeedStep * mod; + } + }; + + const handlePlaybackSpeedSingleControl = () => { + const reachedMaxPlaybackSpeed = + maxPlaybackSpeed === audioRef?.current?.playbackRate; + + if (reachedMaxPlaybackSpeed) { + audioRef.current.playbackRate = minPlaybackSpeed; + return; + } + handlePlaybackSpeed(1); + }; + + return ( + <Box + borderWidth='default' + bg='tint' + borderColor='extra-light' + pb={12} + pie={8} + pis={16} + borderRadius='x4' + w='100%' + maxWidth='x300' + ref={containerRef} + display='flex' + alignItems='center' + > + <IconButton + primary + medium + onClick={handlePlay} + aria-label={isPlaying ? pauseLabel : playLabel} + icon={isPlaying ? 'pause-shape-filled' : 'play-shape-filled'} + /> + <Margins inline={8}> + <Box fontScale='p2' color='secondary-info'> + {isPlaying || currentTime > 0 + ? getMaskTime(currentTime) + : getMaskTime(durationTime)} + </Box> + <Box mi={16} w='full'> + <Slider + aria-label={audioPlaybackRangeLabel} + showOutput={false} + value={currentTime} + maxValue={durationTime} + onChange={(value) => { + if (audioRef.current) { + audioRef.current.currentTime = value; + } + }} + /> + </Box> + + <Button + secondary + small + onClick={handlePlaybackSpeedSingleControl} + aria-label={changePlaybackSpeedLabel} + > + {playbackSpeed}x + </Button> + </Margins> + {download && ( + <IconButton + primary + aria-label={downloadAudioFileLabel} + is='a' + href={src} + download + icon='download' + medium + onClick={(e) => { + const { host } = new URL(src); + if (host !== window.location.host) { + e.preventDefault(); + forceDownload(src); + } + }} + /> + )} + <audio + style={{ display: 'none' }} + onTimeUpdate={(e) => { + setCurrentTime((e.target as HTMLAudioElement).currentTime); + }} + onLoadedData={(e) => { + setDurationTime((e.target as HTMLAudioElement).duration); + }} + onEnded={() => setIsPlaying(false)} + ref={refs} + preload='metadata' + onRateChange={(e) => { + setPlaybackSpeed((e.target as HTMLAudioElement).playbackRate); + }} + onPlay={() => { + setIsPlaying(true); + }} + onPause={() => { + setIsPlaying(false); + }} + controls + > + <source src={src} type={type} /> + </audio> + </Box> + ); + } +); diff --git a/packages/fuselage/src/components/AudioPlayer/index.ts b/packages/fuselage/src/components/AudioPlayer/index.ts new file mode 100644 index 0000000000..58f3d538fc --- /dev/null +++ b/packages/fuselage/src/components/AudioPlayer/index.ts @@ -0,0 +1 @@ +export * from './AudioPlayer'; diff --git a/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx b/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx index 7e3b322bf2..0de0c3a79a 100644 --- a/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx +++ b/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx @@ -121,14 +121,8 @@ export const AutoCompleteMultipleCustomSelected = () => { options={options} onChange={handleChangeRooms} renderSelected={({ selected: { value, label }, onRemove }) => ( - <Chip - key={value} - height='x20' - value={value} - onClick={onRemove} - mie='x4' - > - <Box is='span' margin='none' mis='x4'> + <Chip key={value} height='x20' value={value} onClick={onRemove} mie={4}> + <Box is='span' margin='none' mis={4}> <Avatar size='x20' url={exampleAvatar} /> {' '} {label} diff --git a/packages/fuselage/src/components/Banner/Banner.tsx b/packages/fuselage/src/components/Banner/Banner.tsx index 5305bf638c..e6fef9e325 100644 --- a/packages/fuselage/src/components/Banner/Banner.tsx +++ b/packages/fuselage/src/components/Banner/Banner.tsx @@ -7,9 +7,7 @@ import type { import React, { useRef, useCallback, useMemo } from 'react'; import { composeClassNames as cx } from '../../helpers/composeClassNames'; -import { useStyleSheet } from '../../hooks/useStyleSheet'; import { IconButton } from '../Button'; -import styleSheet from './Banner.styles.scss'; type VariantType = 'neutral' | 'info' | 'success' | 'warning' | 'danger'; @@ -51,9 +49,6 @@ const Banner = ({ variant = 'neutral', ...props }: BannerProps) => { - useStyleSheet(); - useStyleSheet(styleSheet); - const ref = useRef(null); const { inlineSize } = useBorderBoxSize(ref, { debounceDelay: 70, diff --git a/packages/fuselage/src/components/Box/Box.tsx b/packages/fuselage/src/components/Box/Box.tsx index 113249d146..79ee24ef18 100644 --- a/packages/fuselage/src/components/Box/Box.tsx +++ b/packages/fuselage/src/components/Box/Box.tsx @@ -10,7 +10,6 @@ import type { import { useArrayLikeClassNameProp } from '../../hooks/useArrayLikeClassNameProp'; import { useBoxOnlyProps } from '../../hooks/useBoxOnlyProps'; -import { useStyleSheet } from '../../hooks/useStyleSheet'; import type { Falsy } from '../../types/Falsy'; import { useBoxTransform, BoxTransforms } from './BoxTransforms'; import type { StylingProps } from './stylingProps'; @@ -36,8 +35,6 @@ export const Box = forwardRef(function Box( { is = 'div', children, ...props }: BoxProps, ref: Ref<any> ) { - useStyleSheet(); - const propsWithRef: BoxProps & RefAttributes<any> = props; if (ref) { diff --git a/packages/fuselage/src/components/Box/props.stories.mdx b/packages/fuselage/src/components/Box/props.stories.mdx index 49b387043c..512982b027 100644 --- a/packages/fuselage/src/components/Box/props.stories.mdx +++ b/packages/fuselage/src/components/Box/props.stories.mdx @@ -35,7 +35,7 @@ The `is` prop allows `Box` to render any component capable of handling common DO (fn) => ( <Box display='flex' flexWrap='wrap' alignItems='center'> {flattenChildren(fn().props.children).map((child) => ( - <Box key={child.key} bg='neutral-200' m='x16'> + <Box key={child.key} bg='neutral-200' m={16}> {React.cloneElement( child, { bg: 'primary-200' }, @@ -48,20 +48,20 @@ The `is` prop allows `Box` to render any component capable of handling common DO ]} > <> - <Box m='x16' /> - <Box margin='x16' /> - <Box mb='x16' /> - <Box marginBlock='x16' /> - <Box mbs='x16' /> - <Box marginBlockStart='x16' /> - <Box mbe='x16' /> - <Box marginBlockEnd='x16' /> - <Box mi='x16' /> - <Box marginInline='x16' /> - <Box mis='x16' /> - <Box marginInlineStart='x16' /> - <Box mie='x16' /> - <Box marginInlineEnd='x16' /> + <Box m={16} /> + <Box margin={16} /> + <Box mb={16} /> + <Box marginBlock={16} /> + <Box mbs={16} /> + <Box marginBlockStart={16} /> + <Box mbe={16} /> + <Box marginBlockEnd={16} /> + <Box mi={16} /> + <Box marginInline={16} /> + <Box mis={16} /> + <Box marginInlineStart={16} /> + <Box mie={16} /> + <Box marginInlineEnd={16} /> </> </Story> </Canvas> @@ -75,7 +75,7 @@ The `is` prop allows `Box` to render any component capable of handling common DO (fn) => ( <Box display='flex' flexWrap='wrap' alignItems='center'> {flattenChildren(fn().props.children).map((child) => ( - <Box key={child.key} bg='neutral-200' m='x16'> + <Box key={child.key} bg='neutral-200' m={16}> {React.cloneElement( child, { bg: 'primary-200' }, @@ -88,20 +88,20 @@ The `is` prop allows `Box` to render any component capable of handling common DO ]} > <> - <Box p='x16' /> - <Box padding='x16' /> - <Box pb='x16' /> - <Box paddingBlock='x16' /> - <Box pbs='x16' /> - <Box paddingBlockStart='x16' /> - <Box pbe='x16' /> - <Box paddingBlockEnd='x16' /> - <Box pi='x16' /> - <Box paddingInline='x16' /> - <Box pis='x16' /> - <Box paddingInlineStart='x16' /> - <Box pie='x16' /> - <Box paddingInlineEnd='x16' /> + <Box p={16} /> + <Box padding={16} /> + <Box pb={16} /> + <Box paddingBlock={16} /> + <Box pbs={16} /> + <Box paddingBlockStart={16} /> + <Box pbe={16} /> + <Box paddingBlockEnd={16} /> + <Box pi={16} /> + <Box paddingInline={16} /> + <Box pis={16} /> + <Box paddingInlineStart={16} /> + <Box pie={16} /> + <Box paddingInlineEnd={16} /> </> </Story> </Canvas> @@ -786,7 +786,7 @@ The `is` prop allows `Box` to render any component capable of handling common DO key={child.key} position='relative' bg='neutral-200' - m='x16' + m={16} size='x64' > {React.cloneElement(child, { @@ -857,7 +857,7 @@ The `is` prop allows `Box` to render any component capable of handling common DO (fn) => ( <Box display='flex' flexWrap='wrap' alignItems='center'> {flattenChildren(fn()).map((child) => ( - <Box key={child.key} bg='neutral-200' p='x16'> + <Box key={child.key} bg='neutral-200' p={16}> {React.cloneElement(child, { bg: 'primary-200', size: 'x16' })} </Box> ))} diff --git a/packages/fuselage/src/components/Box/stylingProps.ts b/packages/fuselage/src/components/Box/stylingProps.ts index 9f3a0a036d..89a0d5b06d 100644 --- a/packages/fuselage/src/components/Box/stylingProps.ts +++ b/packages/fuselage/src/components/Box/stylingProps.ts @@ -399,6 +399,8 @@ export const propDefs: Record<keyof StylingProps, PropDefinition> = { 0px 0px 12px 0px ${Palette.shadow['shadow-elevation-2y']}; `; } + + return undefined; }, }, invisible: { diff --git a/packages/fuselage/src/components/Box/withBoxStyling.tsx b/packages/fuselage/src/components/Box/withBoxStyling.tsx index e62965c606..53e1cc16d7 100644 --- a/packages/fuselage/src/components/Box/withBoxStyling.tsx +++ b/packages/fuselage/src/components/Box/withBoxStyling.tsx @@ -1,7 +1,6 @@ import type { ComponentType } from 'react'; import React from 'react'; -import { useStyleSheet } from '../../hooks/useStyleSheet'; import type { StylingProps } from './stylingProps'; import { useStylingProps } from './useStylingProps'; @@ -13,7 +12,6 @@ export const withBoxStyling = < Component: ComponentType<TProps> ): ComponentType<TProps & Partial<StylingProps>> => { const WithBoxStyling = (props: TProps & Partial<StylingProps>) => { - useStyleSheet(); const propsWithoutStylingProps = useStylingProps(props); return <Component {...propsWithoutStylingProps} />; }; diff --git a/packages/fuselage/src/components/Button/Button.stories.tsx b/packages/fuselage/src/components/Button/Button.stories.tsx index 4fcc1f590a..6533a115e8 100644 --- a/packages/fuselage/src/components/Button/Button.stories.tsx +++ b/packages/fuselage/src/components/Button/Button.stories.tsx @@ -80,8 +80,9 @@ export const Variants: ComponentStory<typeof Button> = () => ( export const Sizes: ComponentStory<typeof ButtonGroup> = () => ( <> <ButtonGroup marginBlockEnd={12}> - <Button small>Button</Button> - <Button>Button</Button> + <Button small>Small</Button> + <Button medium>Medium</Button> + <Button>Default</Button> </ButtonGroup> <ButtonGroup> <Button mini square> @@ -94,7 +95,7 @@ export const Sizes: ComponentStory<typeof ButtonGroup> = () => ( <Icon name='circled-arrow-down' size='x24' /> </Button> <Button square> - <Icon name='circled-arrow-down' size='x20' /> + <Icon name='circled-arrow-down' size='x32' /> </Button> </ButtonGroup> </> @@ -124,11 +125,8 @@ export const States = () => ( children: <Icon name='circled-arrow-down' size='x20' />, }, 'icon + text': { - children: ( - <> - <Icon name='baloon-text' size='x16' /> Button - </> - ), + children: 'Button', + icon: 'baloon-text', }, 'text': { children: 'Button', @@ -189,11 +187,8 @@ export const States = () => ( children: <Icon name='circled-arrow-down' size='x20' />, }, 'icon + text': { - children: ( - <> - <Icon name='baloon-text' size='x16' /> Button - </> - ), + children: 'Button', + icon: 'baloon-text', }, 'text': { children: 'Button', diff --git a/packages/fuselage/src/components/Button/Button.styles.scss b/packages/fuselage/src/components/Button/Button.styles.scss index 59e3f9c40f..44ee124d34 100644 --- a/packages/fuselage/src/components/Button/Button.styles.scss +++ b/packages/fuselage/src/components/Button/Button.styles.scss @@ -1,19 +1,29 @@ @use 'sass:map'; +@use '../../styles/lengths.scss'; @use '../../styles/typography.scss'; @use '../../styles/variables/buttons.scss' as colors; @use '../../styles/primitives/button.scss'; +@use '../../styles/mixins/size.scss'; +@import '../../styles/mixins/states.scss'; .rcx-button { @mixin with-rectangular-size($height, $padding-x, $line-height) { - padding: calc((#{$height} - #{$line-height}) / 2 - 2px) - calc(#{$padding-x} - 2px); - padding-block: calc((#{$height} - #{$line-height}) / 2 - 2px); - padding-inline: calc(#{$padding-x} - 2px); + min-width: calc(lengths.size($height) * 2); + padding: calc((lengths.padding($height) - $line-height) / 2 - 2px) + calc(lengths.padding($padding-x) - 2px); + padding-block: calc((lengths.padding($height) - $line-height) / 2 - 2px); + + padding-inline: calc(lengths.padding($padding-x) - 2px); + + @include on-active { + @include size.resize(0.975); + } } @mixin with-squared-size($size) { - width: $size; - height: $size; + width: lengths.size($size); + min-width: lengths.size($size); + height: lengths.size($size); padding: 0; &::before, @@ -23,14 +33,14 @@ height: 100%; content: ''; - vertical-align: middle; } } display: inline-block; + transition: all 0.18s ease-out; + text-align: center; - vertical-align: middle; white-space: nowrap; text-decoration: none; @@ -39,8 +49,8 @@ @include typography.use-text-ellipsis; @include with-rectangular-size( - $height: 40px, - $padding-x: 16px, + $height: 40, + $padding-x: 16, $line-height: typography.line-height(p2) ); @@ -50,14 +60,34 @@ @include typography.use-font-scale(c2); @include with-rectangular-size( - $height: 28px, - $padding-x: 8px, + $height: 28, + $padding-x: 8, + $line-height: typography.line-height(c1) + ); + } + + &--medium { + @include typography.use-font-scale(c2); + + @include with-rectangular-size( + $height: 32, + $padding-x: 12, $line-height: typography.line-height(c1) ); } + &--large { + @include typography.use-font-scale(p2); + + @include with-rectangular-size( + $height: 48, + $padding-x: 24, + $line-height: typography.line-height(p2) + ); + } + &--square { - @include with-squared-size($size: 40px); + @include with-squared-size($size: 40); display: flex; justify-content: center; align-items: center; @@ -66,6 +96,11 @@ &--icon { @include button.kind-variant(colors.$icon); + + @include on-pressed { + @include size.resize(0.975); + } + padding: 0; line-height: 0; @@ -107,16 +142,24 @@ } } - &--tiny-square { - @include with-squared-size($size: 24px); + &--mini-square { + @include with-squared-size($size: 20); } - &--mini-square { - @include with-squared-size($size: 20px); + &--tiny-square { + @include with-squared-size($size: 24); } &--small-square { - @include with-squared-size($size: 28px); + @include with-squared-size($size: 28); + } + + &--medium-square { + @include with-squared-size($size: 32); + } + + &--large-square { + @include with-squared-size($size: 40); } &--primary { diff --git a/packages/fuselage/src/components/Button/Button.tsx b/packages/fuselage/src/components/Button/Button.tsx index aa9ae1dc73..5097ff0f62 100644 --- a/packages/fuselage/src/components/Button/Button.tsx +++ b/packages/fuselage/src/components/Button/Button.tsx @@ -2,6 +2,7 @@ import type { ComponentProps, Ref } from 'react'; import React, { forwardRef, useMemo } from 'react'; import Box from '../Box'; +import { Icon } from '../Icon'; export type ButtonProps = ComponentProps<typeof Box> & { primary?: boolean; @@ -10,11 +11,14 @@ export type ButtonProps = ComponentProps<typeof Box> & { warning?: boolean; success?: boolean; disabled?: boolean; - small?: boolean; mini?: boolean; tiny?: boolean; + small?: boolean; + medium?: boolean; + large?: boolean; square?: boolean; external?: boolean; + icon?: ComponentProps<typeof Icon>['name']; }; export const Button = forwardRef(function Button( @@ -25,12 +29,16 @@ export const Button = forwardRef(function Button( warning, success, external, + icon, is = 'button', rel: _rel, - small, tiny, mini, + small, + medium, + large, square, + children, ...props }: ButtonProps, ref: Ref<HTMLElement> @@ -67,20 +75,26 @@ export const Button = forwardRef(function Button( return ( <Box - animated is={is} type='button' rcx-button {...kindAndVariantProps} rcx-button--small={small} + rcx-button--medium={medium} + rcx-button--large={large} rcx-button--square={square} - rcx-button--small-square={small && square} rcx-button--tiny-square={tiny && square} rcx-button--mini-square={mini && square} + rcx-button--small-square={small && square} + rcx-button--medium-square={medium && square} + rcx-button--large-square={large && square} ref={ref} {...extraProps} {...props} - /> + > + {icon && <Icon size='x16' name={icon} mie={4} />} + {children} + </Box> ); }); diff --git a/packages/fuselage/src/components/Button/IconButton.stories.tsx b/packages/fuselage/src/components/Button/IconButton.stories.tsx index 5a6c783b58..c0eb684e6d 100644 --- a/packages/fuselage/src/components/Button/IconButton.stories.tsx +++ b/packages/fuselage/src/components/Button/IconButton.stories.tsx @@ -10,9 +10,21 @@ import { import type { ComponentStory, ComponentMeta } from '@storybook/react'; import React from 'react'; +import { PropsVariationSection } from '../../../.storybook/helpers'; import { ButtonGroup } from '../ButtonGroup'; import { IconButton } from './IconButton'; +const EmojiElement = ( + <div className='rcx-box rcx-box--full'> + <span + className='emojione emojione-diversity _1f918-1f3fe' + title=':metal_tone4:' + > + 🤘🏾 + </span> + </div> +); + export default { title: 'Inputs/IconButton', component: IconButton, @@ -35,99 +47,166 @@ export default { export const _IconButton: ComponentStory<typeof IconButton> = () => ( <IconButton icon='balloon' /> ); -export const _IconButtonDisabled: ComponentStory<typeof IconButton> = () => ( - <IconButton icon='balloon' disabled /> + +export const States = () => ( + <> + <PropsVariationSection + component={IconButton} + common={{ + icon: 'doner', + medium: true, + }} + xAxis={{ + default: {}, + hover: { className: 'hover' }, + active: { className: 'active' }, + focus: { className: 'focus focus-visible' }, + pressed: { pressed: true }, + disabled: { disabled: true }, + }} + yAxis={{ + default: {}, + info: { + info: true, + }, + danger: { + danger: true, + }, + emoji: { + icon: EmojiElement, + }, + }} + /> + {/* <Divider /> + <PropsVariationSection + component={IconButton} + common={{ + icon: 'doner', + large: true, + }} + xAxis={{ + large: {}, + hover: { className: 'hover' }, + active: { className: 'active' }, + focus: { className: 'focus focus-visible' }, + pressed: { pressed: true }, + disabled: { disabled: true }, + }} + yAxis={{ + default: { + }, + info: { + info: true, + }, + danger: { + danger: true, + }, + emoji: { + icon: EmojiElement, + }, + }} + /> */} + </> ); export const Variants = () => ( <> - <ButtonGroup> - <IconButton icon='balloon' small /> - <IconButton icon='balloon' secondary small /> - <IconButton icon='balloon' info small /> - <IconButton icon='balloon' secondary info small /> - <IconButton icon='balloon' success small /> - <IconButton icon='balloon' secondary success small /> - <IconButton icon='balloon' warning small /> - <IconButton icon='balloon' secondary warning small /> - <IconButton icon='balloon' danger small /> - <IconButton icon='balloon' secondary danger small /> - </ButtonGroup> - <ButtonGroup> - <IconButton icon='balloon' disabled small /> - <IconButton icon='balloon' disabled secondary small /> - <IconButton icon='balloon' disabled info small /> - <IconButton icon='balloon' disabled secondary info small /> - <IconButton icon='balloon' disabled success small /> - <IconButton icon='balloon' disabled secondary success small /> - <IconButton icon='balloon' disabled warning small /> - <IconButton icon='balloon' disabled secondary warning small /> - <IconButton icon='balloon' disabled danger small /> - <IconButton icon='balloon' disabled secondary danger small /> - </ButtonGroup> + <PropsVariationSection + component={IconButton} + common={{ + icon: 'doner', + medium: true, + }} + xAxis={{ + default: {}, + hover: { className: 'hover' }, + active: { className: 'active' }, + focus: { className: 'focus focus-visible' }, + disabled: { disabled: true }, + }} + yAxis={{ + 'default': {}, + 'info': { + info: true, + }, + 'danger': { + danger: true, + }, + 'success': { + success: true, + }, + 'warning': { + warning: true, + }, + 'secondary': { + secondary: true, + }, + 'secondary-info | primary': { + info: true, + secondary: true, + }, + 'secondary-danger': { + danger: true, + secondary: true, + }, + 'secondary-success': { + success: true, + secondary: true, + }, + 'secondary-warning': { + warning: true, + secondary: true, + }, + }} + /> </> ); export const Sizes = () => ( <ButtonGroup> - <IconButton icon='balloon' secondary info small /> - <IconButton icon='balloon' secondary info tiny /> - <IconButton icon='balloon' secondary info mini /> + <IconButton icon='balloon' secondary /> + <IconButton icon='balloon' secondary medium /> + <IconButton icon='balloon' secondary small /> + <IconButton icon='balloon' secondary tiny /> + <IconButton icon='balloon' secondary mini /> </ButtonGroup> ); +export const _IconButtonDisabled: ComponentStory<typeof IconButton> = () => ( + <IconButton icon='balloon' disabled /> +); + +export const _IconButtonWithEmoji: ComponentStory<typeof IconButton> = () => ( + <IconButton icon={EmojiElement} /> +); export const _IconButtonInfo: ComponentStory<typeof IconButton> = () => ( <IconButton icon='balloon' info /> ); -export const _IconButtonInfoDisabled: ComponentStory< - typeof IconButton -> = () => <IconButton icon='balloon' info disabled />; export const _IconButtonSecondaryInfo: ComponentStory< typeof IconButton > = () => <IconButton icon='balloon' secondary info />; -export const _IconButtonSecondaryInfoDisabled: ComponentStory< - typeof IconButton -> = () => <IconButton icon='balloon' secondary info disabled />; export const _IconButtonSuccess: ComponentStory<typeof IconButton> = () => ( <IconButton icon='balloon' success /> ); -export const _IconButtonSuccessDisabled: ComponentStory< - typeof IconButton -> = () => <IconButton icon='balloon' success disabled />; export const _IconButtonSecondarySuccess: ComponentStory< typeof IconButton > = () => <IconButton icon='balloon' secondary success />; -export const _IconButtonSecondarySuccessDisabled: ComponentStory< - typeof IconButton -> = () => <IconButton icon='balloon' secondary success disabled />; export const _IconButtonWarning: ComponentStory<typeof IconButton> = () => ( <IconButton icon='balloon' warning /> ); -export const _IconButtonWarningDisabled: ComponentStory< - typeof IconButton -> = () => <IconButton icon='balloon' warning disabled />; export const _IconButtonSecondaryWarning: ComponentStory< typeof IconButton > = () => <IconButton icon='balloon' secondary warning />; -export const _IconButtonSecondaryWarningDisabled: ComponentStory< - typeof IconButton -> = () => <IconButton icon='balloon' secondary warning disabled />; export const _IconButtonDanger: ComponentStory<typeof IconButton> = () => ( <IconButton icon='balloon' danger /> ); -export const _IconButtonDangerDisabled: ComponentStory< - typeof IconButton -> = () => <IconButton icon='balloon' danger disabled />; export const _IconButtonSecondaryDanger: ComponentStory< typeof IconButton > = () => <IconButton icon='balloon' secondary danger />; -export const _IconButtonSecondaryDangerDisabled: ComponentStory< - typeof IconButton -> = () => <IconButton icon='balloon' secondary danger disabled />; diff --git a/packages/fuselage/src/components/Button/IconButton.tsx b/packages/fuselage/src/components/Button/IconButton.tsx index 4a0d2e4694..05293e97ca 100644 --- a/packages/fuselage/src/components/Button/IconButton.tsx +++ b/packages/fuselage/src/components/Button/IconButton.tsx @@ -1,67 +1,114 @@ -import type { ComponentProps, ReactNode, Ref } from 'react'; -import React, { useMemo, forwardRef } from 'react'; +import type { ComponentProps, ReactElement, Ref } from 'react'; +import React, { isValidElement, useMemo, forwardRef } from 'react'; import Box from '../Box'; import { Icon } from '../Icon'; type ButtonSize = { - mini?: boolean; - tiny?: boolean; + large?: boolean; + medium?: boolean; small?: boolean; + tiny?: boolean; + mini?: boolean; }; type IconButtonProps = { - icon: ComponentProps<typeof Icon>['name']; - children?: ReactNode; + icon: ComponentProps<typeof Icon>['name'] | ReactElement; primary?: boolean; secondary?: boolean; info?: boolean; danger?: boolean; warning?: boolean; success?: boolean; + pressed?: boolean; } & ButtonSize & ComponentProps<typeof Box>; -const getSize = ({ mini }: ButtonSize) => (mini ? 'x16' : 'x20'); +const getVariantClass = (variant: string) => { + if (variant) { + const variantClass = [ + `rcx-button--icon-${[variant].filter(Boolean).join('-')}`, + ]; + return variantClass; + } + return ['']; +}; + +const getPressedClass = (variant: string) => { + const variantClass = [ + `rcx-button--icon-${[variant].filter(Boolean).join('-')}-pressed`, + ]; + return variantClass; +}; export const IconButton = forwardRef( ( { icon, - children, primary, info, secondary, danger, warning, success, - small, - tiny, mini, + large, + tiny, + small, + medium, + pressed, + children, ...props }: IconButtonProps, ref: Ref<HTMLElement> ) => { - const kindAndVariantProps = useMemo(() => { - const variant = - (secondary && info && 'secondary-info') || + const variant = useMemo( + () => (secondary && danger && 'secondary-danger') || (secondary && warning && 'secondary-warning') || (secondary && success && 'secondary-success') || - ((primary || info) && 'info') || + (secondary && info && 'secondary-info') || + (info && 'info') || (success && 'success') || (warning && 'warning') || (danger && 'danger') || - (secondary && 'secondary'); + (primary && 'secondary-info') || + (secondary && 'secondary') || + '', + [danger, info, primary, secondary, success, warning] + ); + const kindAndVariantProps = useMemo(() => { + const variantProp = {} as any; if (variant) { - return { - [`rcx-button--icon-${[variant].filter(Boolean).join('-')}`]: true, - }; + variantProp[`${getVariantClass(variant)}`] = true; + } + if (pressed) { + variantProp[`${getPressedClass(variant)}`] = true; } + return variantProp; + }, [variant, pressed]); + + const size = useMemo( + () => + (mini && 'mini') || + (tiny && 'tiny') || + (small && 'small') || + (medium && 'medium') || + (large && 'large') || + 'large', + [medium, mini, small, tiny, large] + ); + + const getSizeClass = () => ({ [`rcx-button--${size}-square`]: true }); - return {}; - }, [primary, info, secondary, danger, warning, success]); + const getIconSize = () => + (large && 'x28') || + (medium && 'x24') || + (small && 'x20') || + (tiny && 'x16') || + (mini && 'x12') || + 'x28'; return ( <Box @@ -71,14 +118,20 @@ export const IconButton = forwardRef( rcx-button--icon rcx-button--square {...kindAndVariantProps} - rcx-button--small-square={small} - rcx-button--tiny-square={tiny} - rcx-button--mini-square={mini} + {...getSizeClass()} + rcx-button--icon-pressed={pressed} ref={ref} {...props} > {children} - <Icon name={icon} size={getSize({ mini })} /> + {isValidElement(icon) ? ( + icon + ) : ( + <Icon + name={icon as ComponentProps<typeof Icon>['name']} + size={getIconSize()} + /> + )} </Box> ); } diff --git a/packages/fuselage/src/components/ButtonGroup/ButtonGroup.styles.scss b/packages/fuselage/src/components/ButtonGroup/ButtonGroup.styles.scss index 6d2a819dca..713652f656 100644 --- a/packages/fuselage/src/components/ButtonGroup/ButtonGroup.styles.scss +++ b/packages/fuselage/src/components/ButtonGroup/ButtonGroup.styles.scss @@ -39,11 +39,11 @@ .rcx-button-group__item { margin-inline: lengths.margin(4); - &:first-child { + &:first-of-type { margin-inline-start: lengths.margin(none); } - &:last-child { + &:last-of-type { margin-inline-end: lengths.margin(none); } diff --git a/packages/fuselage/src/components/Callout/Callout.spec.tsx b/packages/fuselage/src/components/Callout/Callout.spec.tsx index 3389a93561..9f7936fe0f 100644 --- a/packages/fuselage/src/components/Callout/Callout.spec.tsx +++ b/packages/fuselage/src/components/Callout/Callout.spec.tsx @@ -17,7 +17,7 @@ describe('[Callout Component]', () => { ['Success', Success], ['Warning', Warning], ['Danger', Danger], - ])('renders %p story without crashing', (storyName, Story) => { + ])('renders %p story without crashing', (_storyName, Story) => { render(<Story />); }); @@ -26,10 +26,13 @@ describe('[Callout Component]', () => { ['.rcx-callout--type-success', 'success', Success], ['.rcx-callout--type-warning', 'warning', Warning], ['.rcx-callout--type-danger', 'danger', Danger], - ])('should have class %p when type is %p', (className, typeName, Story) => { - const { container } = render(<Story />); - expect(container.querySelector(className)).toBeInTheDocument(); - }); + ])( + 'should have class %p when type is %p', + (className, _typeName, Story) => { + const { container } = render(<Story />); + expect(container.querySelector(className)).toBeInTheDocument(); + } + ); }); it('should show title when this property is passed', () => { diff --git a/packages/fuselage/src/components/Contextualbar/Contextualbar.stories.tsx b/packages/fuselage/src/components/Contextualbar/Contextualbar.stories.tsx new file mode 100644 index 0000000000..1cc1fb81a9 --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/Contextualbar.stories.tsx @@ -0,0 +1,91 @@ +import type { ComponentStory, ComponentMeta } from '@storybook/react'; +import React from 'react'; + +import { + Contextualbar, + ContextualbarAction, + ContextualbarActions, + ContextualbarButton, + ContextualbarContent, + ContextualbarEmptyContent, + ContextualbarFooter, + ContextualbarHeader, + ContextualbarSkeleton, + ContextualbarTitle, +} from '.'; +import { Button, ButtonGroup, IconButton, Box } from '..'; + +export default { + title: 'Containers/Contextualbar', + component: Contextualbar, + parameters: { + docs: { + description: { + component: `The \`Contextualbar\` has the purpose to persist and input information about the scope of the related page. + `, + }, + }, + }, + decorators: [ + (storyFn) => ( + <Box width='x400' elevation='2'> + {storyFn()} + </Box> + ), + ], +} as ComponentMeta<typeof Contextualbar>; + +export const Default: ComponentStory<typeof Contextualbar> = () => ( + <Contextualbar position='static' height='x540'> + <ContextualbarHeader> + <ContextualbarAction name='chevron-right' /> + <ContextualbarAction title='Back' name='arrow-back' /> + <ContextualbarTitle>Contextualbar Title</ContextualbarTitle> + <ContextualbarActions> + <ContextualbarAction + title='Title' + name='new-window' + onClick={() => {}} + /> + <ContextualbarAction + name='add-user' + onClick={() => console.log('close')} + /> + </ContextualbarActions> + </ContextualbarHeader> + <ContextualbarContent>Contextualbar Content</ContextualbarContent> + <ContextualbarFooter> + <ButtonGroup> + <ContextualbarButton width='full' secondary> + Cancel + </ContextualbarButton> + <Button width='full' primary> + Save + </Button> + <IconButton icon='menu' /> + </ButtonGroup> + </ContextualbarFooter> + </Contextualbar> +); + +export const Skeleton: ComponentStory<typeof Contextualbar> = () => ( + <ContextualbarSkeleton position='static' height='x540' /> +); + +export const Empty: ComponentStory<typeof Contextualbar> = () => ( + <Contextualbar position='static' height='x540'> + <ContextualbarHeader> + <ContextualbarAction name='chevron-right' /> + <ContextualbarTitle>Contextualbar Empty</ContextualbarTitle> + <ContextualbarActions> + <ContextualbarAction + title='Title' + name='new-window' + onClick={() => {}} + /> + </ContextualbarActions> + </ContextualbarHeader> + <ContextualbarEmptyContent /> + <ContextualbarFooter>Footer</ContextualbarFooter> + </Contextualbar> +); diff --git a/packages/fuselage/src/components/Contextualbar/Contextualbar.tsx b/packages/fuselage/src/components/Contextualbar/Contextualbar.tsx new file mode 100644 index 0000000000..9308e5e25b --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/Contextualbar.tsx @@ -0,0 +1,36 @@ +import type { ComponentProps } from 'react'; +import React, { memo } from 'react'; + +import { Box } from '..'; + +type ContextualbarProps = ComponentProps<typeof Box>; + +const Contextualbar = ({ + children, + width, + position, + bg = 'room', + ...props +}: ContextualbarProps) => ( + <Box + rcx-vertical-bar + bg={bg} + display='flex' + flexDirection='column' + flexShrink={0} + width={width} + borderInlineStartWidth='default' + borderInlineStartColor='extra-light' + borderInlineStartStyle='solid' + height='full' + position={position} + insetInlineEnd='none' + insetBlockStart='none' + zIndex={5} + {...props} + > + {children} + </Box> +); + +export default memo(Contextualbar); diff --git a/packages/fuselage/src/components/Contextualbar/ContextualbarAction.tsx b/packages/fuselage/src/components/Contextualbar/ContextualbarAction.tsx new file mode 100644 index 0000000000..9b2e7f91e7 --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/ContextualbarAction.tsx @@ -0,0 +1,18 @@ +import type { ReactElement, ComponentProps } from 'react'; +import React, { memo } from 'react'; + +import type { Icon } from '..'; +import { IconButton } from '..'; + +type ContextualbarActionProps = { + name: ComponentProps<typeof Icon>['name']; +} & Omit<ComponentProps<typeof IconButton>, 'icon'>; + +const ContextualbarAction = ({ + name, + ...props +}: ContextualbarActionProps): ReactElement => ( + <IconButton flexShrink={0} icon={name} {...props} tiny /> +); + +export default memo(ContextualbarAction); diff --git a/packages/fuselage/src/components/Contextualbar/ContextualbarActions.tsx b/packages/fuselage/src/components/Contextualbar/ContextualbarActions.tsx new file mode 100644 index 0000000000..56c80dfe53 --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/ContextualbarActions.tsx @@ -0,0 +1,10 @@ +import type { ReactElement, ComponentProps } from 'react'; +import React, { memo } from 'react'; + +import { ButtonGroup } from '..'; + +const ContextualbarActions = ( + props: ComponentProps<typeof ButtonGroup> +): ReactElement => <ButtonGroup {...props} />; + +export default memo(ContextualbarActions); diff --git a/packages/fuselage/src/components/Contextualbar/ContextualbarButton.tsx b/packages/fuselage/src/components/Contextualbar/ContextualbarButton.tsx new file mode 100644 index 0000000000..3e9cdc53d1 --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/ContextualbarButton.tsx @@ -0,0 +1,10 @@ +import type { ComponentProps, ReactElement } from 'react'; +import React, { memo } from 'react'; + +import { Button } from '..'; + +const ContextualbarButton = ( + props: ComponentProps<typeof Button> +): ReactElement => <Button {...props} />; + +export default memo(ContextualbarButton); diff --git a/packages/fuselage/src/components/Contextualbar/ContextualbarContent.tsx b/packages/fuselage/src/components/Contextualbar/ContextualbarContent.tsx new file mode 100644 index 0000000000..112d0aff41 --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/ContextualbarContent.tsx @@ -0,0 +1,24 @@ +import type { ComponentProps } from 'react'; +import React, { forwardRef, memo } from 'react'; + +import { Box } from '..'; + +const ContextualbarContent = forwardRef< + HTMLElement, + ComponentProps<typeof Box> +>(function ContextualbarContent(props, ref) { + return ( + <Box + ref={ref} + rcx-vertical-bar__content + paddingInline={24} + display='flex' + flexDirection='column' + overflowY='hidden' + height='full' + {...props} + /> + ); +}); + +export default memo(ContextualbarContent); diff --git a/packages/fuselage/src/components/Contextualbar/ContextualbarEmptyContent.tsx b/packages/fuselage/src/components/Contextualbar/ContextualbarEmptyContent.tsx new file mode 100644 index 0000000000..6e66941fb7 --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/ContextualbarEmptyContent.tsx @@ -0,0 +1,32 @@ +import type { ComponentProps } from 'react'; +import React, { forwardRef, memo } from 'react'; + +import type { Box } from '..'; +import { StatesIcon, States, StatesTitle, StatesSubtitle } from '..'; +import ContextualbarContent from './ContextualbarContent'; + +type ContextualbarEmptyContentProps = ComponentProps<typeof Box> & { + icon?: ComponentProps<typeof StatesIcon>['name']; + title?: string; + subtitle?: string; +}; + +const ContextualbarEmptyContent = forwardRef< + HTMLElement, + ContextualbarEmptyContentProps +>(function ContextualbarEmptyContent( + { icon = 'magnifier', title = 'Nothing Found', subtitle, ...props }, + ref +) { + return ( + <ContextualbarContent justifyContent='center' {...props} ref={ref}> + <States> + <StatesIcon name={icon} /> + <StatesTitle>{title}</StatesTitle> + {subtitle && <StatesSubtitle>{subtitle}</StatesSubtitle>} + </States> + </ContextualbarContent> + ); +}); + +export default memo(ContextualbarEmptyContent); diff --git a/packages/fuselage/src/components/Contextualbar/ContextualbarFooter.tsx b/packages/fuselage/src/components/Contextualbar/ContextualbarFooter.tsx new file mode 100644 index 0000000000..fab42762db --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/ContextualbarFooter.tsx @@ -0,0 +1,16 @@ +import type { ComponentProps } from 'react'; +import React, { forwardRef, memo } from 'react'; + +import { Box } from '..'; + +const ContextualbarFooter = forwardRef<HTMLElement, ComponentProps<typeof Box>>( + function ContextualbarFooter({ children, ...props }, ref) { + return ( + <Box is='footer' p={24} {...props} ref={ref}> + {children} + </Box> + ); + } +); + +export default memo(ContextualbarFooter); diff --git a/packages/fuselage/src/components/Contextualbar/ContextualbarHeader.tsx b/packages/fuselage/src/components/Contextualbar/ContextualbarHeader.tsx new file mode 100644 index 0000000000..b6a00560ae --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/ContextualbarHeader.tsx @@ -0,0 +1,39 @@ +import type { ComponentProps } from 'react'; +import React, { memo } from 'react'; + +import Box from '../Box'; +import Margins from '../Margins'; + +type ContextualbarHeaderProps = ComponentProps<typeof Box>; + +const ContextualbarHeader = ({ + children, + ...props +}: ContextualbarHeaderProps) => ( + <Box + display='flex' + alignItems='center' + height='x56' + is='h3' + pi={24} + borderBlockEndWidth='default' + borderBlockColor='extra-light' + flexShrink={0} + {...props} + > + <Box + marginInline='neg-x4' + display='flex' + alignItems='center' + justifyContent='space-between' + fontScale='h4' + flexGrow={1} + overflow='hidden' + color='default' + > + <Margins inline='x4'>{children}</Margins> + </Box> + </Box> +); + +export default memo(ContextualbarHeader); diff --git a/packages/fuselage/src/components/Contextualbar/ContextualbarIcon.tsx b/packages/fuselage/src/components/Contextualbar/ContextualbarIcon.tsx new file mode 100644 index 0000000000..629dd9d39f --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/ContextualbarIcon.tsx @@ -0,0 +1,10 @@ +import type { ReactElement, ComponentProps } from 'react'; +import React, { memo } from 'react'; + +import { Icon } from '..'; + +const ContextualbarIcon = ( + props: ComponentProps<typeof Icon> +): ReactElement => <Icon {...props} pi={2} size='x24' />; + +export default memo(ContextualbarIcon); diff --git a/packages/fuselage/src/components/Contextualbar/ContextualbarSkeleton.tsx b/packages/fuselage/src/components/Contextualbar/ContextualbarSkeleton.tsx new file mode 100644 index 0000000000..32ea63c0e9 --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/ContextualbarSkeleton.tsx @@ -0,0 +1,25 @@ +import type { ReactElement, ComponentProps } from 'react'; +import React, { memo } from 'react'; + +import { Contextualbar, ContextualbarHeader } from '.'; +import { Box, Skeleton } from '..'; + +const ContextualbarSkeleton = ( + props: ComponentProps<typeof Box> +): ReactElement => ( + <Contextualbar {...props} width='100%'> + <ContextualbarHeader> + <Skeleton width='100%' /> + </ContextualbarHeader> + <Box p={24}> + <Skeleton mbe={4} width='32px' height='32px' variant='rect' /> + {Array(5) + .fill(5) + .map((_, index) => ( + <Skeleton key={index} /> + ))} + </Box> + </Contextualbar> +); + +export default memo(ContextualbarSkeleton); diff --git a/packages/fuselage/src/components/Contextualbar/ContextualbarTitle.tsx b/packages/fuselage/src/components/Contextualbar/ContextualbarTitle.tsx new file mode 100644 index 0000000000..32d7117ef1 --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/ContextualbarTitle.tsx @@ -0,0 +1,12 @@ +import type { ReactElement, ComponentProps } from 'react'; +import React, { memo } from 'react'; + +import { Box } from '..'; + +const ContextualbarTitle = ( + props: ComponentProps<typeof Box> +): ReactElement => ( + <Box flexShrink={1} flexGrow={1} withTruncatedText {...props} /> +); + +export default memo(ContextualbarTitle); diff --git a/packages/fuselage/src/components/Contextualbar/index.ts b/packages/fuselage/src/components/Contextualbar/index.ts new file mode 100644 index 0000000000..9d21b8c6bf --- /dev/null +++ b/packages/fuselage/src/components/Contextualbar/index.ts @@ -0,0 +1,25 @@ +import Contextualbar from './Contextualbar'; +import ContextualbarAction from './ContextualbarAction'; +import ContextualbarActions from './ContextualbarActions'; +import ContextualbarButton from './ContextualbarButton'; +import ContextualbarContent from './ContextualbarContent'; +import ContextualbarEmptyContent from './ContextualbarEmptyContent'; +import ContextualbarFooter from './ContextualbarFooter'; +import ContextualbarHeader from './ContextualbarHeader'; +import ContextualbarIcon from './ContextualbarIcon'; +import ContextualbarSkeleton from './ContextualbarSkeleton'; +import ContextualbarTitle from './ContextualbarTitle'; + +export { + Contextualbar, + ContextualbarAction, + ContextualbarActions, + ContextualbarButton, + ContextualbarContent, + ContextualbarEmptyContent, + ContextualbarFooter, + ContextualbarHeader, + ContextualbarIcon, + ContextualbarSkeleton, + ContextualbarTitle, +}; diff --git a/packages/fuselage/src/components/Dropdown/Dropdown.spec.tsx b/packages/fuselage/src/components/Dropdown/Dropdown.spec.tsx index acd9fb47fe..bf7b52022b 100644 --- a/packages/fuselage/src/components/Dropdown/Dropdown.spec.tsx +++ b/packages/fuselage/src/components/Dropdown/Dropdown.spec.tsx @@ -17,15 +17,15 @@ describe('[Dropdown Component]', () => { it('should show dropdown when anchor is clicked once', async () => { const { getByTestId } = render(<Default {...Default.args} />); const anchor = getByTestId('dropdown-anchor'); - userEvent.click(anchor); + await userEvent.click(anchor); expect(await screen.findByTestId('dropdown')).toBeInTheDocument(); }); it('should hide dropdown when anchor is clicked twice', async () => { const { getByTestId } = render(<Default {...Default.args} />); const anchor = getByTestId('dropdown-anchor'); - userEvent.click(anchor); - userEvent.click(anchor); + await userEvent.click(anchor); + await userEvent.click(anchor); expect(dropdownOption).toBeNull(); }); }); diff --git a/packages/fuselage/src/components/Dropdown/Dropdown.tsx b/packages/fuselage/src/components/Dropdown/Dropdown.tsx index 61c1fd9d0c..f8a124f951 100644 --- a/packages/fuselage/src/components/Dropdown/Dropdown.tsx +++ b/packages/fuselage/src/components/Dropdown/Dropdown.tsx @@ -1,9 +1,9 @@ -import type { usePosition } from '@rocket.chat/fuselage-hooks'; +import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks'; import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; import type { ReactNode, Ref, RefObject } from 'react'; import React, { forwardRef } from 'react'; -import { DropdownDesktop } from './DropdownDesktop'; +import { DropdownDesktopWrapper } from './DropdownDesktopWrapper'; import { DropdownMobile } from './DropdownMobile'; export const Dropdown = forwardRef(function Dropdown< @@ -16,7 +16,7 @@ export const Dropdown = forwardRef(function Dropdown< placement = 'bottom-start', }: { reference: RefObject<T>; - placement?: Parameters<typeof usePosition>[2]['placement']; + placement?: UsePositionOptions['placement']; children: ReactNode; }, ref: Ref<R> @@ -24,7 +24,7 @@ export const Dropdown = forwardRef(function Dropdown< const notSmall = useMediaQuery('(min-width: 500px)'); return notSmall ? ( - <DropdownDesktop + <DropdownDesktopWrapper reference={reference} children={children} placement={placement} diff --git a/packages/fuselage/src/components/Dropdown/DropdownDesktop.tsx b/packages/fuselage/src/components/Dropdown/DropdownDesktop.tsx index 004db72fdd..33a4c768f0 100644 --- a/packages/fuselage/src/components/Dropdown/DropdownDesktop.tsx +++ b/packages/fuselage/src/components/Dropdown/DropdownDesktop.tsx @@ -1,26 +1,22 @@ -import { usePosition } from '@rocket.chat/fuselage-hooks'; -import type { ReactNode, Ref, RefObject } from 'react'; +import type { CSSProperties, ReactNode, Ref } from 'react'; import React, { forwardRef } from 'react'; import { Box, Tile } from '..'; export const DropdownDesktop = forwardRef(function DropdownDesktop< - T extends HTMLElement, R extends HTMLElement >( { children, - reference, - placement = 'bottom-start', + style, + ...props }: { - reference: RefObject<T>; - placement?: Parameters<typeof usePosition>[2]['placement']; children: ReactNode; + maxWidth?: string; + style?: CSSProperties; }, ref: Ref<R> ) { - const { style } = usePosition(reference, ref as RefObject<R>, { placement }); - return ( <Tile style={style} @@ -32,9 +28,10 @@ export const DropdownDesktop = forwardRef(function DropdownDesktop< flexDirection='column' overflow='auto' data-testid='dropdown' + {...props} > - <Box flexShrink={1} pb='x12'> - {children} + <Box flexShrink={1} pb={12}> + {(style as any).visibility === 'hidden' ? null : children} </Box> </Tile> ); diff --git a/packages/fuselage/src/components/Dropdown/DropdownDesktopWrapper.tsx b/packages/fuselage/src/components/Dropdown/DropdownDesktopWrapper.tsx new file mode 100644 index 0000000000..dc16fefd7f --- /dev/null +++ b/packages/fuselage/src/components/Dropdown/DropdownDesktopWrapper.tsx @@ -0,0 +1,30 @@ +import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks'; +import { usePosition } from '@rocket.chat/fuselage-hooks'; +import type { ReactNode, Ref, RefObject } from 'react'; +import React, { forwardRef } from 'react'; + +import { DropdownDesktop } from './DropdownDesktop'; + +export const DropdownDesktopWrapper = forwardRef( + function DropdownDesktopWrapper<T extends HTMLElement, R extends HTMLElement>( + { + children, + reference, + placement = 'bottom-start', + ...props + }: { + reference: RefObject<T>; + placement?: UsePositionOptions['placement']; + children: ReactNode; + }, + ref: Ref<R> + ) { + const { style } = usePosition(reference, ref as RefObject<R>, { + placement, + }); + + return ( + <DropdownDesktop style={style} children={children} ref={ref} {...props} /> + ); + } +); diff --git a/packages/fuselage/src/components/Dropdown/DropdownMobile.tsx b/packages/fuselage/src/components/Dropdown/DropdownMobile.tsx index b4f926ff83..9b4fd7a70b 100644 --- a/packages/fuselage/src/components/Dropdown/DropdownMobile.tsx +++ b/packages/fuselage/src/components/Dropdown/DropdownMobile.tsx @@ -8,6 +8,7 @@ export const DropdownMobile = forwardRef(function DropdownMobile< >( { children, + ...props }: { children: ReactNode; }, @@ -19,7 +20,7 @@ export const DropdownMobile = forwardRef(function DropdownMobile< elevation='2' pi='0' pb='0' - w='100%' + w='100vw' maxHeight='80%' position='fixed' display='flex' @@ -28,8 +29,9 @@ export const DropdownMobile = forwardRef(function DropdownMobile< style={{ bottom: 0, left: 0 }} zIndex={2} data-testid='dropdown' + {...props} > - <Box flexShrink={1} pb='x16'> + <Box flexShrink={1} pb={16}> {children} </Box> </Tile> diff --git a/packages/fuselage/src/components/Icon/Icon.styles.scss b/packages/fuselage/src/components/Icon/Icon.styles.scss index 5f722b1383..1c0738508d 100644 --- a/packages/fuselage/src/components/Icon/Icon.styles.scss +++ b/packages/fuselage/src/components/Icon/Icon.styles.scss @@ -3,7 +3,7 @@ user-select: none; - vertical-align: middle; + vertical-align: text-bottom; letter-spacing: 0; diff --git a/packages/fuselage/src/components/InputBox/InputBox.styles.scss b/packages/fuselage/src/components/InputBox/InputBox.styles.scss index 2ccca8daa0..f8a8a9ce32 100644 --- a/packages/fuselage/src/components/InputBox/InputBox.styles.scss +++ b/packages/fuselage/src/components/InputBox/InputBox.styles.scss @@ -154,10 +154,7 @@ &::-webkit-inner-spin-button, &::-webkit-calendar-picker-indicator { position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; width: auto; height: auto; diff --git a/packages/fuselage/src/components/Margins/Margins.stories.tsx b/packages/fuselage/src/components/Margins/Margins.stories.tsx index 2876f9b1da..ff3774cf98 100644 --- a/packages/fuselage/src/components/Margins/Margins.stories.tsx +++ b/packages/fuselage/src/components/Margins/Margins.stories.tsx @@ -320,10 +320,10 @@ export const WithNegativeValues = () => ( <Margins inline='-x40'> <Tile display='flex' padding='none'> <Margins inline='x40'> - <Tile padding='x40' /> - <Tile padding='x40' /> - <Tile padding='x40' /> - <Tile padding='x40' /> + <Tile padding={40} /> + <Tile padding={40} /> + <Tile padding={40} /> + <Tile padding={40} /> </Margins> </Tile> </Margins> @@ -332,10 +332,10 @@ export const WithNegativeValues = () => ( <Margins block='-x40'> <Tile display='flex' padding='none'> <Margins block='x40'> - <Tile padding='x40' /> - <Tile padding='x40' /> - <Tile padding='x40' /> - <Tile padding='x40' /> + <Tile padding={40} /> + <Tile padding={40} /> + <Tile padding={40} /> + <Tile padding={40} /> </Margins> </Tile> </Margins> @@ -347,11 +347,11 @@ WithNegativeValues.storyName = 'With negative values'; export const WithAutoValue = () => ( <Tile display='flex'> - <Tile padding='x40' /> + <Tile padding={40} /> <Margins inline='auto'> - <Tile padding='x40' /> + <Tile padding={40} /> </Margins> - <Tile padding='x40' /> + <Tile padding={40} /> </Tile> ); WithAutoValue.storyName = 'With auto value'; @@ -359,14 +359,14 @@ WithAutoValue.storyName = 'With auto value'; export const WithNoneValue = () => ( <Tile display='flex'> <Margins inline='x16'> - <Tile padding='x40' /> + <Tile padding={40} /> </Margins> <Margins inline='none'> - <Tile padding='x40' /> - <Tile padding='x40' /> + <Tile padding={40} /> + <Tile padding={40} /> </Margins> <Margins inline='x16'> - <Tile padding='x40' /> + <Tile padding={40} /> </Margins> </Tile> ); diff --git a/packages/fuselage/src/components/Menu/Menu.spec.tsx b/packages/fuselage/src/components/Menu/Menu.spec.tsx index 11c67f9156..fbd96e9bdf 100644 --- a/packages/fuselage/src/components/Menu/Menu.spec.tsx +++ b/packages/fuselage/src/components/Menu/Menu.spec.tsx @@ -20,23 +20,23 @@ describe('[Menu Component]', () => { it('should open options when click', async () => { const { getByTestId } = render(<Simple {...Simple.args} />); const button = getByTestId('menu'); - userEvent.click(button); + await userEvent.click(button); expect(await screen.findByText('Make Admin')).toBeInTheDocument(); }); it('should have no options when click twice', async () => { const { getByTestId } = render(<Simple {...Simple.args} />); const button = getByTestId('menu'); - userEvent.click(button); - userEvent.click(button); + await userEvent.click(button); + await userEvent.click(button); expect(menuOption).toBeNull(); }); it('should have no options when click on menu and then elsewhere', async () => { const { getByTestId } = render(<Simple {...Simple.args} />); const button = getByTestId('menu'); - userEvent.click(button); - userEvent.click(document.body); + await userEvent.click(button); + await userEvent.click(document.body); expect(menuOption).toBeNull(); }); }); diff --git a/packages/fuselage/src/components/Menu/Menu.stories.tsx b/packages/fuselage/src/components/Menu/Menu.stories.tsx index 4cf6e71e10..78834304b1 100644 --- a/packages/fuselage/src/components/Menu/Menu.stories.tsx +++ b/packages/fuselage/src/components/Menu/Menu.stories.tsx @@ -30,7 +30,7 @@ Simple.args = { makeAdmin: { label: ( <Box display='flex' alignItems='center'> - <Icon mie='x4' name='key' size='x16' /> + <Icon mie={4} name='key' size='x16' /> Make Admin </Box> ), @@ -39,7 +39,7 @@ Simple.args = { delete: { label: ( <Box display='flex' alignItems='center' color='danger'> - <Icon mie='x4' name='trash' size='x16' /> + <Icon mie={4} name='trash' size='x16' /> Delete </Box> ), @@ -71,7 +71,7 @@ Complex.args = { type: 'option', label: ( <Box display='flex' alignItems='center' color='danger'> - <Icon mie='x4' name='trash' size='x16' /> + <Icon mie={4} name='trash' size='x16' /> Delete </Box> ), diff --git a/packages/fuselage/src/components/Menu/Menu.tsx b/packages/fuselage/src/components/Menu/Menu.tsx index cc14657f3b..4d8977dcd7 100644 --- a/packages/fuselage/src/components/Menu/Menu.tsx +++ b/packages/fuselage/src/components/Menu/Menu.tsx @@ -1,4 +1,4 @@ -import type { Placements } from '@rocket.chat/fuselage-hooks'; +import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks'; import type { ComponentProps, ElementType, ReactNode } from 'react'; import React, { useRef, useCallback, useEffect } from 'react'; @@ -17,7 +17,7 @@ type MenuProps = Omit<ComponentProps<typeof IconButton>, 'icon'> & { }; }; optionWidth?: ComponentProps<typeof Box>['width']; - placement?: Placements; + placement?: UsePositionOptions['placement']; renderItem?: ElementType; icon?: ComponentProps<typeof IconButton>['icon']; maxHeight?: string | number; diff --git a/packages/fuselage/src/components/Menu/V2/Menu.stories.tsx b/packages/fuselage/src/components/Menu/V2/Menu.stories.tsx new file mode 100644 index 0000000000..b6498c752b --- /dev/null +++ b/packages/fuselage/src/components/Menu/V2/Menu.stories.tsx @@ -0,0 +1,459 @@ +import type { ComponentStory, ComponentMeta } from '@storybook/react'; +import type { ComponentProps, ReactNode, Ref } from 'react'; +import React, { forwardRef, useState } from 'react'; + +import { + MenuV2 as Menu, + MenuItem, + MenuSection, + MenuItemContent, + MenuItemIcon, + MenuItemInput, +} from '.'; +import Box from '../../Box/Box'; +import { IconButton } from '../../Button'; +import { ButtonGroup } from '../../ButtonGroup'; +import { CheckBox } from '../../CheckBox'; +import { RadioButton } from '../../RadioButton'; +import Sidebar from '../../Sidebar'; +import { ToggleSwitch } from '../../ToggleSwitch'; + +type MenuStories = ComponentMeta<typeof Menu>; + +export default { + title: 'Navigation/Menu/v2', + component: Menu, + decorators: [ + (story) => ( + <Box + minHeight={50} + height='full' + minWidth={100} + maxWidth={250} + width={'full'} + > + {story()} + </Box> + ), + ], + parameters: { + docs: { + source: { + excludeDecorators: true, + transform: (src: string) => console.log(src), + }, + description: { + component: 'Kebab Menu. Use `<MenuItem>` to render the menu items.', + }, + }, + }, +} as MenuStories; + +export const Simple: ComponentStory<typeof Menu> = (args) => ( + <Menu {...args} placement='right-start'> + <MenuItem key='1'>Profile</MenuItem> + <MenuItem key='2'>Chats</MenuItem> + <MenuItem key='3'>Settings</MenuItem> + </Menu> +); + +export const Complex: ComponentStory<typeof Menu> = (args) => ( + <Menu {...args}> + <MenuItem key='profile'> + <MenuItemIcon name='user' /> + <MenuItemContent>Profile</MenuItemContent> + </MenuItem> + <MenuItem key='chats'> + <MenuItemIcon name='chat' /> + <MenuItemContent>Chats</MenuItemContent> + </MenuItem> + <MenuItem key='settings'> + <MenuItemIcon name='cog' /> + <MenuItemContent>Settings</MenuItemContent> + </MenuItem> + </Menu> +); +Complex.parameters = { + docs: { + description: { + story: + 'You can use `MenuItem` sub-components to render more complex items, with icons, avatars, text and inputs. The available components are: <br>`MenuItemAvatar` <br>`MenuItemColumn` <br>`MenuItemContent` <br>`MenuItemIcon` <br>`MenuItemInput` <br>`MenuItemSkeleton` <br>`MenuItemTitle`', + }, + }, +}; + +export const WithSections: ComponentStory<typeof Menu> = (args) => ( + <Menu {...args}> + <MenuSection title='Styles'> + <MenuItem key='bold'>Bold</MenuItem> + <MenuItem key='underline'>Underline</MenuItem> + </MenuSection> + <MenuSection> + <MenuItem key='left'>Left</MenuItem> + <MenuItem key='middle'>Middle</MenuItem> + <MenuItem key='right'>Right</MenuItem> + </MenuSection> + </Menu> +); +WithSections.parameters = { + docs: { + description: { + story: + 'Use `<MenuSection>` to group the menu items. The `title` prop is optional. If provided, a **title** and a **divider** is added - otherwise, only the divider is added.', + }, + }, +}; + +export const MenuDisplayExample: ComponentStory<typeof Menu> = (args) => { + const [display, setDisplay] = useState('condensed'); + const [avatarDisplay, setAvatarDisplay] = useState(false); + const [sortBy, setSortBy] = useState('name'); + const [groupByUnread, setGroupByUnread] = useState(false); + const [groupByFav, setGroupByFav] = useState(false); + const [groupByTypes, setGroupByTypes] = useState(false); + + return ( + <Menu selectionMode='multiple' placement='top-start' {...args}> + <MenuSection title='Display'> + <MenuItem key='extended'> + <MenuItemIcon name='extended-view' /> + <MenuItemContent>Extended</MenuItemContent> + <MenuItemInput> + <RadioButton + mi={16} + onChange={() => setDisplay('extended')} + checked={display === 'extended'} + /> + </MenuItemInput> + </MenuItem> + <MenuItem key='medium'> + <MenuItemIcon name='medium-view' /> + <MenuItemContent>Medium</MenuItemContent> + <MenuItemInput> + <RadioButton + mi={16} + onChange={() => setDisplay('medium')} + checked={display === 'medium'} + /> + </MenuItemInput> + </MenuItem> + <MenuItem key='condensed'> + <MenuItemIcon name='condensed-view' /> + <MenuItemContent>Condensed</MenuItemContent> + <MenuItemInput> + <RadioButton + mi={16} + onChange={() => setDisplay('condensed')} + checked={display === 'condensed'} + /> + </MenuItemInput> + </MenuItem> + <MenuItem key='avatars'> + <MenuItemIcon name='user-rounded' /> + <MenuItemContent>Avatars</MenuItemContent> + <MenuItemInput> + <ToggleSwitch + mie={16} + onChange={() => setAvatarDisplay(!avatarDisplay)} + checked={avatarDisplay} + /> + </MenuItemInput> + </MenuItem> + </MenuSection> + <MenuSection title='Sort by'> + <MenuItem key='activities'> + <MenuItemIcon name='clock' /> + <MenuItemContent>Activities</MenuItemContent> + <MenuItemInput> + <RadioButton + mi={16} + onChange={() => setSortBy('activity')} + checked={sortBy === 'activity'} + /> + </MenuItemInput> + </MenuItem> + <MenuItem key='name'> + <MenuItemIcon name='sort-az' /> + <MenuItemContent>Name</MenuItemContent> + <MenuItemInput> + <RadioButton + mi={16} + onChange={() => setSortBy('alphabetical')} + checked={sortBy === 'alphabetical'} + /> + </MenuItemInput> + </MenuItem> + </MenuSection> + <MenuSection title='Group by'> + <MenuItem key='unread'> + <MenuItemIcon name='flag' /> + <MenuItemContent>Unread</MenuItemContent> + <MenuItemInput> + <CheckBox + mi={16} + checked={groupByUnread} + onChange={() => setGroupByUnread(!groupByUnread)} + /> + </MenuItemInput> + </MenuItem> + <MenuItem key='favorites'> + <MenuItemIcon name='star' /> + <MenuItemContent>Favorites</MenuItemContent> + <MenuItemInput> + <CheckBox + mi={16} + checked={groupByFav} + onChange={() => setGroupByFav(!groupByFav)} + /> + </MenuItemInput> + </MenuItem> + <MenuItem key='types'> + <MenuItemIcon name='group-by-type' /> + <MenuItemContent>Types</MenuItemContent> + <MenuItemInput> + <CheckBox + mi={16} + checked={groupByTypes} + onChange={() => setGroupByTypes(!groupByTypes)} + /> + </MenuItemInput> + </MenuItem> + </MenuSection> + </Menu> + ); +}; + +type Item = { + name: string; + icon: ComponentProps<typeof MenuItemIcon>['name']; + input?: ReactNode; + description?: string; + variant?: string; +}; +const GenericMenuItem = ({ item: { icon, name, input } }: { item: Item }) => ( + <> + {icon && <MenuItemIcon name={icon} />} + <MenuItemContent>{name}</MenuItemContent> + {input && <MenuItemInput>{input}</MenuItemInput>} + </> +); +export const MenuMapGenericItem = () => { + const [sortBy, setSortBy] = useState('name'); + + const [groupByUnread, setGroupByUnread] = useState(false); + const [groupByFav, setGroupByFav] = useState(false); + const [groupByTypes, setGroupByTypes] = useState(false); + + const groupByItems: Item[] = [ + { + name: 'Unread', + icon: 'flag', + input: ( + <CheckBox + mi={16} + checked={groupByUnread} + onChange={() => setGroupByUnread(!groupByUnread)} + /> + ), + }, + { + name: 'favorites', + icon: 'star', + description: + 'Group by favorites and unread bla bla balaisudhf ioioasdhoaisdf asdifh oaisdhf aosidhf aisdhf aosdihf', + input: ( + <CheckBox + mi={16} + checked={groupByFav} + onChange={() => setGroupByFav(!groupByFav)} + /> + ), + }, + { + name: 'Types', + icon: 'group-by-type', + input: ( + <CheckBox + mi={16} + checked={groupByTypes} + onChange={() => setGroupByTypes(!groupByTypes)} + /> + ), + }, + ]; + const sortByItems: Item[] = [ + { + name: 'Activities', + icon: 'clock', + input: ( + <CheckBox + mi={16} + onChange={() => setSortBy('activity')} + checked={sortBy === 'activity'} + /> + ), + }, + { + name: 'Name', + icon: 'sort-az', + input: ( + <CheckBox + mi={16} + onChange={() => setSortBy('alphabetical')} + checked={sortBy === 'alphabetical'} + /> + ), + }, + ]; + + return ( + <Menu selectionMode='multiple'> + <MenuSection title='Sort by' items={sortByItems}> + {(item) => ( + <MenuItem key={item.name}> + <GenericMenuItem item={item} /> + </MenuItem> + )} + </MenuSection> + <MenuSection title='Group By' items={groupByItems}> + {(item) => ( + <MenuItem key={item.name}> + <GenericMenuItem item={item} /> + </MenuItem> + )} + </MenuSection> + <MenuSection + items={[ + { id: 'delete', name: 'Delete', icon: 'trash', variant: 'danger' }, + ]} + > + {(item) => ( + <MenuItem key={item.id}> + <GenericMenuItem item={item as any} /> + </MenuItem> + )} + </MenuSection> + </Menu> + ); +}; + +export const AsSidebarTopbarActions = () => ( + <Sidebar.TopBar.Actions> + <Sidebar.TopBar.Action icon='user' title='user' /> + <Sidebar.TopBar.Action icon='book' title='book' /> + <Menu title='test' is={Sidebar.TopBar.Action}> + <MenuItem>test</MenuItem> + <MenuItem>test</MenuItem> + <MenuItem>test</MenuItem> + <MenuItem>test</MenuItem> + </Menu> + <Menu icon='sort' title='sort'> + <MenuItem>test</MenuItem> + <MenuItem>test</MenuItem> + <MenuItem>test</MenuItem> + <MenuItem>test</MenuItem> + </Menu> + </Sidebar.TopBar.Actions> +); + +export const ControlledOpenState = () => { + const [isOpen, setIsOpen] = useState(false); + + return ( + <Menu isOpen={isOpen} onOpenChange={setIsOpen}> + <MenuItem key='1'>Profile</MenuItem> + <MenuItem key='2'>Chats</MenuItem> + <MenuItem key='3'>Settings</MenuItem> + </Menu> + ); +}; + +export const ItemVariants = () => ( + <Menu> + <MenuSection + items={[ + { + id: 'default', + name: 'Default', + icon: 'info-circled', + }, + { + id: 'success', + name: 'Success', + icon: 'circle-check', + variant: 'success', + }, + { + id: 'warning', + name: 'Warning', + icon: 'modal-warning', + variant: 'warning', + }, + { + id: 'danger', + name: 'Danger', + icon: 'circle-cross', + variant: 'danger', + }, + ]} + > + {(item) => ( + <MenuItem key={item.id}> + <GenericMenuItem item={item as any} /> + </MenuItem> + )} + </MenuSection> + </Menu> +); + +export const Sizes = () => ( + <ButtonGroup> + <Menu large> + <MenuItem key='1'>Profile</MenuItem> + <MenuItem key='2'>Chats</MenuItem> + <MenuItem key='3'>Settings</MenuItem> + </Menu> + <Menu medium> + <MenuItem key='1'>Profile</MenuItem> + <MenuItem key='2'>Chats</MenuItem> + <MenuItem key='3'>Settings</MenuItem> + </Menu> + <Menu small> + <MenuItem key='1'>Profile</MenuItem> + <MenuItem key='2'>Chats</MenuItem> + <MenuItem key='3'>Settings</MenuItem> + </Menu> + <Menu tiny> + <MenuItem key='1'>Profile</MenuItem> + <MenuItem key='2'>Chats</MenuItem> + <MenuItem key='3'>Settings</MenuItem> + </Menu> + <Menu mini> + <MenuItem key='1'>Profile</MenuItem> + <MenuItem key='2'>Chats</MenuItem> + <MenuItem key='3'>Settings</MenuItem> + </Menu> + </ButtonGroup> +); + +const CustomButton = forwardRef((props, ref: Ref<HTMLElement>) => ( + <IconButton ref={ref} {...props} icon='kebab' secondary small={false} /> +)); + +export const WithCustomButton = () => ( + <ButtonGroup> + <Menu + title='using prop customButton' + button={<IconButton icon='kebab' secondary />} + > + <MenuItem key='1'>Profile</MenuItem> + <MenuItem key='2'>Chats</MenuItem> + <MenuItem key='3'>Settings</MenuItem> + </Menu> + <Menu title='using prop is' is={CustomButton}> + <MenuItem key='1'>Profile</MenuItem> + <MenuItem key='2'>Chats</MenuItem> + <MenuItem key='3'>Settings</MenuItem> + </Menu> + </ButtonGroup> +); diff --git a/packages/fuselage/src/components/Menu/V2/Menu.tsx b/packages/fuselage/src/components/Menu/V2/Menu.tsx new file mode 100644 index 0000000000..06f221e9b6 --- /dev/null +++ b/packages/fuselage/src/components/Menu/V2/Menu.tsx @@ -0,0 +1,94 @@ +import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks'; +import type { ComponentProps, ElementType } from 'react'; +import React, { cloneElement, useRef } from 'react'; +import type { AriaMenuProps } from 'react-aria'; +import { useButton, useMenuTrigger } from 'react-aria'; +import type { MenuTriggerProps } from 'react-stately'; +import { useMenuTriggerState } from 'react-stately'; + +import type Box from '../../Box/Box'; +import { IconButton } from '../../Button'; +import MenuDropDown from './MenuDropdown'; +import MenuPopover from './MenuPopover'; +import { getPlacement } from './helpers/helpers'; + +interface MenuButtonProps<T> extends AriaMenuProps<T>, MenuTriggerProps { + icon?: ComponentProps<typeof IconButton>['icon']; + large?: boolean; + medium?: boolean; + small?: boolean; + tiny?: boolean; + mini?: boolean; + placement?: UsePositionOptions['placement']; + title?: string; + /** + * A component that renders an IconButton + */ + is?: ElementType; + className?: ComponentProps<typeof Box>['className']; + pressed?: boolean; + maxWidth?: string; + button?: React.ReactElement; +} +const Menu = <T extends object>({ + icon = 'kebab', + placement = 'bottom-start', + title, + is: MenuButton = IconButton, + className, + pressed, + maxWidth = 'x250', + button, + ...props +}: MenuButtonProps<T>) => { + const state = useMenuTriggerState(props); + + const ref = useRef(null); + const { menuTriggerProps, menuProps } = useMenuTrigger<T>({}, state, ref); + + const { buttonProps } = useButton( + { ...menuTriggerProps, ...{ preventFocusOnPress: true } }, + ref + ); + + const { large, medium, tiny, mini } = props; + const sizes = { large, medium, tiny, mini }; + const defaultSmall = !large && !medium && !tiny && !mini; + + return ( + <> + {button ? ( + cloneElement(button, { + ...buttonProps, + ref, + icon, + className, + title, + pressed: pressed || state.isOpen, + }) + ) : ( + <MenuButton + {...buttonProps} + ref={ref} + icon={icon} + className={className} + title={title} + pressed={pressed || state.isOpen} + small={defaultSmall} + {...sizes} + /> + )} + {state.isOpen && ( + <MenuPopover + state={state} + triggerRef={ref} + placement={getPlacement(placement)} + maxWidth={maxWidth} + > + <MenuDropDown {...props} {...menuProps} /> + </MenuPopover> + )} + </> + ); +}; +export default Menu; diff --git a/packages/fuselage/src/components/Menu/V2/MenuDropdown.tsx b/packages/fuselage/src/components/Menu/V2/MenuDropdown.tsx new file mode 100644 index 0000000000..afec6a6ff8 --- /dev/null +++ b/packages/fuselage/src/components/Menu/V2/MenuDropdown.tsx @@ -0,0 +1,28 @@ +import React, { useRef } from 'react'; +import type { AriaMenuProps } from 'react-aria'; +import { useMenu } from 'react-aria'; +import { useTreeState } from 'react-stately'; + +import MenuItem from './MenuItem'; +import MenuSection from './MenuSection'; + +function MenuDropDown<T extends object>(props: AriaMenuProps<T>) { + const state = useTreeState(props); + + const ref = useRef(null); + const { menuProps } = useMenu(props, state, ref); + + return ( + <div {...menuProps} ref={ref}> + {[...state.collection].map((item) => + item.type === 'section' ? ( + <MenuSection key={item.key} section={item} state={state} /> + ) : ( + <MenuItem key={item.key} item={item} state={state} /> + ) + )} + </div> + ); +} + +export default MenuDropDown; diff --git a/packages/fuselage/src/components/Menu/V2/MenuItem.tsx b/packages/fuselage/src/components/Menu/V2/MenuItem.tsx new file mode 100644 index 0000000000..5229fa15e7 --- /dev/null +++ b/packages/fuselage/src/components/Menu/V2/MenuItem.tsx @@ -0,0 +1,43 @@ +import type { ComponentProps, ReactNode } from 'react'; +import React, { useRef } from 'react'; +import { useMenuItem } from 'react-aria'; +import type { TreeState } from 'react-stately'; + +import { MenuItemDescription } from '.'; +import MenuOption from './MenuOption'; +import type { Node } from './types'; + +type MenuItemProps = { + item: Node<{ + description?: ReactNode; + variant?: ComponentProps<typeof MenuOption>['variant']; + }>; + state: TreeState<unknown>; +}; + +function MenuItem({ item, state }: MenuItemProps) { + const ref = useRef(null); + const { menuItemProps, isFocused, isDisabled } = useMenuItem( + { key: item.key }, + state, + ref + ); + + return ( + <MenuOption + {...menuItemProps} + ref={ref} + focus={isFocused} + disabled={isDisabled} + is='label' + variant={item.value && item.value.variant} + > + <div className='rcx-option__wrapper'>{item.rendered}</div> + {item.value && item.value.description && ( + <MenuItemDescription>{item.value.description}</MenuItemDescription> + )} + </MenuOption> + ); +} + +export default MenuItem; diff --git a/packages/fuselage/src/components/Menu/V2/MenuOption.tsx b/packages/fuselage/src/components/Menu/V2/MenuOption.tsx new file mode 100644 index 0000000000..c7e107d128 --- /dev/null +++ b/packages/fuselage/src/components/Menu/V2/MenuOption.tsx @@ -0,0 +1,79 @@ +import type { + Ref, + ComponentProps, + ReactNode, + MouseEvent, + AllHTMLAttributes, +} from 'react'; +import React, { forwardRef, memo } from 'react'; + +import { prevent } from '../../../helpers/prevent'; +import type Box from '../../Box/Box'; + +type OptionProps = { + is?: ComponentProps<typeof Box>['is']; + id?: string; + children?: ReactNode; + focus?: boolean; + selected?: boolean; + className?: ComponentProps<typeof Box>['className']; + ref?: Ref<Element>; + title?: string; + disabled?: boolean; + value?: string; + variant?: 'danger' | 'success' | 'warning' | 'primary'; + onClick?: (event: MouseEvent<HTMLElement>) => void; + description?: ReactNode; +} & Omit<AllHTMLAttributes<HTMLElement>, 'label'>; + +const MenuOption = memo( + forwardRef( + ( + { + is: Tag = 'li', + id, + children, + focus, + selected, + className, + title, + disabled, + variant, + onClick, + ...props + }: OptionProps, + ref + ) => ( + <Tag + {...props} + key={id} + id={id} + ref={ref} + aria-selected={!!selected} + aria-disabled={!!disabled} + title={title} + onClick={(e: React.MouseEvent<HTMLDivElement>) => { + if (disabled) { + prevent(e); + return; + } + onClick?.(e); + }} + className={[ + 'rcx-option', + className, + focus && 'rcx-option--focus', + selected && 'rcx-option--selected', + disabled && 'rcx-option--disabled', + variant && `rcx-option--${variant}`, + ] + .filter(Boolean) + .join(' ')} + > + {children} + </Tag> + ) + ) +); + +export default MenuOption; diff --git a/packages/fuselage/src/components/Menu/V2/MenuPopover.tsx b/packages/fuselage/src/components/Menu/V2/MenuPopover.tsx new file mode 100644 index 0000000000..19093e5bb4 --- /dev/null +++ b/packages/fuselage/src/components/Menu/V2/MenuPopover.tsx @@ -0,0 +1,50 @@ +import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; +import React from 'react'; +import { usePopover } from 'react-aria'; +import type { AriaPopoverProps } from 'react-aria'; +import type { OverlayTriggerState } from 'react-stately'; + +import { DropdownDesktop } from '../../Dropdown/DropdownDesktop'; +import { DropdownMobile } from '../../Dropdown/DropdownMobile'; + +interface PopoverProps extends Omit<AriaPopoverProps, 'popoverRef'> { + children: React.ReactNode; + state: OverlayTriggerState; + maxWidth?: string; +} + +function MenuPopover({ + children, + state, + offset = 4, + maxWidth, + ...props +}: PopoverProps) { + const popoverRef = React.useRef(null); + const { popoverProps } = usePopover( + { + ...props, + offset, + popoverRef, + }, + state + ); + + const breakpoints = useBreakpoints(); + const isMobile = !breakpoints.includes('sm'); + + if (isMobile) { + const mobileProps = { ...popoverProps, style: { bottom: 0, left: 0 } }; + return <DropdownMobile children={children} {...mobileProps} />; + } + + return ( + <DropdownDesktop + children={children} + ref={popoverRef} + maxWidth={maxWidth} + {...popoverProps} + /> + ); +} +export default MenuPopover; diff --git a/packages/fuselage/src/components/Menu/V2/MenuSection.tsx b/packages/fuselage/src/components/Menu/V2/MenuSection.tsx new file mode 100644 index 0000000000..fbc9c00977 --- /dev/null +++ b/packages/fuselage/src/components/Menu/V2/MenuSection.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { useMenuSection, useSeparator } from 'react-aria'; +import type { TreeState } from 'react-stately'; + +import Box from '../../Box/Box'; +import { Divider } from '../../Divider'; +import { OptionTitle } from '../../Option'; +import MenuItem from './MenuItem'; +import type { Node } from './types'; + +type MenuSectionProps = { + section: Node<unknown>; + state: TreeState<unknown>; +}; + +function MenuSection({ section, state }: MenuSectionProps) { + const { itemProps, headingProps, groupProps } = useMenuSection({ + 'heading': section.rendered, + 'aria-label': section['aria-label'], + }); + + const { separatorProps } = useSeparator({ + elementType: 'span', + }); + + // If the section is not the first, add a separator element. + return ( + <> + {section.key !== state.collection.getFirstKey() && ( + <Divider {...separatorProps} /> + )} + <div {...itemProps}> + {section.rendered && ( + <OptionTitle {...headingProps}>{section.rendered}</OptionTitle> + )} + <Box {...groupProps} p='0'> + {[...section.childNodes].map((node) => ( + <MenuItem key={node.key} item={node as any} state={state} /> + ))} + </Box> + </div> + </> + ); +} + +export default MenuSection; diff --git a/packages/fuselage/src/components/Menu/V2/helpers/helpers.ts b/packages/fuselage/src/components/Menu/V2/helpers/helpers.ts new file mode 100644 index 0000000000..4acbe7bf0d --- /dev/null +++ b/packages/fuselage/src/components/Menu/V2/helpers/helpers.ts @@ -0,0 +1,61 @@ +import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks'; + +type ReactAriaPlacement = + | 'bottom' + | 'bottom left' + | 'bottom right' + | 'bottom start' + | 'bottom end' + | 'top' + | 'top left' + | 'top right' + | 'top start' + | 'top end' + | 'left' + | 'left top' + | 'left bottom' + | 'start' + | 'start top' + | 'start bottom' + | 'right' + | 'right top' + | 'right bottom' + | 'end' + | 'end top' + | 'end bottom'; + +export const getPlacement = ( + placement: UsePositionOptions['placement'] +): ReactAriaPlacement => { + // switch case for placement from usePosition placement to react-aria + switch (placement) { + case 'bottom': + return 'bottom'; + + case 'bottom-start': + return 'bottom start'; + case 'bottom-end': + return 'bottom end'; + case 'top': + return 'top'; + + case 'top-start': + return 'top start'; + case 'top-end': + return 'top end'; + case 'left': + return 'left'; + case 'left-start': + return 'left top'; + case 'left-end': + return 'left bottom'; + case 'right': + return 'right'; + case 'right-start': + return 'right top'; + case 'right-end': + return 'right bottom'; + default: + return 'bottom start'; + } +}; diff --git a/packages/fuselage/src/components/Menu/V2/index.ts b/packages/fuselage/src/components/Menu/V2/index.ts new file mode 100644 index 0000000000..2a48108a31 --- /dev/null +++ b/packages/fuselage/src/components/Menu/V2/index.ts @@ -0,0 +1,26 @@ +import { Item, Section } from 'react-stately'; + +import { + OptionIcon, + OptionAvatar, + OptionColumn, + OptionContent, + OptionInput, + OptionSkeleton, + OptionTitle, + OptionDescriptionBlock, +} from '../../Option'; + +export { default as MenuV2 } from './Menu'; + +export { Item as MenuItem, Section as MenuSection }; +export { + OptionAvatar as MenuItemAvatar, + OptionColumn as MenuItemColumn, + OptionContent as MenuItemContent, + OptionIcon as MenuItemIcon, + OptionInput as MenuItemInput, + OptionSkeleton as MenuItemSkeleton, + OptionTitle as MenuItemTitle, + OptionDescriptionBlock as MenuItemDescription, +}; diff --git a/packages/fuselage/src/components/Menu/V2/types.ts b/packages/fuselage/src/components/Menu/V2/types.ts new file mode 100644 index 0000000000..8e14031f48 --- /dev/null +++ b/packages/fuselage/src/components/Menu/V2/types.ts @@ -0,0 +1,39 @@ +import type { Key, ReactElement, ReactNode } from 'react'; + +// Workaround since react-aria and react-stately don't export this type. +// Issue: https://github.com/adobe/react-spectrum/issues/3902 + +export interface Node<T> { + /** The type of item this node represents. */ + 'type': string; + /** A unique key for the node. */ + 'key': Key; + /** The object value the node was created from. */ + 'value': T; + /** The level of depth this node is at in the heirarchy. */ + 'level': number; + /** Whether this item has children, even if not loaded yet. */ + 'hasChildNodes': boolean; + /** The loaded children of this node. */ + 'childNodes': Iterable<Node<T>>; + /** The rendered contents of this node (e.g. JSX). */ + 'rendered': ReactNode; + /** A string value for this node, used for features like typeahead. */ + 'textValue': string; + /** An accessibility label for this node. */ + 'aria-label'?: string; + /** The index of this node within its parent. */ + 'index'?: number; + /** A function that should be called to wrap the rendered node. */ + 'wrapper'?: (element: ReactElement) => ReactElement; + /** The key of the parent node. */ + 'parentKey'?: Key; + /** The key of the node before this node. */ + 'prevKey'?: Key; + /** The key of the node after this node. */ + 'nextKey'?: Key; + /** Additional properties specific to a particular node type. */ + 'props'?: any; + /** @private */ + 'shouldInvalidate'?: (context: unknown) => boolean; +} diff --git a/packages/fuselage/src/components/Menu/index.ts b/packages/fuselage/src/components/Menu/index.ts index 629d3d0aa1..f92b689813 100644 --- a/packages/fuselage/src/components/Menu/index.ts +++ b/packages/fuselage/src/components/Menu/index.ts @@ -1 +1,2 @@ export * from './Menu'; +export * from './V2'; diff --git a/packages/fuselage/src/components/Message/MessageDivider/MessageDivider.styles.scss b/packages/fuselage/src/components/Message/MessageDivider/MessageDivider.styles.scss index edcf71a340..be469ea87a 100644 --- a/packages/fuselage/src/components/Message/MessageDivider/MessageDivider.styles.scss +++ b/packages/fuselage/src/components/Message/MessageDivider/MessageDivider.styles.scss @@ -15,11 +15,11 @@ $message-divider-color: theme('message-divider-color', colors.font(default)); $message-divider-color-unread: theme( 'message-divider-color-unread', - colors.stroke(error) + colors.font(danger) ); $message-divider-background-color-unread: theme( 'message-divider-background-color-unread', - colors.stroke(extra-light-error) + colors.stroke(error) ); $message-divider-size: theme( diff --git a/packages/fuselage/src/components/Message/MessageHighlight.tsx b/packages/fuselage/src/components/Message/MessageHighlight.tsx new file mode 100644 index 0000000000..dc76891d7a --- /dev/null +++ b/packages/fuselage/src/components/Message/MessageHighlight.tsx @@ -0,0 +1,36 @@ +import type { ElementType, HTMLAttributes } from 'react'; +import React from 'react'; + +import { prependClassName } from '../../helpers/prependClassName'; + +export type MessageHighlightProps = { + is?: ElementType; + clickable?: boolean; + variant?: 'critical' | 'relevant' | 'other' | 'link'; + className?: string; + children: any; + title?: string; +} & HTMLAttributes<HTMLElement>; + +export function MessageHighlight({ + is: Tag = 'span', + variant = 'other', + className, + clickable, + ...props +}: MessageHighlightProps) { + const modifiers = [variant, clickable && 'clickable'] + .filter(Boolean) + .map((modifier) => `rcx-message__highlight--${modifier}`) + .join(' '); + + return ( + <Tag + className={prependClassName( + className, + `rcx-box rcx-box--full rcx-message__highlight ${modifiers}` + )} + {...props} + /> + ); +} diff --git a/packages/fuselage/src/components/Message/Messages.stories.tsx b/packages/fuselage/src/components/Message/Messages.stories.tsx index 2c5982815a..12eb9d2ed0 100644 --- a/packages/fuselage/src/components/Message/Messages.stories.tsx +++ b/packages/fuselage/src/components/Message/Messages.stories.tsx @@ -42,12 +42,48 @@ export const Default: ComponentStory<typeof Message> = () => ( <Message.Timestamp>12:00 PM</Message.Timestamp> </Message.Header> <Message.Body> - Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris - nisi ut aliquip ex ea commodo consequat a duis aute irure dolor in + Ut enim ad minim veniam,{' '} + <Message.Highlight clickable variant='other'> + channel + </Message.Highlight>{' '} + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat a duis aute irure dolor in{' '} + <Message.Highlight clickable variant='critical'> + Haylie George + </Message.Highlight>{' '} + <Message.Highlight clickable variant='critical'> + Haylie George + </Message.Highlight>{' '} + <Message.Highlight clickable variant='critical'> + Haylie George + </Message.Highlight>{' '} + <Message.Highlight clickable variant='critical'> + Haylie George + </Message.Highlight>{' '} + <Message.Highlight clickable variant='critical'> + Haylie George + </Message.Highlight>{' '} + commodo consequat a duis aute irure dolor in reprehenderit in + voluptate velit esse cillum dolore eu fugiat nulla pariatur. + Consectetur adipiscing commodo consequat a duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla - pariatur. Consectetur adipiscing elit, sed do eiusmod tempor - incididunt ut labore et dolore magna aliqua. Ut enim ad minim - veniam... + pariatur. Consectetur adipiscing{' '} + <Message.Highlight variant='critical'> + highlighted text + </Message.Highlight> + touching text.{' '} + <Message.Highlight clickable variant='relevant'> + all + </Message.Highlight> + . elit, sed do eiusmod tempor incididunt + <Message.Highlight clickable variant='link'> + Room Name + </Message.Highlight> + ut labore et dolore magna + <Message.Highlight clickable variant='other'> + Gabriel.Henriques + </Message.Highlight> + . aliqua. Ut enim ad minim veniam... </Message.Body> <MessageReactions> <MessageReactions.Reaction mine counter={1} /> diff --git a/packages/fuselage/src/components/Message/Messages.styles.scss b/packages/fuselage/src/components/Message/Messages.styles.scss index 68831ef837..8b853a79d7 100644 --- a/packages/fuselage/src/components/Message/Messages.styles.scss +++ b/packages/fuselage/src/components/Message/Messages.styles.scss @@ -50,6 +50,41 @@ $message-background-color-highlight: functions.theme( colors.status-background(warning-2) ); +$message-link-color: functions.theme('message-link-color', colors.font(info)); + +$message-highlight-colors-critical-color: theme( + 'message-highlight-colors-critical-color', + colors.font(pure-white) +); +$message-highlight-colors-background-critical-color: theme( + 'message-highlight-colors-background-critical-color', + colors.badge('level-4') +); + +$message-highlight-colors-relevant-color: theme( + 'message-highlight-colors-relevant-color', + colors.font(pure-white) +); +$message-highlight-colors-background-relevant-color: theme( + 'message-highlight-colors-background-relevant-color', + colors.badge('level-3') +); + +$message-highlight-colors-other-color: theme( + 'message-highlight-colors-other-color', + colors.font(default) +); + +$message-highlight-colors-other-link-color: theme( + 'message-highlight-colors-other-color', + colors.font(info) +); + +$message-highlight-colors-background-other-color: theme( + 'message-highlight-colors-background-other-color', + colors.badge('level-0') +); + .rcx-message { @include mixins.container(); position: relative; @@ -185,10 +220,16 @@ $message-background-color-highlight: functions.theme( transition: opacity 0.3s linear; + word-break: break-word; + opacity: 1; color: colors.font(default); + & a { + color: $message-link-color; + } + & h1 { @include typography.use-font-scale(h1); } @@ -279,4 +320,75 @@ $message-background-color-highlight: functions.theme( @include size.square(lengths.size(44)); } } + + &__highlight { + position: relative; + + z-index: 1; + + display: inline-block; + + padding-inline: lengths.padding(2); + + white-space: nowrap; + + word-break: keep-all; + + font-weight: 500; + + &--clickable { + cursor: pointer; + + &:hover { + text-decoration: underline; + } + } + + &::before { + position: absolute; + + z-index: -1; + + width: 100%; + height: 18px; + + content: ''; + + transform: translateY(lengths.margin(1)) translateX(lengths.margin(-2)); + + border-radius: theme( + 'message-highlight-border-radius', + lengths.border-radius(medium) + ); + } + + &--critical { + &::before { + background-color: $message-highlight-colors-background-critical-color; + } + color: $message-highlight-colors-critical-color; + } + + &--relevant { + &::before { + background-color: $message-highlight-colors-background-relevant-color; + } + color: $message-highlight-colors-relevant-color; + } + + &--other, + &--link { + &::before { + background-color: $message-highlight-colors-background-other-color; + } + } + + &--link { + color: $message-highlight-colors-other-link-color; + } + + &--other { + color: $message-highlight-colors-other-color; + } + } } diff --git a/packages/fuselage/src/components/Message/index.tsx b/packages/fuselage/src/components/Message/index.tsx index 977df2f049..771cef083d 100644 --- a/packages/fuselage/src/components/Message/index.tsx +++ b/packages/fuselage/src/components/Message/index.tsx @@ -5,6 +5,7 @@ import { MessageContainer } from './MessageContainer'; import { MessageContainerFixed } from './MessageContainerFixed'; import { MessageDivider } from './MessageDivider'; import { MessageHeader } from './MessageHeader'; +import { MessageHighlight } from './MessageHighlight'; import { MessageLeftContainer } from './MessageLeftContainer'; import MessageMetrics from './MessageMetrics'; import { MessageName } from './MessageName'; @@ -36,6 +37,7 @@ export * from './MessageRoles'; export * from './MessageTimestamp'; export * from './MessageUsername'; export * from './MessageEmoji'; +export * from './MessageHighlight'; export default Object.assign(Message, { Metrics: MessageMetrics, @@ -53,4 +55,5 @@ export default Object.assign(Message, { Roles: MessageRoles, Role: MessageRole, Divider: MessageDivider, + Highlight: MessageHighlight, }); diff --git a/packages/fuselage/src/components/Modal/ModalIcon.tsx b/packages/fuselage/src/components/Modal/ModalIcon.tsx index 50427834d8..7c8c30a56c 100644 --- a/packages/fuselage/src/components/Modal/ModalIcon.tsx +++ b/packages/fuselage/src/components/Modal/ModalIcon.tsx @@ -15,6 +15,6 @@ export const ModalIcon = ({ ...props }: ModalIconProps) => ( <Box {...props} display='flex' alignItems={alignItems}> - <Icon mb='x4' name={name} size={size} /> + <Icon mb={4} name={name} size={size} /> </Box> ); diff --git a/packages/fuselage/src/components/Modal/ModalThumb.tsx b/packages/fuselage/src/components/Modal/ModalThumb.tsx index 1f24cfdd53..623a185549 100644 --- a/packages/fuselage/src/components/Modal/ModalThumb.tsx +++ b/packages/fuselage/src/components/Modal/ModalThumb.tsx @@ -8,6 +8,6 @@ export type ModalThumbProps = ComponentProps<typeof Avatar>; export const ModalThumb = (props: ModalThumbProps) => ( <Box> - <Avatar size='x32' {...props} /> + <Avatar size='x28' {...props} /> </Box> ); diff --git a/packages/fuselage/src/components/Option/Option.stories.tsx b/packages/fuselage/src/components/Option/Option.stories.tsx index eddd58565e..7bfb761576 100644 --- a/packages/fuselage/src/components/Option/Option.stories.tsx +++ b/packages/fuselage/src/components/Option/Option.stories.tsx @@ -9,8 +9,8 @@ import { import type { ComponentStory, ComponentMeta } from '@storybook/react'; import React from 'react'; -import { Box, Menu, StatusBullet } from '../..'; -import { exampleAvatar, menuOptions } from '../../../.storybook/helpers.js'; +import { Box, Menu, StatusBullet, Tile } from '../..'; +import { exampleAvatar, menuOptions } from '../../../.storybook/helpers'; import { Avatar } from '../Avatar'; import Option from './Option'; import OptionAvatar from './OptionAvatar'; @@ -24,6 +24,13 @@ import OptionSkeleton from './OptionSkeleton'; export default { title: 'Navigation/Option', component: Option, + decorators: [ + (Story) => ( + <Tile position='relative' maxWidth={250} pi='0'> + <Story /> + </Tile> + ), + ], parameters: { docs: { description: { @@ -44,7 +51,7 @@ export default { } as ComponentMeta<typeof Menu>; export const Default: ComponentStory<typeof Option> = () => ( - <Box position='relative' maxWidth={250}> + <> <Option onClick={action('click')}> <OptionContent>Lorem Ipsum Lorem</OptionContent> </Option> @@ -61,11 +68,11 @@ export const Default: ComponentStory<typeof Option> = () => ( </OptionDescription> </OptionContent> </Option> - </Box> + </> ); export const WithAvatar: ComponentStory<typeof Option> = () => ( - <Box position='relative' maxWidth={250}> + <> <Option onClick={action('click')}> <OptionAvatar> <Avatar url={exampleAvatar} size='x28' /> @@ -91,11 +98,11 @@ export const WithAvatar: ComponentStory<typeof Option> = () => ( </OptionDescription> </OptionContent> </Option> - </Box> + </> ); export const WithPresence: ComponentStory<typeof Option> = () => ( - <Box position='relative' maxWidth={250}> + <> <Option onClick={action('click')}> <OptionColumn> <StatusBullet /> @@ -113,11 +120,11 @@ export const WithPresence: ComponentStory<typeof Option> = () => ( Lorem Ipsum Lorem Lorem Ipsum Lorem Lorem Ipsum Lorem Lorem Ipsum Lorem </OptionContent> </Option> - </Box> + </> ); export const WithMenu: ComponentStory<typeof Option> = () => ( - <Box position='relative' maxWidth={250}> + <> <Option onClick={action('click')}> <OptionContent>Lorem Ipsum Lorem</OptionContent> <OptionMenu> @@ -138,11 +145,11 @@ export const WithMenu: ComponentStory<typeof Option> = () => ( <Menu options={menuOptions} /> </OptionMenu> </Option> - </Box> + </> ); export const WithIcon: ComponentStory<typeof Option> = () => ( - <Box position='relative' maxWidth={250}> + <> <Option onClick={action('click')}> <OptionIcon name='bell' /> <OptionContent>Lorem Ipsum Lorem</OptionContent> @@ -165,22 +172,42 @@ export const WithIcon: ComponentStory<typeof Option> = () => ( <Menu options={menuOptions} /> </OptionMenu> </Option> - </Box> + </> ); +export const WithAndWithoutIcon: ComponentStory<typeof Option> = () => ( + <> + <Option onClick={action('click')} icon='star' label='Lorem Ipsum Lorem' /> + <Option onClick={action('click')} icon='user' label='Lorem Ipsum Lorem' /> + <Option + onClick={action('click')} + icon='hashtag' + label='Lorem Ipsum Lorem' + /> + <Option onClick={action('click')} gap label='Lorem Ipsum Lorem' /> + </> +); +WithAndWithoutIcon.parameters = { + docs: { + description: { + story: + " When using `Option`, you can also use the `gap` prop to add spacing to the left. If the list is mixed with items **with and without** icons, it's recommended to add the gap.", + }, + }, +}; export const Disabled: ComponentStory<typeof Option> = () => ( - <Box position='relative' maxWidth={330}> + <> <Option onClick={action('click')}> <OptionContent>Enabled</OptionContent> </Option> <Option disabled={true}> <OptionContent>Disabled</OptionContent> </Option> - </Box> + </> ); export const AsUserItem: ComponentStory<typeof Option> = () => ( - <Box position='relative' maxWidth={330}> + <> <Option onClick={action('click')}> <OptionAvatar> <Avatar url={exampleAvatar} size='x28' /> @@ -189,22 +216,17 @@ export const AsUserItem: ComponentStory<typeof Option> = () => ( <StatusBullet /> </OptionColumn> <OptionContent> - <Box withTruncatedText fontScale='p2'> - carla.culhane{' '} - <Box is='span' color='neutral-500'> - (carla hune) - </Box> + <Box> + carla.culhane <Box>(carla hune)</Box> </Box> </OptionContent> <OptionMenu> <Menu options={menuOptions} /> </OptionMenu> </Option> - </Box> + </> ); export const AsSkeleton: ComponentStory<typeof Option> = () => ( - <Box position='relative' maxWidth={330}> - <OptionSkeleton /> - </Box> + <OptionSkeleton /> ); diff --git a/packages/fuselage/src/components/Option/Option.styles.scss b/packages/fuselage/src/components/Option/Option.styles.scss index 6bc80f1da5..c35ceb35cb 100644 --- a/packages/fuselage/src/components/Option/Option.styles.scss +++ b/packages/fuselage/src/components/Option/Option.styles.scss @@ -21,7 +21,9 @@ $variants: ( display: list-item; - padding: lengths.padding(4) lengths.padding(12); + padding-block: lengths.padding(4); + padding-inline-start: lengths.padding(12); + padding-inline-end: lengths.padding(24); list-style: none; @@ -32,7 +34,8 @@ $variants: ( padding-block-start: lengths.padding(8); padding-block-end: lengths.padding(4); - padding-inline: lengths.padding(12); + padding-inline-start: lengths.padding(12); + padding-inline-end: lengths.padding(24); color: colors.font(default); } @@ -42,6 +45,10 @@ $variants: ( align-items: center; margin-inline: lengths.margin(-2); + + &--align-top { + align-items: flex-start !important; + } } &__icon { @@ -83,7 +90,7 @@ $variants: ( opacity: 0; } - .rcx-option__column { + &__column { @extend %column; display: flex; @@ -94,7 +101,18 @@ $variants: ( min-height: lengths.size(20); } - .rcx-option__description { + &__input { + display: flex; + + justify-content: center; + align-items: center; + + min-width: lengths.size(20); + min-height: lengths.size(20); + margin-inline-end: lengths.margin(-12); + } + + &__description { @include typography.use-font-scale(p2); @extend %column; display: inline; @@ -102,6 +120,17 @@ $variants: ( color: colors.font(secondary-info); } + &__description-block { + @include typography.use-font-scale(p2); + + padding: lengths.margin(4); + + white-space: normal; + word-break: break-word; + + color: colors.font(secondary-info); + } + &:hover, &--focus { background: colors.surface(hover); diff --git a/packages/fuselage/src/components/Option/Option.tsx b/packages/fuselage/src/components/Option/Option.tsx index 93770045fc..73bbb0afbe 100644 --- a/packages/fuselage/src/components/Option/Option.tsx +++ b/packages/fuselage/src/components/Option/Option.tsx @@ -5,9 +5,10 @@ import type { MouseEvent, AllHTMLAttributes, } from 'react'; -import React, { memo } from 'react'; +import React, { forwardRef, memo } from 'react'; import type { Icon } from '../..'; +import { OptionColumn } from '../..'; import { prevent } from '../../helpers/prevent'; import type Box from '../Box'; import OptionAvatar from './OptionAvatar'; @@ -24,65 +25,81 @@ type OptionProps = { className?: ComponentProps<typeof Box>['className']; ref?: Ref<Element>; icon?: ComponentProps<typeof Icon>['name']; + gap?: boolean; avatar?: ReactNode; title?: string; disabled?: boolean; value?: string; variant?: 'danger' | 'success' | 'warning' | 'primary'; - onClick?: (event: MouseEvent<HTMLDivElement>) => void; -} & AllHTMLAttributes<HTMLElement>; + onClick?: (event: MouseEvent<HTMLElement>) => void; + description?: ReactNode; +} & Omit<AllHTMLAttributes<HTMLElement>, 'label'>; const Option = memo( - ({ - is: Tag = 'li', - id, - children, - label, - focus, - selected, - className, - ref, - icon, - avatar, - title, - disabled, - onClick, - variant, - ...options - }: OptionProps) => ( - <Tag - key={id} - id={id} - ref={ref} - aria-selected={selected} - aria-disabled={String(disabled)} - title={title} - onClick={(e: React.MouseEvent<HTMLDivElement>) => { - if (disabled) { - prevent(e); - return; - } - onClick?.(e); - }} - {...options} - className={[ - 'rcx-option', + forwardRef( + ( + { + is: Tag = 'li', + id, + children, + label, + focus, + selected, className, - focus && 'rcx-option--focus', - selected && 'rcx-option--selected', - disabled && 'rcx-option--disabled', - variant && `rcx-option--${variant}`, - ] - .filter(Boolean) - .join(' ')} - > - <div className='rcx-option__wrapper'> - {avatar && <OptionAvatar>{avatar}</OptionAvatar>} - {icon && <OptionIcon name={icon} />} - {label && <OptionContent>{label}</OptionContent>} - {label !== children && children} - </div> - </Tag> + icon, + gap, + avatar, + title, + disabled, + variant, + onClick, + description, + ...props + }: OptionProps, + ref + ) => ( + <Tag + {...props} + key={id} + id={id} + ref={ref} + aria-selected={!!selected} + aria-disabled={!!disabled} + title={title} + onClick={(e: React.MouseEvent<HTMLDivElement>) => { + if (disabled) { + prevent(e); + return; + } + onClick?.(e); + }} + className={[ + 'rcx-option', + className, + focus && 'rcx-option--focus', + selected && 'rcx-option--selected', + disabled && 'rcx-option--disabled', + variant && `rcx-option--${variant}`, + ] + .filter(Boolean) + .join(' ')} + > + <div + className={[ + 'rcx-option__wrapper', + !!description && 'rcx-option__wrapper--align-top', + ] + .filter(Boolean) + .join(' ')} + > + {avatar && <OptionAvatar>{avatar}</OptionAvatar>} + {icon && <OptionIcon name={icon} />} + {gap && <OptionColumn />} + {label && <OptionContent>{label}</OptionContent>} + {label !== children && children} + </div> + </Tag> + ) ) ); diff --git a/packages/fuselage/src/components/Option/OptionDescriptionBlock.tsx b/packages/fuselage/src/components/Option/OptionDescriptionBlock.tsx new file mode 100644 index 0000000000..8722b62198 --- /dev/null +++ b/packages/fuselage/src/components/Option/OptionDescriptionBlock.tsx @@ -0,0 +1,12 @@ +import type { ReactNode } from 'react'; +import React from 'react'; + +type OptionDescriptionProps = { + children?: ReactNode; +}; + +const OptionDescriptionBlock = (props: OptionDescriptionProps) => ( + <div className='rcx-option__description-block' {...props} /> +); + +export default OptionDescriptionBlock; diff --git a/packages/fuselage/src/components/Option/OptionInput.tsx b/packages/fuselage/src/components/Option/OptionInput.tsx new file mode 100644 index 0000000000..f4df80ad50 --- /dev/null +++ b/packages/fuselage/src/components/Option/OptionInput.tsx @@ -0,0 +1,12 @@ +import type { ReactNode } from 'react'; +import React from 'react'; + +type OptionInputProps = { + children?: ReactNode; +}; + +const OptionInput = (props: OptionInputProps) => ( + <div className='rcx-option__input' {...props} /> +); + +export default OptionInput; diff --git a/packages/fuselage/src/components/Option/index.tsx b/packages/fuselage/src/components/Option/index.tsx index ecf8b8a437..20ed98550c 100644 --- a/packages/fuselage/src/components/Option/index.tsx +++ b/packages/fuselage/src/components/Option/index.tsx @@ -3,9 +3,11 @@ import OptionAvatar from './OptionAvatar'; import OptionColumn from './OptionColumn'; import OptionContent from './OptionContent'; import OptionDescription from './OptionDescription'; +import OptionDescriptionBlock from './OptionDescriptionBlock'; import OptionDivider from './OptionDivider'; import OptionHeader from './OptionHeader'; import OptionIcon from './OptionIcon'; +import OptionInput from './OptionInput'; import OptionMenu from './OptionMenu'; import OptionSkeleton from './OptionSkeleton'; import OptionTitle from './OptionTitle'; @@ -34,8 +36,10 @@ export { OptionAvatar }; export { OptionColumn }; export { OptionContent }; export { OptionDescription }; +export { OptionDescriptionBlock }; export { OptionDivider }; export { OptionIcon }; +export { OptionInput }; export { OptionMenu }; export { OptionSkeleton }; export { OptionTitle }; diff --git a/packages/fuselage/src/components/Options/Options.tsx b/packages/fuselage/src/components/Options/Options.tsx index f1d333ad11..755581aada 100644 --- a/packages/fuselage/src/components/Options/Options.tsx +++ b/packages/fuselage/src/components/Options/Options.tsx @@ -48,7 +48,7 @@ export const Empty = memo(({ customEmpty }: { customEmpty: string }) => ( export const Options = forwardRef( ( { - maxHeight = '144px', + maxHeight = 'x144', multiple, renderEmpty: EmptyComponent = Empty, options, @@ -142,3 +142,28 @@ export const Options = forwardRef( ); } ); +export const OptionContainer = forwardRef< + HTMLElement, + ComponentProps<typeof Box> +>(({ children, ...props }, ref) => ( + <Box rcx-options> + <Tile padding={0} paddingBlock={'x12'} paddingInline={0}> + <Scrollable vertical smooth> + <Tile + ref={ref} + elevation='0' + padding='none' + maxHeight='x240' + // onMouseDown={prevent} + // onClick={prevent} + // is='ol' + // aria-multiselectable={multiple || true} + // role='listbox' + {...props} + > + {children} + </Tile> + </Scrollable> + </Tile> + </Box> +)); diff --git a/packages/fuselage/src/components/PaginatedSelect/PaginatedMultiSelect.tsx b/packages/fuselage/src/components/PaginatedSelect/PaginatedMultiSelect.tsx index d260cc11c2..7304026725 100644 --- a/packages/fuselage/src/components/PaginatedSelect/PaginatedMultiSelect.tsx +++ b/packages/fuselage/src/components/PaginatedSelect/PaginatedMultiSelect.tsx @@ -26,7 +26,7 @@ import SelectAddon from '../Select/SelectAddon'; import SelectFocus from '../Select/SelectFocus'; const SelectedOptions = memo<ComponentProps<typeof Chip>>((props) => ( - <Chip maxWidth='150px' withTruncatedText {...props} /> + <Chip maxWidth='x150' withTruncatedText {...props} /> )); export type PaginatedMultiSelectOption = { diff --git a/packages/fuselage/src/components/PaginatedSelect/PaginatedSelect.tsx b/packages/fuselage/src/components/PaginatedSelect/PaginatedSelect.tsx index d195b561e1..b40ad4e006 100644 --- a/packages/fuselage/src/components/PaginatedSelect/PaginatedSelect.tsx +++ b/packages/fuselage/src/components/PaginatedSelect/PaginatedSelect.tsx @@ -120,7 +120,7 @@ export const PaginatedSelect = ({ <Box flexGrow={1} is='span' - mi='x4' + mi={4} rcx-select__item fontScale='p2m' color={valueLabel ? 'default' : 'hint'} diff --git a/packages/fuselage/src/components/PaginatedSelect/PaginatedSelectFiltered.tsx b/packages/fuselage/src/components/PaginatedSelect/PaginatedSelectFiltered.tsx index 9150a21334..08e764402d 100644 --- a/packages/fuselage/src/components/PaginatedSelect/PaginatedSelectFiltered.tsx +++ b/packages/fuselage/src/components/PaginatedSelect/PaginatedSelectFiltered.tsx @@ -29,7 +29,7 @@ export const PaginatedSelectFiltered = ({ ref: Ref<HTMLInputElement> ) => ( <InputBox.Input - mi='x4' + mi={4} flexGrow={1} className='rcx-select__focus' ref={ref} diff --git a/packages/fuselage/src/components/Position/Position.tsx b/packages/fuselage/src/components/Position/Position.tsx index 676eba78e4..ccff0187c8 100644 --- a/packages/fuselage/src/components/Position/Position.tsx +++ b/packages/fuselage/src/components/Position/Position.tsx @@ -1,4 +1,4 @@ -import type { Placements } from '@rocket.chat/fuselage-hooks'; +import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks'; import { usePosition } from '@rocket.chat/fuselage-hooks'; import type { RefObject, @@ -6,7 +6,7 @@ import type { ReactPortal, ReactElement, } from 'react'; -import { useRef, useMemo, useEffect, cloneElement } from 'react'; +import { useRef, useMemo, cloneElement, useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; import type Box from '../Box'; @@ -15,7 +15,7 @@ type PositionProps = { anchor: RefObject<Element>; children: ReactElement; margin?: number; - placement?: Placements; + placement?: UsePositionOptions['placement']; } & Omit<ComponentProps<typeof Box>, 'children' | 'margin'>; const Position = ({ @@ -38,17 +38,25 @@ const Position = ({ () => ({ position: 'fixed', ...positionStyle }), [positionStyle] ); - const portalContainer = useMemo(() => { + const [portalContainer] = useState(() => { + const prev = document.getElementById('position-container'); + if (prev) { + return prev; + } const element = document.createElement('div'); + + element.id = 'position-container'; + document.body.appendChild(element); return element; - }, []); + }); useEffect( - () => - function () { + () => () => { + if (portalContainer.childNodes.length === 0) { document.body.removeChild(portalContainer); - }, + } + }, [portalContainer] ); diff --git a/packages/fuselage/src/components/ProgressBar/ProgressBar.styles.scss b/packages/fuselage/src/components/ProgressBar/ProgressBar.styles.scss index 2c0dec5ca8..87268f42fc 100644 --- a/packages/fuselage/src/components/ProgressBar/ProgressBar.styles.scss +++ b/packages/fuselage/src/components/ProgressBar/ProgressBar.styles.scss @@ -38,10 +38,7 @@ $progress-bar-border-radius: theme( &--animated::before { position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; width: inherit; diff --git a/packages/fuselage/src/components/Select/Listbox.tsx b/packages/fuselage/src/components/Select/Listbox.tsx new file mode 100644 index 0000000000..531be7c9ab --- /dev/null +++ b/packages/fuselage/src/components/Select/Listbox.tsx @@ -0,0 +1,93 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ +import type { Node } from '@react-types/shared'; +import React, { useRef } from 'react'; +import type { AriaListBoxOptions } from 'react-aria'; +import { useListBox, useListBoxSection, useOption } from 'react-aria'; +import type { ListState } from 'react-stately'; + +import Option from '../Option'; + +interface ListBoxProps extends AriaListBoxOptions<unknown> { + listBoxRef?: React.RefObject<HTMLDivElement>; + state: ListState<unknown>; +} + +interface SectionProps { + section: Node<unknown>; + state: ListState<unknown>; +} + +interface OptionProps { + item: Node<unknown>; + state: ListState<unknown>; +} + +export function ListBox(props: ListBoxProps) { + const ref = useRef(null); + const { listBoxRef = ref, state } = props; + const { listBoxProps } = useListBox(props, state, listBoxRef); + + return ( + <div {...listBoxProps} ref={listBoxRef}> + {[...state.collection].map((item) => + item.type === 'section' ? ( + <ListBoxSection key={item.key} section={item} state={state} /> + ) : ( + <OptionAria key={item.key} item={item} state={state} /> + ) + )} + </div> + ); +} + +function ListBoxSection({ section, state }: SectionProps) { + const { itemProps, headingProps, groupProps } = useListBoxSection({ + 'heading': section.rendered, + 'aria-label': section['aria-label'], + }); + + return ( + <> + <li {...itemProps} className='pt-2'> + {section.rendered && ( + <span + {...headingProps} + className='text-xs font-bold uppercase text-gray-500 mx-3' + > + {section.rendered} + </span> + )} + <ul {...groupProps}> + {[...section.childNodes].map((node) => ( + <OptionAria key={node.key} item={node} state={state} /> + ))} + </ul> + </li> + </> + ); +} + +function OptionAria({ item, state }: OptionProps) { + const ref = React.useRef<HTMLLIElement>(null); + const { optionProps, isDisabled, isSelected, isFocused } = useOption( + { + key: item.key, + }, + state, + ref + ); + + return ( + <Option + ref={ref} + disabled={isDisabled} + selected={isSelected} + focus={isFocused} + key={item.key} + label={item.rendered} + {...optionProps} + > + {item.rendered} + </Option> + ); +} diff --git a/packages/fuselage/src/components/Select/Popover.tsx b/packages/fuselage/src/components/Select/Popover.tsx new file mode 100644 index 0000000000..4200233dfa --- /dev/null +++ b/packages/fuselage/src/components/Select/Popover.tsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import type { AriaPopoverProps } from 'react-aria'; +import { usePopover, DismissButton, Overlay } from 'react-aria'; +import type { OverlayTriggerState } from 'react-stately'; + +interface PopoverProps extends Omit<AriaPopoverProps, 'popoverRef'> { + children: React.ReactNode; + state: OverlayTriggerState; + className?: string; + popoverRef?: React.RefObject<HTMLDivElement>; +} + +export function Popover(props: PopoverProps) { + const ref = React.useRef<HTMLDivElement>(null); + const { popoverRef = ref, state, children, isNonModal } = props; + + const { popoverProps, underlayProps } = usePopover( + { + ...props, + containerPadding: 8, + popoverRef, + }, + state + ); + + return ( + <Overlay> + {!isNonModal && <div {...underlayProps} />} + <div {...popoverProps} ref={popoverRef}> + {!isNonModal && <DismissButton onDismiss={state.close} />} + {children} + <DismissButton onDismiss={state.close} /> + </div> + </Overlay> + ); +} diff --git a/packages/fuselage/src/components/Select/Select.stories.tsx b/packages/fuselage/src/components/Select/Select.stories.tsx index 70b0ff7f3b..6e2679d42b 100644 --- a/packages/fuselage/src/components/Select/Select.stories.tsx +++ b/packages/fuselage/src/components/Select/Select.stories.tsx @@ -9,16 +9,11 @@ import type { ComponentStory, ComponentMeta } from '@storybook/react'; import React from 'react'; import type { SelectOption } from '../..'; -import { Select, SelectFiltered } from '../..'; +import { Select } from '../..'; const options: SelectOption[] = Array.from({ - length: 10, -}).map((_, i) => [`${i + 1}`, `a teste ${i + 1}`]); - -const optionsEllipses: SelectOption[] = [ - ['11', 'Very very very very very very very very very large text'], - ...options, -]; + length: 12, +}).map((_, i) => [`${i + 1}`, `a test ${i + 1}`]); export default { title: 'Inputs/Select', @@ -43,79 +38,43 @@ export default { const Template: ComponentStory<typeof Select> = (args) => <Select {...args} />; +const TemplateControlled: ComponentStory<typeof Select> = (args) => { + const [value, setValue] = React.useState<React.Key>('3'); + + return <Select {...args} value={value} onChange={setValue as any} />; +}; + export const Default: ComponentStory<typeof Select> = Template.bind({}); Default.args = { - width: '250px', - placeholder: 'Placeholder here...', + 'placeholder': 'Placeholder here...', + options, + 'aria-label': 'Select', +}; + +export const Controlled = TemplateControlled.bind({}); +Controlled.args = { + 'aria-label': 'Controlled select', options, }; export const Error: ComponentStory<typeof Select> = Template.bind({}); Error.args = { - width: '250px', - error: 'Error', - placeholder: 'Placeholder here...', + 'aria-label': 'Error select', + 'error': 'Error', + 'placeholder': 'Placeholder here...', options, }; export const Disabled: ComponentStory<typeof Select> = Template.bind({}); Disabled.args = { - width: '250px', - disabled: true, - placeholder: 'Placeholder here...', + 'aria-label': 'Disabled select', + 'disabled': true, + 'placeholder': 'Placeholder here...', options, }; export const NoPlaceholder: ComponentStory<typeof Select> = Template.bind({}); NoPlaceholder.args = { - width: '250px', - options, -}; - -const TemplateWithFilter: ComponentStory<typeof Select> = (args) => ( - <SelectFiltered {...args} /> -); - -export const WithFilter: ComponentStory<typeof Select> = - TemplateWithFilter.bind({}); -WithFilter.args = { - width: '250px', - placeholder: 'Placeholder here...', - options, -}; - -export const WithFilterAndEllipses: ComponentStory<typeof Select> = - TemplateWithFilter.bind({}); -WithFilterAndEllipses.args = { - width: '250px', - placeholder: 'Placeholder here...', - options: optionsEllipses, - value: '11', -}; - -export const WithEmptyOptions: ComponentStory<typeof Select> = - TemplateWithFilter.bind({}); -WithEmptyOptions.args = { - width: '250px', - placeholder: 'Placeholder here...', - options: [], -}; - -export const WithAddon: ComponentStory<typeof Select> = TemplateWithFilter.bind( - {} -); -WithAddon.args = { - width: '250px', - placeholder: 'Placeholder here...', + 'aria-label': 'No placeholder select', options, - addonIcon: 'magnifier', -}; - -export const CustomEmpty: ComponentStory<typeof Select> = - TemplateWithFilter.bind({}); -CustomEmpty.args = { - width: '250px', - placeholder: 'Placeholder here...', - options: [], - customEmpty: 'Custom empty placeholder', }; diff --git a/packages/fuselage/src/components/Select/Select.styles.scss b/packages/fuselage/src/components/Select/Select.styles.scss index 3b460fcad5..97acaebcb0 100644 --- a/packages/fuselage/src/components/Select/Select.styles.scss +++ b/packages/fuselage/src/components/Select/Select.styles.scss @@ -1,12 +1,14 @@ +@use '../../styles/lengths.scss'; @use '../../styles/typography.scss'; .rcx-select { @extend %rcx-input-box; - position: relative; align-items: center; + min-height: lengths.size(40); + // TODO move to .rcx-input-box &__item { @include typography.use-text-ellipsis; diff --git a/packages/fuselage/src/components/Select/Select.tsx b/packages/fuselage/src/components/Select/Select.tsx index b0802f4556..f599f44188 100644 --- a/packages/fuselage/src/components/Select/Select.tsx +++ b/packages/fuselage/src/components/Select/Select.tsx @@ -1,243 +1,38 @@ -import { - useMergedRefs, - useMutableCallback, - useResizeObserver, - useOutsideClick, -} from '@rocket.chat/fuselage-hooks'; -import type { - ComponentProps, - DependencyList, - Ref, - ElementType, - ReactNode, -} from 'react'; -import React, { useState, useRef, useEffect, forwardRef, useMemo } from 'react'; +import type { AriaSelectProps } from '@react-types/select'; +import type { ComponentProps, Key } from 'react'; +import React from 'react'; +import { Item } from 'react-stately'; -import { isForwardRefType } from '../../helpers/isForwardRefType'; -import AnimatedVisibility from '../AnimatedVisibility'; -import Box from '../Box'; -import { Icon } from '../Icon'; -import Margins from '../Margins'; -import type { OptionType } from '../Options'; -import { Options, useCursor } from '../Options'; -import PositionAnimated from '../PositionAnimated'; -import SelectAddon from './SelectAddon'; -import type { SelectAnchorParams } from './SelectAnchorParams'; -import SelectFocus from './SelectFocus'; +import { SelectAria } from './SelectAria'; -export type SelectOption = readonly [ - value: string, - label: string, - selected?: boolean -]; +type SelectOption = readonly [value: string, label: string, selected?: boolean]; -type WrapperProps = ComponentProps<typeof Box>; - -const Wrapper = forwardRef((props: WrapperProps, ref: Ref<HTMLDivElement>) => ( - <Box is='div' rcx-select__wrapper ref={ref} {...props} /> -)); - -const useDidUpdate = (func: () => void, deps: DependencyList | undefined) => { - const didMount = useRef(false); - const fn = useMutableCallback(func); - - useEffect(() => { - if (didMount.current) { - fn(); - } - didMount.current = true; - }, deps || []); -}; - -export type SelectProps = Omit<ComponentProps<typeof Box>, 'onChange'> & { - anchor?: ElementType; +type SelectProps<T, V extends Key> = Omit< + AriaSelectProps<T>, + 'value' | 'onChange' | 'children' +> & { error?: string; + placeholder?: string; + value?: V | null; + onChange?: ((key: V) => any) | undefined; options: SelectOption[]; - onChange: (value: SelectOption[0]) => void; - getLabel?: (params: SelectOption) => SelectOption[1]; - getValue?: (params: SelectOption) => SelectOption[0]; - filter?: string; - renderOptions?: ElementType; - renderItem?: ElementType; - renderSelected?: ElementType; - customEmpty?: string; - addonIcon?: ComponentProps<typeof Icon>['name']; -}; - -export const Select = forwardRef( - ( - { - value, - filter, - error, - disabled, - options = [], - anchor: Anchor = SelectFocus, - onChange = () => {}, - getValue = ([value] = ['', '']) => value, - getLabel = ([_, label] = ['', '']) => label, - placeholder = '', - renderItem, - renderSelected: RenderSelected, - renderOptions: _Options = Options, - addonIcon, - customEmpty, - ...props - }: SelectProps, - ref: Ref<HTMLInputElement> - ) => { - const [internalValue, setInternalValue] = useState(value || ''); - - const internalChangedByKeyboard = useMutableCallback(([value]) => { - setInternalValue(value); - onChange(value); - }); - - const option = options.find( - (option) => getValue(option) === internalValue - ) as SelectOption; - - const index = options.indexOf(option); - - const filteredOptions = useMemo<OptionType[]>((): OptionType[] => { - const mapOptions = ([value, label]: SelectOption): OptionType => { - if (internalValue === value) { - return [value, label, true]; - } - return [value, label]; - }; - - const applyFilter = ([, option]: SelectOption) => - !filter || ~option.toLowerCase().indexOf(filter.toLowerCase()); - - return options.filter(applyFilter).map(mapOptions); - }, [options, internalValue, filter]); - - const [cursor, handleKeyDown, handleKeyUp, reset, [visible, hide, show]] = - useCursor(index, filteredOptions, internalChangedByKeyboard); - - const innerRef = useRef<HTMLInputElement | null>(null); - const anchorRef = useMergedRefs(ref, innerRef); - - const removeFocusClass = () => - innerRef.current?.classList.remove('focus-visible'); - - const internalChangedByClick = useMutableCallback(([value]) => { - setInternalValue(value); - onChange(value); - removeFocusClass(); - hide(); - }); - - const renderAnchor = (params: SelectAnchorParams) => { - if (isForwardRefType(Anchor)) { - return <Anchor {...params} />; - } - - if (typeof Anchor === 'function') { - return (Anchor as (params: SelectAnchorParams) => ReactNode)(params); - } - - return null; - }; - - const { ref: containerRef, borderBoxSize } = useResizeObserver(); - - useDidUpdate(reset, [filter, internalValue]); - useOutsideClick([containerRef], removeFocusClass); - - const valueLabel = getLabel(option); - - const visibleText = - (filter === undefined || visible === AnimatedVisibility.HIDDEN) && - (valueLabel || placeholder || typeof placeholder === 'string'); - - const handleClick = useMutableCallback(() => { - if (innerRef.current?.classList.contains('focus-visible')) { - removeFocusClass(); - return hide(); - } - - innerRef.current?.classList.add('focus-visible'); - innerRef.current?.focus(); - return show(); - }); - - return ( - <Box - rcx-select - disabled={disabled} - ref={containerRef} - onClick={handleClick} - className={useMemo( - () => [error && 'invalid', disabled && 'disabled'], - [error, disabled] - )} - {...props} - > - <Wrapper - display='flex' - mi='neg-x4' - rcx-select__wrapper--hidden={!!visibleText} +} & Omit<React.AllHTMLAttributes<HTMLElement>, 'onChange'>; + +export const Select = function Select<T extends object, V extends Key>({ + options, + ...props +}: SelectProps<T, V>) { + return ( + <SelectAria {...(props as ComponentProps<typeof SelectAria>)}> + {options.map((option) => ( + <Item + title={option[1] ?? option[0]} + textValue={option[0]} + key={option[0]} > - {visibleText && - (RenderSelected ? ( - <RenderSelected - role='option' - value={getValue(option)} - label={valueLabel} - key={getValue(option)} - onClick={internalChangedByClick} - /> - ) : ( - <Box - flexGrow={1} - is='span' - mi='x4' - rcx-select__item - fontScale='p2' - color={valueLabel ? 'default' : 'hint'} - > - {visibleText} - </Box> - ))} - {renderAnchor({ - ref: anchorRef, - children: !value ? option || placeholder : null, - disabled: disabled ?? false, - onClick: show, - onBlur: hide, - onKeyDown: handleKeyDown, - onKeyUp: handleKeyUp, - })} - <Margins inline='x4'> - <SelectAddon - children={ - <Icon - name={ - visible === AnimatedVisibility.VISIBLE - ? 'chevron-up' - : addonIcon || 'chevron-down' - } - size='x20' - /> - } - /> - </Margins> - </Wrapper> - <PositionAnimated visible={visible} anchor={containerRef}> - <_Options - width={borderBoxSize.inlineSize} - role='listbox' - filter={filter} - options={filteredOptions} - onSelect={internalChangedByClick} - renderItem={renderItem} - cursor={cursor} - customEmpty={customEmpty} - /> - </PositionAnimated> - </Box> - ); - } -); + {option[1] ?? option[0]} + </Item> + ))} + </SelectAria> + ); +}; diff --git a/packages/fuselage/src/components/Select/SelectAria.tsx b/packages/fuselage/src/components/Select/SelectAria.tsx new file mode 100644 index 0000000000..f011842f2b --- /dev/null +++ b/packages/fuselage/src/components/Select/SelectAria.tsx @@ -0,0 +1,103 @@ +import type { AriaSelectProps } from '@react-types/select'; +import { useResizeObserver } from '@rocket.chat/fuselage-hooks'; +import type { Key } from 'react'; +import React from 'react'; +import { + useSelect, + HiddenSelect, + useButton, + mergeProps, + useFocusRing, +} from 'react-aria'; +import { useSelectState } from 'react-stately'; + +import Box from '../Box/Box'; +import { Icon } from '../Icon'; +import { OptionContainer } from '../Options'; +import { ListBox } from './Listbox'; +import { Popover } from './Popover'; + +export { Item } from 'react-stately'; + +export const SelectAria = function SelectAria<T extends object>({ + disabled, + error, + placeholder, + value, + onChange, + ...props +}: Omit<AriaSelectProps<T>, 'value' | 'onChange'> & { + error?: string; + placeholder?: string; + value?: Key | null; + onChange?: ((key: Key) => any) | undefined; +} & React.AllHTMLAttributes<HTMLElement>) { + const state = useSelectState({ + isDisabled: disabled, + selectedKey: value, + onSelectionChange: onChange, + ...props, + }); + + const { ref, borderBoxSize } = useResizeObserver<any>(); + + const { triggerProps, valueProps, menuProps } = useSelect(props, state, ref); + + const { buttonProps } = useButton(triggerProps, ref); + + const { focusProps, isFocusVisible } = useFocusRing(); + + return ( + <> + <Box + disabled={disabled} + rcx-select + {...mergeProps(buttonProps, focusProps)} + is='button' + display='flex' + flexDirection='row' + fontScale='p2' + ref={ref} + justifyContent='space-between' + className={[ + error && 'invalid', + disabled && 'disabled', + (isFocusVisible || state.isOpen) && 'focus', + ] + .filter(Boolean) + .join(' ')} + > + <HiddenSelect + state={state} + triggerRef={ref} + label={props.label} + name={props.name} + /> + <Box + is='span' + {...valueProps} + color={state.selectedItem ? 'default' : 'hint'} + > + {state.selectedItem ? state.selectedItem.rendered : placeholder} + </Box> + + <Icon + color='default' + name={state.isOpen ? 'chevron-up' : 'chevron-down'} + size='x20' + /> + </Box> + {state.isOpen && ( + <Popover state={state} triggerRef={ref} placement='bottom' offset={4}> + <OptionContainer + style={{ + width: borderBoxSize?.inlineSize, + }} + > + <ListBox {...menuProps} state={state} /> + </OptionContainer> + </Popover> + )} + </> + ); +}; diff --git a/packages/fuselage/src/components/Select/SelectFiltered.tsx b/packages/fuselage/src/components/Select/SelectFiltered.tsx index aef7d3f90b..994446917a 100644 --- a/packages/fuselage/src/components/Select/SelectFiltered.tsx +++ b/packages/fuselage/src/components/Select/SelectFiltered.tsx @@ -1,12 +1,12 @@ import type { ComponentProps, Dispatch, SetStateAction } from 'react'; import React, { useState } from 'react'; -import { Select } from '.'; +import { SelectLegacy } from '.'; import type { Icon } from '..'; import type { SelectAnchorParams } from './SelectAnchorParams'; import SelectFilteredAnchor from './SelectFilteredAnchor'; -export type SelectFilteredProps = ComponentProps<typeof Select> & { +export type SelectFilteredProps = ComponentProps<typeof SelectLegacy> & { filter?: string; setFilter?: Dispatch<SetStateAction<string>>; addonIcon?: ComponentProps<typeof Icon>['name']; @@ -22,7 +22,7 @@ export const SelectFiltered = ({ const [filter, setFilter] = useState(''); return ( - <Select + <SelectLegacy placeholder={placeholder} filter={propFilter || filter} options={options} diff --git a/packages/fuselage/src/components/Select/SelectFilteredAnchor.tsx b/packages/fuselage/src/components/Select/SelectFilteredAnchor.tsx index 792fb2a20e..5e6f04e2cc 100644 --- a/packages/fuselage/src/components/Select/SelectFilteredAnchor.tsx +++ b/packages/fuselage/src/components/Select/SelectFilteredAnchor.tsx @@ -34,7 +34,7 @@ const SelectFilteredAnchor = forwardRef(function SelectFilteredAnchor( ) { return ( <InputBox.Input - mi='x4' + mi={4} flexGrow={1} className='rcx-select__focus' ref={ref} diff --git a/packages/fuselage/src/components/Select/SelectLegacy.tsx b/packages/fuselage/src/components/Select/SelectLegacy.tsx new file mode 100644 index 0000000000..0ef5798859 --- /dev/null +++ b/packages/fuselage/src/components/Select/SelectLegacy.tsx @@ -0,0 +1,243 @@ +import { + useMergedRefs, + useMutableCallback, + useResizeObserver, + useOutsideClick, +} from '@rocket.chat/fuselage-hooks'; +import type { + ComponentProps, + DependencyList, + Ref, + ElementType, + ReactNode, +} from 'react'; +import React, { useState, useRef, useEffect, forwardRef, useMemo } from 'react'; + +import { isForwardRefType } from '../../helpers/isForwardRefType'; +import AnimatedVisibility from '../AnimatedVisibility'; +import Box from '../Box'; +import { Icon } from '../Icon'; +import Margins from '../Margins'; +import type { OptionType } from '../Options'; +import { Options, useCursor } from '../Options'; +import PositionAnimated from '../PositionAnimated'; +import SelectAddon from './SelectAddon'; +import type { SelectAnchorParams } from './SelectAnchorParams'; +import SelectFocus from './SelectFocus'; + +export type SelectOption = readonly [ + value: string, + label: string, + selected?: boolean +]; + +type WrapperProps = ComponentProps<typeof Box>; + +const Wrapper = forwardRef((props: WrapperProps, ref: Ref<HTMLDivElement>) => ( + <Box is='div' rcx-select__wrapper ref={ref} {...props} /> +)); + +const useDidUpdate = (func: () => void, deps: DependencyList | undefined) => { + const didMount = useRef(false); + const fn = useMutableCallback(func); + + useEffect(() => { + if (didMount.current) { + fn(); + } + didMount.current = true; + }, deps || []); +}; + +export type SelectProps = Omit<ComponentProps<typeof Box>, 'onChange'> & { + anchor?: ElementType; + error?: string; + options: SelectOption[]; + onChange: (value: SelectOption[0]) => void; + getLabel?: (params: SelectOption) => SelectOption[1]; + getValue?: (params: SelectOption) => SelectOption[0]; + filter?: string; + renderOptions?: ElementType; + renderItem?: ElementType; + renderSelected?: ElementType; + customEmpty?: string; + addonIcon?: ComponentProps<typeof Icon>['name']; +}; + +export const SelectLegacy = forwardRef( + ( + { + value, + filter, + error, + disabled, + options = [], + anchor: Anchor = SelectFocus, + onChange = () => {}, + getValue = ([value] = ['', '']) => value, + getLabel = ([_, label] = ['', '']) => label, + placeholder = '', + renderItem, + renderSelected: RenderSelected, + renderOptions: _Options = Options, + addonIcon, + customEmpty, + ...props + }: SelectProps, + ref: Ref<HTMLInputElement> + ) => { + const [internalValue, setInternalValue] = useState(value || ''); + + const internalChangedByKeyboard = useMutableCallback(([value]) => { + setInternalValue(value); + onChange(value); + }); + + const option = options.find( + (option) => getValue(option) === internalValue + ) as SelectOption; + + const index = options.indexOf(option); + + const filteredOptions = useMemo<OptionType[]>((): OptionType[] => { + const mapOptions = ([value, label]: SelectOption): OptionType => { + if (internalValue === value) { + return [value, label, true]; + } + return [value, label]; + }; + + const applyFilter = ([, option]: SelectOption) => + !filter || ~option.toLowerCase().indexOf(filter.toLowerCase()); + + return options.filter(applyFilter).map(mapOptions); + }, [options, internalValue, filter]); + + const [cursor, handleKeyDown, handleKeyUp, reset, [visible, hide, show]] = + useCursor(index, filteredOptions, internalChangedByKeyboard); + + const innerRef = useRef<HTMLInputElement | null>(null); + const anchorRef = useMergedRefs(ref, innerRef); + + const removeFocusClass = () => + innerRef.current?.classList.remove('focus-visible'); + + const internalChangedByClick = useMutableCallback(([value]) => { + setInternalValue(value); + onChange(value); + removeFocusClass(); + hide(); + }); + + const renderAnchor = (params: SelectAnchorParams) => { + if (isForwardRefType(Anchor)) { + return <Anchor {...params} />; + } + + if (typeof Anchor === 'function') { + return (Anchor as (params: SelectAnchorParams) => ReactNode)(params); + } + + return null; + }; + + const { ref: containerRef, borderBoxSize } = useResizeObserver(); + + useDidUpdate(reset, [filter, internalValue]); + useOutsideClick([containerRef], removeFocusClass); + + const valueLabel = getLabel(option); + + const visibleText = + (filter === undefined || visible === AnimatedVisibility.HIDDEN) && + (valueLabel || placeholder || typeof placeholder === 'string'); + + const handleClick = useMutableCallback(() => { + if (innerRef.current?.classList.contains('focus-visible')) { + removeFocusClass(); + return hide(); + } + + innerRef.current?.classList.add('focus-visible'); + innerRef.current?.focus(); + return show(); + }); + + return ( + <Box + rcx-select + disabled={disabled} + ref={containerRef} + onClick={handleClick} + className={useMemo( + () => [error && 'invalid', disabled && 'disabled'], + [error, disabled] + )} + {...props} + > + <Wrapper + display='flex' + mi='neg-x4' + rcx-select__wrapper--hidden={!!visibleText} + > + {visibleText && + (RenderSelected ? ( + <RenderSelected + role='option' + value={getValue(option)} + label={valueLabel} + key={getValue(option)} + onClick={internalChangedByClick} + /> + ) : ( + <Box + flexGrow={1} + is='span' + mi={4} + rcx-select__item + fontScale='p2' + color={valueLabel ? 'default' : 'hint'} + > + {visibleText} + </Box> + ))} + {renderAnchor({ + ref: anchorRef, + children: !value ? option || placeholder : null, + disabled: disabled ?? false, + onClick: show, + onBlur: hide, + onKeyDown: handleKeyDown, + onKeyUp: handleKeyUp, + })} + <Margins inline='x4'> + <SelectAddon + children={ + <Icon + name={ + visible === AnimatedVisibility.VISIBLE + ? 'chevron-up' + : addonIcon || 'chevron-down' + } + size='x20' + /> + } + /> + </Margins> + </Wrapper> + <PositionAnimated visible={visible} anchor={containerRef}> + <_Options + width={borderBoxSize.inlineSize} + role='listbox' + filter={filter} + options={filteredOptions} + onSelect={internalChangedByClick} + renderItem={renderItem} + cursor={cursor} + customEmpty={customEmpty} + /> + </PositionAnimated> + </Box> + ); + } +); diff --git a/packages/fuselage/src/components/Select/index.ts b/packages/fuselage/src/components/Select/index.ts index 18e7ed5869..d9a11afb23 100644 --- a/packages/fuselage/src/components/Select/index.ts +++ b/packages/fuselage/src/components/Select/index.ts @@ -1,2 +1,3 @@ +export * from './SelectLegacy'; export * from './Select'; export * from './SelectFiltered'; diff --git a/packages/fuselage/src/components/SelectInput/SelectInput.spec.tsx b/packages/fuselage/src/components/SelectInput/SelectInput.spec.tsx index 9c39064c14..ac32100909 100644 --- a/packages/fuselage/src/components/SelectInput/SelectInput.spec.tsx +++ b/packages/fuselage/src/components/SelectInput/SelectInput.spec.tsx @@ -4,5 +4,5 @@ import React from 'react'; import { SelectInput } from '.'; it('renders without crashing', () => { - render(<SelectInput options={[]} onChange={() => {}} addon={undefined} />); + render(<SelectInput />); }); diff --git a/packages/fuselage/src/components/SelectInput/SelectInput.tsx b/packages/fuselage/src/components/SelectInput/SelectInput.tsx index 026f5421d9..d08a3ff9bd 100644 --- a/packages/fuselage/src/components/SelectInput/SelectInput.tsx +++ b/packages/fuselage/src/components/SelectInput/SelectInput.tsx @@ -8,9 +8,9 @@ type SelectInputOptions = readonly (readonly [string, string])[]; type SelectInputProps = Omit<ComponentProps<typeof InputBox>, 'type'> & { error?: string; - options: SelectInputOptions; + options?: SelectInputOptions; htmlSize?: number; - addon: ReactNode; + addon?: ReactNode; }; export const SelectInput = forwardRef(function SelectInput( diff --git a/packages/fuselage/src/components/Sidebar/Sidebar.stories.tsx b/packages/fuselage/src/components/Sidebar/Sidebar.stories.tsx index 9b134203ba..5fcd391a64 100644 --- a/packages/fuselage/src/components/Sidebar/Sidebar.stories.tsx +++ b/packages/fuselage/src/components/Sidebar/Sidebar.stories.tsx @@ -349,36 +349,36 @@ export const WithFooter: ComponentStory<typeof Sidebar> = () => ( export const Admin: ComponentStory<typeof Sidebar> = () => ( <div className='rcx-sidebar'> - <Box display='flex' is='header' pbs='x16' pbe='x8' pi='x24'> + <Box display='flex' is='header' pbs={16} pbe={8} pi={24}> <Box fontSize='p2' fontWeight='p2'> Administration </Box> - <Box mi='x8'> + <Box mi={8}> <Tag variant='featured'>Development</Tag> </Box> </Box> <Divider /> <SidebarItem clickable> - <Box display='flex' justifyContent='center' pb='x8'> - <Icon name='import' mi='x4' size='x20' /> + <Box display='flex' justifyContent='center' pb={8}> + <Icon name='import' mi={4} size='x20' /> Import </Box> </SidebarItem> <SidebarItem clickable> - <Box display='flex' justifyContent='center' pb='x8'> - <Icon name='user' mi='x4' size='x20' /> + <Box display='flex' justifyContent='center' pb={8}> + <Icon name='user' mi={4} size='x20' /> Users </Box> </SidebarItem> <SidebarItem clickable> - <Box display='flex' justifyContent='center' pb='x8'> - <Icon name='hashtag' mi='x4' size='x20' /> + <Box display='flex' justifyContent='center' pb={8}> + <Icon name='hashtag' mi={4} size='x20' /> Rooms </Box> </SidebarItem> <SidebarItem clickable> - <Box display='flex' justifyContent='center' pb='x8'> - <Icon name='cube' mi='x4' size='x20' /> + <Box display='flex' justifyContent='center' pb={8}> + <Icon name='cube' mi={4} size='x20' /> Apps </Box> </SidebarItem> @@ -387,11 +387,11 @@ export const Admin: ComponentStory<typeof Sidebar> = () => ( export const WithBanner: ComponentStory<typeof Sidebar> = () => ( <div className='rcx-sidebar'> - <Box display='flex' is='header' pbs='x16' pbe='x8' pi='x24'> + <Box display='flex' is='header' pbs={16} pbe={8} pi={24}> <Box fontSize='p2' fontWeight='p2'> Administration </Box> - <Box mi='x8'> + <Box mi={8}> <Tag variant='featured'>Development</Tag> </Box> </Box> @@ -403,26 +403,26 @@ export const WithBanner: ComponentStory<typeof Sidebar> = () => ( onClose={action('click')} /> <SidebarItem clickable> - <Box display='flex' justifyContent='center' pb='x8'> - <Icon name='import' mi='x4' size='x20' /> + <Box display='flex' justifyContent='center' pb={8}> + <Icon name='import' mi={4} size='x20' /> Import </Box> </SidebarItem> <SidebarItem clickable> - <Box display='flex' justifyContent='center' pb='x8'> - <Icon name='user' mi='x4' size='x20' /> + <Box display='flex' justifyContent='center' pb={8}> + <Icon name='user' mi={4} size='x20' /> Users </Box> </SidebarItem> <SidebarItem clickable> - <Box display='flex' justifyContent='center' pb='x8'> - <Icon name='hashtag' mi='x4' size='x20' /> + <Box display='flex' justifyContent='center' pb={8}> + <Icon name='hashtag' mi={4} size='x20' /> Rooms </Box> </SidebarItem> <SidebarItem clickable> - <Box display='flex' justifyContent='center' pb='x8'> - <Icon name='cube' mi='x4' size='x20' /> + <Box display='flex' justifyContent='center' pb={8}> + <Icon name='cube' mi={4} size='x20' /> Apps </Box> </SidebarItem> @@ -432,7 +432,7 @@ export const WithBanner: ComponentStory<typeof Sidebar> = () => ( export const CustomBannerContent: ComponentStory<typeof SidebarBanner> = () => ( <SidebarBanner> <Box display='flex' justifyContent='space-between'> - <Icon name='modal-warning' size='x20' mi='x8' /> + <Icon name='modal-warning' size='x20' mi={8} /> <div>This is a customized banner content</div> </Box> </SidebarBanner> diff --git a/packages/fuselage/src/components/Sidebar/Sidebar.styles.scss b/packages/fuselage/src/components/Sidebar/Sidebar.styles.scss index 7eec865a05..d29edf941d 100644 --- a/packages/fuselage/src/components/Sidebar/Sidebar.styles.scss +++ b/packages/fuselage/src/components/Sidebar/Sidebar.styles.scss @@ -23,14 +23,9 @@ $sidebar-color-font-default: theme( colors.font(default) ); -$sidebar-color-font-primary: theme( - 'sidebar-color-font-primary', - colors.font(annotation) -); - -$sidebar-color-font-secondary: theme( - 'sidebar-color-font-secondary', - colors.font(secondary-info) +$sidebar-color-font-title: theme( + 'sidebar-color-font-title', + colors.font(titles-labels) ); $sidebar-color-stroke-default: theme( @@ -163,7 +158,7 @@ $sidebar-banner-color-danger: theme( } .rcx-sidebar { - color: $sidebar-color-font-secondary; + color: $sidebar-color-font-default; background: $sidebar-color-surface-default; &--divider { @@ -183,10 +178,6 @@ $sidebar-banner-color-danger: theme( color: $sidebar-item-color; - &--section { - padding-block-start: lengths.padding(4); - } - &--toolbox { height: $sidebar-section-height; } @@ -204,7 +195,7 @@ $sidebar-banner-color-danger: theme( &__title { @include typography.use-font-scale(p2m); - color: $sidebar-color-font-default; + color: $sidebar-color-font-title; } } @@ -215,7 +206,7 @@ $sidebar-banner-color-danger: theme( padding-block: lengths.padding(4); padding-inline: lengths.padding(16); - color: $sidebar-color-font-secondary; + color: $sidebar-color-font-default; &__wrapper { @extend %sidebar-base; @@ -351,7 +342,7 @@ $sidebar-banner-color-danger: theme( @include typography.use-font-scale(c2); @include typography.use-with-truncated-text(); - color: $sidebar-color-font-secondary; + color: $sidebar-color-font-default; } &-section { diff --git a/packages/fuselage/src/components/Skeleton/Skeleton.styles.scss b/packages/fuselage/src/components/Skeleton/Skeleton.styles.scss index fc1d991764..1c6d7bfbb2 100644 --- a/packages/fuselage/src/components/Skeleton/Skeleton.styles.scss +++ b/packages/fuselage/src/components/Skeleton/Skeleton.styles.scss @@ -8,6 +8,8 @@ animation: rcx-skeleton__animation 1s linear 0s infinite running; + border-radius: lengths.border-radius(medium); + background-color: colors.stroke(extra-dark); &--text { @@ -17,8 +19,6 @@ transform: scale(1, 0.6); transform-origin: 0 60%; - border-radius: lengths.border-radius(medium); - &:empty::before { content: '\00a0'; } diff --git a/packages/fuselage/src/components/Slider/Slider.stories.tsx b/packages/fuselage/src/components/Slider/Slider.stories.tsx index bd774beb33..63b31a045f 100644 --- a/packages/fuselage/src/components/Slider/Slider.stories.tsx +++ b/packages/fuselage/src/components/Slider/Slider.stories.tsx @@ -31,7 +31,12 @@ export default { } as ComponentMeta<typeof Slider>; const Template: ComponentStory<typeof Slider> = (args) => ( - <Box width='500px' minHeight='100%' display='flex' alignItems='center'> + <Box width='x300' display='flex' alignItems='center'> + <Slider {...args} /> + </Box> +); +const TemplateVertical: ComponentStory<typeof Slider> = (args) => ( + <Box h='x200' display='flex' alignItems='center'> <Slider {...args} /> </Box> ); @@ -63,18 +68,6 @@ Default.args = { 'maxValue': 500, } as const; -export const Small: ComponentStory<typeof Slider> = Template.bind({}); -Small.args = { - 'aria-label': 'aria-range-label', - 'small': true, -} as const; - -export const Large: ComponentStory<typeof Slider> = Template.bind({}); -Large.args = { - 'aria-label': 'aria-range-label', - 'large': true, -} as const; - export const NoOutput: ComponentStory<typeof Slider> = Template.bind({}); NoOutput.args = { 'showOutput': false, @@ -87,16 +80,17 @@ WithLabel.args = { 'aria-label': 'range', } as const; -export const Vertical: ComponentStory<typeof Slider> = Template.bind({}); +export const Vertical: ComponentStory<typeof Slider> = TemplateVertical.bind( + {} +); Vertical.args = { 'label': 'Range', 'aria-label': 'range', 'orientation': 'vertical', } as const; -export const VerticalMultiThumb: ComponentStory<typeof Slider> = Template.bind( - {} -); +export const VerticalMultiThumb: ComponentStory<typeof Slider> = + TemplateVertical.bind({}); VerticalMultiThumb.args = { 'label': 'Range', 'aria-label': 'range', @@ -104,13 +98,6 @@ VerticalMultiThumb.args = { 'multiThumb': true, } as const; -export const VerticalSmall: ComponentStory<typeof Slider> = Template.bind({}); -VerticalSmall.args = { - 'aria-label': 'aria-range-label', - 'small': true, - 'orientation': 'vertical', -} as const; - export const WithDefaultValue: ComponentStory<typeof Slider> = Template.bind( {} ); diff --git a/packages/fuselage/src/components/Slider/Slider.tsx b/packages/fuselage/src/components/Slider/Slider.tsx index c8299034cf..7502a17d00 100644 --- a/packages/fuselage/src/components/Slider/Slider.tsx +++ b/packages/fuselage/src/components/Slider/Slider.tsx @@ -35,11 +35,6 @@ type SliderProps<T extends number | number[]> = AriaAttributes & { orientation?: 'horizontal' | 'vertical'; disabled?: boolean; defaultValue?: T; - small?: boolean; - /** - * 100% of parent's dimention - */ - large?: boolean; } & ( | { value: T; @@ -61,8 +56,6 @@ export function Slider<T extends number | [min: number, max: number]>( multiThumb, maxValue, minValue, - small, - large, } = props; // Get a defaultValue in the range for multiThumb @@ -79,6 +72,8 @@ export function Slider<T extends number | [min: number, max: number]>( } return [0, 100] as T; } + + return undefined; }; const { defaultValue = getMultiThumbDefaultValue() } = props; @@ -117,12 +112,12 @@ export function Slider<T extends number | [min: number, max: number]>( ${isHorizontal && css` flex-direction: column; - width: ${small ? '150px' : large ? '100%' : '300px'}; + width: 100%; `}; ${isVertical && css` flex-direction: row-reverse; - height: ${small ? '50px' : large ? '100%' : '100px'}; + height: 100%; `} `, sliderState diff --git a/packages/fuselage/src/components/Slider/SliderThumb.tsx b/packages/fuselage/src/components/Slider/SliderThumb.tsx index a44cb4006c..8cc5f10a8c 100644 --- a/packages/fuselage/src/components/Slider/SliderThumb.tsx +++ b/packages/fuselage/src/components/Slider/SliderThumb.tsx @@ -7,6 +7,7 @@ import { VisuallyHidden, } from 'react-aria'; +import { Palette } from '../../Theme'; import { useStyle } from '../../hooks/useStyle'; export const SliderThumb = (props: any) => { @@ -25,13 +26,14 @@ export const SliderThumb = (props: any) => { const thumb = useStyle( css` - width: 16px; - height: 16px; + width: 12px; + height: 12px; cursor: ${state.isDisabled ? 'not-allowed' : 'pointer'}; - border: 1px solid #095ad2; border-radius: 50%; - background: ${isFocusVisible || isDragging ? '#76B7FC' : '#156ff5'}; + background: ${isFocusVisible || isDragging + ? Palette.text['font-info'] + : Palette.stroke['stroke-highlight']}; ${state.orientation === 'horizontal' ? css` top: 50%; diff --git a/packages/fuselage/src/components/Slider/SliderTrack.tsx b/packages/fuselage/src/components/Slider/SliderTrack.tsx index 211d62eb65..1ca942c88d 100644 --- a/packages/fuselage/src/components/Slider/SliderTrack.tsx +++ b/packages/fuselage/src/components/Slider/SliderTrack.tsx @@ -3,6 +3,7 @@ import type { DOMAttributes, MutableRefObject, ReactNode } from 'react'; import React, { useMemo } from 'react'; import type { SliderState } from 'react-stately'; +import { Palette } from '../../Theme'; import { useStyle } from '../../hooks/useStyle'; type SliderTrackProps = { @@ -13,6 +14,9 @@ type SliderTrackProps = { multiThumb?: boolean; }; +const highlight = Palette.stroke['stroke-highlight']; +const light = Palette.stroke['stroke-light']; + export const SliderTrack = ({ trackProps, trackRef, @@ -41,28 +45,31 @@ export const SliderTrack = ({ const getTrackGradient = () => { if (isHorizontal) { return multiThumb - ? `to right, transparent ${getThumbPosition( + ? `to right, ${light} ${getThumbPosition( state.values[0] - )}%, #156ff5 0, #156ff5 ${getThumbPosition( + )}%, ${highlight} 0, ${highlight} ${getThumbPosition( state.values[1] - )}%, transparent 0` - : `to right, #156ff5 ${getThumbPosition( + )}%, ${light} 0` + : `to right, ${highlight} ${getThumbPosition( state.values[0] - )}%, transparent 0%`; + )}%, ${light} 0%`; } if (isVertical) { return multiThumb - ? `to top, transparent ${getThumbPosition( + ? `to top, ${light} ${getThumbPosition( state.values[0] - )}%, #156ff5 0, #156ff5 ${getThumbPosition( + )}%, ${highlight} 0, ${highlight} ${getThumbPosition( state.values[1] - )}%, transparent 0` - : `to top, #156ff5 ${getThumbPosition( + )}%, ${light} 0` + : `to top, ${highlight} ${getThumbPosition( state.values[0] - )}%, transparent 0%`; + )}%, ${light} 0%`; } + + return undefined; }; + const track = useStyle( css` &::before { @@ -73,7 +80,6 @@ export const SliderTrack = ({ background: linear-gradient(${getTrackGradient()}); transform: translateX(-50%); border-radius: 1rem; - border: 1px solid #095ad2; } ${isHorizontal && css` @@ -82,7 +88,7 @@ export const SliderTrack = ({ &::before { top: 50%; width: 100%; - height: 8px; + height: 4px; transform: translateY(-50%); } `}; @@ -92,7 +98,7 @@ export const SliderTrack = ({ height: 100%; &::before { left: 50%; - width: 8px; + width: 4px; height: 100%; } `}; diff --git a/packages/fuselage/src/components/States/States.stories.tsx b/packages/fuselage/src/components/States/States.stories.tsx index 87fe9e3950..92e7c739af 100644 --- a/packages/fuselage/src/components/States/States.stories.tsx +++ b/packages/fuselage/src/components/States/States.stories.tsx @@ -11,6 +11,7 @@ import { StatesSuggestionText, StatesActions, StatesAction, + StatesLink, } from '.'; import { Box, Icon } from '..'; @@ -94,6 +95,25 @@ export const ActionButtonWithNoSuggestions = () => ( </Box> ); +export const Link = () => ( + <Box> + <States> + <StatesIcon name='magnifier' /> + <StatesTitle>No app matches</StatesTitle> + <StatesSubtitle> + No app matches for ”search term here” Try searching in the Marketplace + instead. + </StatesSubtitle> + <StatesActions> + <StatesAction>Reload</StatesAction> + </StatesActions> + <StatesLink target='_blank' href='https://go.rocket.chat'> + Link to another page + </StatesLink> + </States> + </Box> +); + export const Variations = () => ( <Box> <States> diff --git a/packages/fuselage/src/components/States/States.styles.scss b/packages/fuselage/src/components/States/States.styles.scss index 6ee0cc2b44..67b27494bd 100644 --- a/packages/fuselage/src/components/States/States.styles.scss +++ b/packages/fuselage/src/components/States/States.styles.scss @@ -28,7 +28,7 @@ $variants: ( border-radius: lengths.border-radius(full); - background-color: colors.surface(tint); + background-color: colors.surface(neutral); @each $name, $color in $variants { &--#{$name} { @@ -38,12 +38,13 @@ $variants: ( } &__title { + margin-block-start: lengths.margin(0); margin-block-end: lengths.margin(8); text-align: center; color: colors.font(default); - @include typography.use-font-scale(h2); + @include typography.use-font-scale(h3); } &__list, @@ -92,4 +93,14 @@ $variants: ( } } } + + &__link { + @include typography.use-font-scale(p2); + margin-block: lengths.margin(16); + + text-decoration: none; + + color: colors.font(info); + @extend %--with-inline-elements; + } } diff --git a/packages/fuselage/src/components/States/States.tsx b/packages/fuselage/src/components/States/States.tsx index bdc0a564bc..8cb25e3866 100644 --- a/packages/fuselage/src/components/States/States.tsx +++ b/packages/fuselage/src/components/States/States.tsx @@ -1,12 +1,14 @@ -import type { ReactNode } from 'react'; +import type { AllHTMLAttributes, ReactNode } from 'react'; import React from 'react'; type StatesProps = { children?: ReactNode; -}; +} & AllHTMLAttributes<HTMLDivElement>; -const States = ({ children }: StatesProps) => ( - <div className='rcx-states'>{children}</div> +const States = ({ children, ...props }: StatesProps) => ( + <div {...props} className='rcx-states'> + {children} + </div> ); export default States; diff --git a/packages/fuselage/src/components/States/StatesAction.tsx b/packages/fuselage/src/components/States/StatesAction.tsx index 23a103cc01..a0220bb86c 100644 --- a/packages/fuselage/src/components/States/StatesAction.tsx +++ b/packages/fuselage/src/components/States/StatesAction.tsx @@ -5,8 +5,8 @@ import { Button } from '..'; type StatesActionProps = ComponentProps<typeof Button>; -const StatesAction = ({ ...props }: StatesActionProps) => ( - <Button primary {...props} /> +const StatesAction = (props: StatesActionProps) => ( + <Button {...props} primary /> ); export default StatesAction; diff --git a/packages/fuselage/src/components/States/StatesActions.tsx b/packages/fuselage/src/components/States/StatesActions.tsx index bdb0fb9b4f..15ebdddfb2 100644 --- a/packages/fuselage/src/components/States/StatesActions.tsx +++ b/packages/fuselage/src/components/States/StatesActions.tsx @@ -6,7 +6,7 @@ import { ButtonGroup } from '../ButtonGroup'; type StatesActionsProps = ComponentProps<typeof ButtonGroup>; const StatesActions = ({ children, ...props }: StatesActionsProps) => ( - <ButtonGroup {...props}> {children} </ButtonGroup> + <ButtonGroup {...props}>{children}</ButtonGroup> ); export default StatesActions; diff --git a/packages/fuselage/src/components/States/StatesIcon.tsx b/packages/fuselage/src/components/States/StatesIcon.tsx index ece7cdd177..1d973202ce 100644 --- a/packages/fuselage/src/components/States/StatesIcon.tsx +++ b/packages/fuselage/src/components/States/StatesIcon.tsx @@ -4,15 +4,14 @@ import React from 'react'; import { Icon } from '../Icon'; type StatesIconProps = { - name: ComponentProps<typeof Icon>['name']; variation?: 'danger' | 'success' | 'warning' | 'primary'; -}; +} & ComponentProps<typeof Icon>; -const StatesIcon = ({ name, variation }: StatesIconProps) => ( +const StatesIcon = ({ variation, ...props }: StatesIconProps) => ( <Icon + {...props} rcx-states__icon className={variation && `rcx-states__icon--${variation}`} - name={name} size='x32' /> ); diff --git a/packages/fuselage/src/components/States/StatesLink.tsx b/packages/fuselage/src/components/States/StatesLink.tsx new file mode 100644 index 0000000000..fa69a7a0e1 --- /dev/null +++ b/packages/fuselage/src/components/States/StatesLink.tsx @@ -0,0 +1,13 @@ +import type { AllHTMLAttributes, ComponentProps } from 'react'; +import React from 'react'; + +import Box from '../Box/Box'; + +type StatesLinkProps = ComponentProps<typeof Box> & + AllHTMLAttributes<HTMLAnchorElement>; + +const StatesLink = (props: StatesLinkProps) => ( + <Box is='a' rcx-states__link {...props} /> +); + +export default StatesLink; diff --git a/packages/fuselage/src/components/States/StatesSubtitle.tsx b/packages/fuselage/src/components/States/StatesSubtitle.tsx index c279435609..041a0b44be 100644 --- a/packages/fuselage/src/components/States/StatesSubtitle.tsx +++ b/packages/fuselage/src/components/States/StatesSubtitle.tsx @@ -1,12 +1,14 @@ -import type { ReactNode } from 'react'; +import type { AllHTMLAttributes, ReactNode } from 'react'; import React from 'react'; type StatesSubtitleProps = { children?: ReactNode; -}; +} & AllHTMLAttributes<HTMLDivElement>; -const StatesSubtitle = ({ children }: StatesSubtitleProps) => ( - <div className='rcx-states__subtitle'>{children}</div> +const StatesSubtitle = ({ children, ...props }: StatesSubtitleProps) => ( + <div {...props} className='rcx-states__subtitle'> + {children} + </div> ); export default StatesSubtitle; diff --git a/packages/fuselage/src/components/States/StatesSuggestion.tsx b/packages/fuselage/src/components/States/StatesSuggestion.tsx index cf86b56c67..8a95787658 100644 --- a/packages/fuselage/src/components/States/StatesSuggestion.tsx +++ b/packages/fuselage/src/components/States/StatesSuggestion.tsx @@ -1,12 +1,14 @@ -import type { ReactNode } from 'react'; +import type { ReactNode, AllHTMLAttributes } from 'react'; import React from 'react'; type StatesSuggestionProps = { children?: ReactNode; -}; +} & AllHTMLAttributes<HTMLDivElement>; -const StatesSuggestion = ({ children }: StatesSuggestionProps) => ( - <div className='rcx-states__suggestion'>{children}</div> +const StatesSuggestion = ({ children, ...props }: StatesSuggestionProps) => ( + <div {...props} className='rcx-states__suggestion'> + {children} + </div> ); export default StatesSuggestion; diff --git a/packages/fuselage/src/components/States/StatesSuggestionList.tsx b/packages/fuselage/src/components/States/StatesSuggestionList.tsx index 8d60be3886..46596debfd 100644 --- a/packages/fuselage/src/components/States/StatesSuggestionList.tsx +++ b/packages/fuselage/src/components/States/StatesSuggestionList.tsx @@ -1,12 +1,17 @@ -import type { ReactNode } from 'react'; +import type { AllHTMLAttributes, ReactNode } from 'react'; import React from 'react'; type StatesSuggestionListProps = { children?: ReactNode; -}; +} & AllHTMLAttributes<HTMLUListElement>; -const StatesSuggestionList = ({ children }: StatesSuggestionListProps) => ( - <ul className='rcx-states__list'>{children}</ul> +const StatesSuggestionList = ({ + children, + ...props +}: StatesSuggestionListProps) => ( + <ul {...props} className='rcx-states__list'> + {children} + </ul> ); export default StatesSuggestionList; diff --git a/packages/fuselage/src/components/States/StatesSuggestionListItem.tsx b/packages/fuselage/src/components/States/StatesSuggestionListItem.tsx index 83562627e1..fb431ed2b8 100644 --- a/packages/fuselage/src/components/States/StatesSuggestionListItem.tsx +++ b/packages/fuselage/src/components/States/StatesSuggestionListItem.tsx @@ -1,14 +1,15 @@ -import type { ReactNode } from 'react'; +import type { AllHTMLAttributes, ReactNode } from 'react'; import React from 'react'; type StatesSuggestionListItemProps = { children?: ReactNode; -}; +} & AllHTMLAttributes<HTMLLIElement>; const StatesSuggestionListItem = ({ children, + ...props }: StatesSuggestionListItemProps) => ( - <li className='rcx-states__list-item'> + <li {...props} className='rcx-states__list-item'> <span className='rcx-states__list-item-wrapper'>{children}</span> </li> ); diff --git a/packages/fuselage/src/components/States/StatesSuggestionText.tsx b/packages/fuselage/src/components/States/StatesSuggestionText.tsx index c4c9861852..1bc00fa7f4 100644 --- a/packages/fuselage/src/components/States/StatesSuggestionText.tsx +++ b/packages/fuselage/src/components/States/StatesSuggestionText.tsx @@ -1,12 +1,17 @@ -import type { ReactNode } from 'react'; +import type { AllHTMLAttributes, ReactNode } from 'react'; import React from 'react'; type StatesSuggestionTextProps = { children?: ReactNode; -}; +} & AllHTMLAttributes<HTMLDivElement>; -const StatesSuggestionText = ({ children }: StatesSuggestionTextProps) => ( - <div className='rcx-states__suggestion-text'>{children}</div> +const StatesSuggestionText = ({ + children, + ...props +}: StatesSuggestionTextProps) => ( + <div {...props} className='rcx-states__suggestion-text'> + {children} + </div> ); export default StatesSuggestionText; diff --git a/packages/fuselage/src/components/States/StatesTitle.tsx b/packages/fuselage/src/components/States/StatesTitle.tsx index 8f7ae7d4c9..ebd4d2291e 100644 --- a/packages/fuselage/src/components/States/StatesTitle.tsx +++ b/packages/fuselage/src/components/States/StatesTitle.tsx @@ -1,12 +1,14 @@ -import type { ReactNode } from 'react'; +import type { AllHTMLAttributes, ReactNode } from 'react'; import React from 'react'; type StatesTitleProps = { children?: ReactNode; -}; +} & AllHTMLAttributes<HTMLHeadingElement>; -const StatesTitle = ({ children }: StatesTitleProps) => ( - <div className='rcx-states__title'>{children}</div> +const StatesTitle = ({ children, ...props }: StatesTitleProps) => ( + <h3 {...props} className='rcx-states__title'> + {children} + </h3> ); export default StatesTitle; diff --git a/packages/fuselage/src/components/States/index.tsx b/packages/fuselage/src/components/States/index.tsx index ca8d58eddb..47d03ec212 100644 --- a/packages/fuselage/src/components/States/index.tsx +++ b/packages/fuselage/src/components/States/index.tsx @@ -2,6 +2,7 @@ import States from './States'; import StatesAction from './StatesAction'; import StatesActions from './StatesActions'; import StatesIcon from './StatesIcon'; +import StatesLink from './StatesLink'; import StatesSubtitle from './StatesSubtitle'; import StatesSuggestion from './StatesSuggestion'; import StatesSuggestionList from './StatesSuggestionList'; @@ -14,6 +15,7 @@ export { StatesAction, StatesActions, StatesIcon, + StatesLink, StatesSubtitle, StatesSuggestion, StatesSuggestionList, diff --git a/packages/fuselage/src/components/StatusBullet/StatusBullet.styles.scss b/packages/fuselage/src/components/StatusBullet/StatusBullet.styles.scss index 1c3fb321a3..61f50ce8aa 100644 --- a/packages/fuselage/src/components/StatusBullet/StatusBullet.styles.scss +++ b/packages/fuselage/src/components/StatusBullet/StatusBullet.styles.scss @@ -4,31 +4,6 @@ @use '../../styles/functions.scss'; @use '../../styles/mixins/size.scss'; -$status-bullet-online-background-color: functions.theme( - 'status-bullet-online-background-color', - colors.success(500) -); -$status-bullet-away-background: functions.theme( - 'status-bullet-away-background', - url('./icons/away.svg') top left / contain no-repeat -); -$status-bullet-busy-background: functions.theme( - 'status-bullet-busy-background', - url('./icons/busy.svg') top left / contain no-repeat -); -$status-bullet-offline-background: functions.theme( - 'status-bullet-offline-background', - url('./icons/offline.svg') top left / contain no-repeat -); -$status-bullet-loading-background: functions.theme( - 'status-bullet-loading-background', - url('./icons/loading.svg') top left / contain no-repeat -); -$status-bullet-disabled-background: functions.theme( - 'status-bullet-disabled-background', - url('./icons/disabled.svg') top left / contain no-repeat -); - .rcx-status-bullet { display: inline-block; @@ -37,7 +12,6 @@ $status-bullet-disabled-background: functions.theme( border-radius: lengths.border-radius(full); - background: $status-bullet-loading-background; background-size: contain; @include size.square(lengths.size(12)); @@ -47,22 +21,26 @@ $status-bullet-disabled-background: functions.theme( } &--online { - background: $status-bullet-online-background-color; + fill: colors.status-bullet(online); } &--away { - background: $status-bullet-away-background; + fill: colors.status-bullet(away); } &--busy { - background: $status-bullet-busy-background; + fill: colors.status-bullet(busy); + } + + &--disabled { + fill: colors.status-bullet(disabled); } &--offline { - background: $status-bullet-offline-background; + stroke: colors.status-bullet(offline); } - &--disabled { - background: $status-bullet-disabled-background; + &--loading { + stroke: colors.status-bullet(loading); } } diff --git a/packages/fuselage/src/components/StatusBullet/StatusBullet.tsx b/packages/fuselage/src/components/StatusBullet/StatusBullet.tsx index 784677124d..4175a2c23e 100644 --- a/packages/fuselage/src/components/StatusBullet/StatusBullet.tsx +++ b/packages/fuselage/src/components/StatusBullet/StatusBullet.tsx @@ -1,30 +1,32 @@ import type { AllHTMLAttributes } from 'react'; import React from 'react'; -import { useStyleSheet } from '../../hooks/useStyleSheet'; -import styleSheet from './StatusBullet.styles.scss'; +import Away from './icons/Away'; +import Busy from './icons/Busy'; +import Disabled from './icons/Disabled'; +import Loading from './icons/Loading'; +import Offline from './icons/Offline'; +import Online from './icons/Online'; -type StatusBulletProps = { +export type StatusBulletProps = { status?: 'loading' | 'online' | 'busy' | 'away' | 'offline' | 'disabled'; size?: 'small' | 'large'; -} & Omit<AllHTMLAttributes<HTMLElement>, 'size'>; +} & Omit<AllHTMLAttributes<SVGElement>, 'size'>; -const StatusBullet = ({ - status = 'loading', - size, - className = '', - ...props -}: StatusBulletProps) => { - useStyleSheet(); - useStyleSheet(styleSheet); - - return ( - <span - className={`rcx-box rcx-box--full rcx-status-bullet rcx-status-bullet--${status} ${ - size === 'small' ? 'rcx-status-bullet--small' : '' - } ${className}`} - {...props} - /> - ); +const StatusBullet = ({ status = 'loading', ...props }: StatusBulletProps) => { + switch (status) { + case 'online': + return <Online {...props} />; + case 'away': + return <Away {...props} />; + case 'busy': + return <Busy {...props} />; + case 'disabled': + return <Disabled {...props} />; + case 'offline': + return <Offline {...props} />; + default: + return <Loading {...props} />; + } }; export { StatusBullet }; diff --git a/packages/fuselage/src/components/StatusBullet/icons/Away.tsx b/packages/fuselage/src/components/StatusBullet/icons/Away.tsx new file mode 100644 index 0000000000..154df8174b --- /dev/null +++ b/packages/fuselage/src/components/StatusBullet/icons/Away.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import type { StatusBulletProps } from '../StatusBullet'; + +const Away = ({ size, className, ...props }: StatusBulletProps) => ( + <svg + {...props} + width='10' + height='10' + viewBox='0 0 10 10' + className={`rcx-status-bullet rcx-status-bullet--away ${className} ${ + size === 'small' ? 'rcx-status-bullet--small' : '' + }`} + xmlns='http://www.w3.org/2000/svg' + > + <path + fillRule='evenodd' + clipRule='evenodd' + d='M5.13337 9.93325C7.78434 9.93325 9.93338 7.78422 9.93338 5.13325C9.93338 2.48229 7.78434 0.333252 5.13337 0.333252C2.48241 0.333252 0.333374 2.48229 0.333374 5.13325C0.333374 7.78422 2.48241 9.93325 5.13337 9.93325ZM5.80004 2.33325C5.80004 1.96506 5.50156 1.66659 5.13337 1.66659C4.76518 1.66659 4.46671 1.96506 4.46671 2.33325V5.13325V5.45367L4.71691 5.65383L6.71691 7.25383C7.00442 7.48384 7.42395 7.43722 7.65395 7.14972C7.88396 6.86221 7.83735 6.44268 7.54984 6.21267L5.80004 4.81284V2.33325Z' + /> + </svg> +); + +export default Away; diff --git a/packages/fuselage/src/components/StatusBullet/icons/Busy.tsx b/packages/fuselage/src/components/StatusBullet/icons/Busy.tsx new file mode 100644 index 0000000000..fcb9dc6977 --- /dev/null +++ b/packages/fuselage/src/components/StatusBullet/icons/Busy.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import type { StatusBulletProps } from '../StatusBullet'; + +const Busy = ({ size, className, ...props }: StatusBulletProps) => ( + <svg + {...props} + width='10' + height='10' + viewBox='0 0 10 10' + className={`rcx-status-bullet rcx-status-bullet--busy ${className} ${ + size === 'small' ? 'rcx-status-bullet--small' : '' + }`} + xmlns='http://www.w3.org/2000/svg' + > + <path + fillRule='evenodd' + clipRule='evenodd' + d='M5.13337 9.93325C7.78434 9.93325 9.93338 7.78422 9.93338 5.13325C9.93338 2.48229 7.78434 0.333252 5.13337 0.333252C2.48241 0.333252 0.333374 2.48229 0.333374 5.13325C0.333374 7.78422 2.48241 9.93325 5.13337 9.93325ZM3.53338 4.46655C3.16519 4.46655 2.86671 4.76503 2.86671 5.13322C2.86671 5.50141 3.16519 5.79989 3.53338 5.79989H6.73338C7.10157 5.79989 7.40004 5.50141 7.40004 5.13322C7.40004 4.76503 7.10157 4.46655 6.73338 4.46655H3.53338Z' + /> + </svg> +); + +export default Busy; diff --git a/packages/fuselage/src/components/StatusBullet/icons/Disabled.tsx b/packages/fuselage/src/components/StatusBullet/icons/Disabled.tsx new file mode 100644 index 0000000000..61bc8565f4 --- /dev/null +++ b/packages/fuselage/src/components/StatusBullet/icons/Disabled.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import type { StatusBulletProps } from '../StatusBullet'; + +const Disabled = ({ size, className, ...props }: StatusBulletProps) => ( + <svg + {...props} + width='24' + height='24' + viewBox='0 0 24 24' + className={`rcx-status-bullet rcx-status-bullet--disabled ${className} ${ + size === 'small' ? 'rcx-status-bullet--small' : '' + }`} + xmlns='http://www.w3.org/2000/svg' + > + <path + fillRule='evenodd' + clipRule='evenodd' + d='M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24ZM13.3367 5.33333C13.3367 4.59695 12.7398 4 12.0034 4C11.267 4 10.67 4.59695 10.67 5.33333V14.6667C10.67 15.403 11.267 16 12.0034 16C12.7398 16 13.3367 15.403 13.3367 14.6667V5.33333ZM13.3367 18.6667C13.3367 17.9303 12.7398 17.3333 12.0034 17.3333C11.267 17.3333 10.67 17.9303 10.67 18.6667C10.67 19.403 11.267 20 12.0034 20C12.7398 20 13.3367 19.403 13.3367 18.6667Z' + /> + </svg> +); + +export default Disabled; diff --git a/packages/fuselage/src/components/StatusBullet/icons/Loading.tsx b/packages/fuselage/src/components/StatusBullet/icons/Loading.tsx new file mode 100644 index 0000000000..83d961fc0f --- /dev/null +++ b/packages/fuselage/src/components/StatusBullet/icons/Loading.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import type { StatusBulletProps } from '../StatusBullet'; + +const Loading = ({ size, className, ...props }: StatusBulletProps) => ( + <svg + {...props} + width='12' + height='12' + viewBox='0 0 12 12' + fill='none' + xmlns='http://www.w3.org/2000/svg' + className={`rcx-status-bullet rcx-status-bullet--loading ${className} ${ + size === 'small' ? 'rcx-status-bullet--small' : '' + }`} + > + <circle + cx='6' + cy='6' + r='5' + className='rcx-status-bullet rcx-status-bullet--loading' + strokeWidth='2' + strokeDasharray='2 2' + /> + </svg> +); + +export default Loading; diff --git a/packages/fuselage/src/components/StatusBullet/icons/Offline.tsx b/packages/fuselage/src/components/StatusBullet/icons/Offline.tsx new file mode 100644 index 0000000000..e0eff430ad --- /dev/null +++ b/packages/fuselage/src/components/StatusBullet/icons/Offline.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import type { StatusBulletProps } from '../StatusBullet'; + +const Offline = ({ size, className, ...props }: StatusBulletProps) => ( + <svg + {...props} + width='12' + height='12' + viewBox='0 0 12 12' + fill='none' + xmlns='http://www.w3.org/2000/svg' + className={`rcx-status-bullet rcx-status-bullet--offline ${className} ${ + size === 'small' ? 'rcx-status-bullet--small' : '' + }`} + > + <circle + cx='6' + cy='6' + r='5' + className={`rcx-status-bullet rcx-status-bullet--offline`} + strokeWidth='2' + /> + </svg> +); + +export default Offline; diff --git a/packages/fuselage/src/components/StatusBullet/icons/Online.tsx b/packages/fuselage/src/components/StatusBullet/icons/Online.tsx new file mode 100644 index 0000000000..45121a0066 --- /dev/null +++ b/packages/fuselage/src/components/StatusBullet/icons/Online.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import type { StatusBulletProps } from '../StatusBullet'; + +const Online = ({ size, className, ...props }: StatusBulletProps) => ( + <svg + {...props} + width='24' + height='24' + viewBox='0 0 24 24' + className={`rcx-status-bullet rcx-status-bullet--online ${className} ${ + size === 'small' ? 'rcx-status-bullet--small' : '' + }`} + xmlns='http://www.w3.org/2000/svg' + > + <path d='M24 12.0001C24 18.6275 18.6274 24.0001 12 24.0001C5.37255 24.0001 -3.05176e-05 18.6275 -3.05176e-05 12.0001C-3.05176e-05 5.37271 5.37255 0.00012207 12 0.00012207C18.6274 0.00012207 24 5.37271 24 12.0001Z' /> + </svg> +); + +export default Online; diff --git a/packages/fuselage/src/components/StatusBullet/icons/away.svg b/packages/fuselage/src/components/StatusBullet/icons/away.svg deleted file mode 100644 index 2cc8dceefb..0000000000 --- a/packages/fuselage/src/components/StatusBullet/icons/away.svg +++ /dev/null @@ -1,14 +0,0 @@ -<svg - width="10" - height="10" - viewBox="0 0 10 10" - fill="none" - xmlns="http://www.w3.org/2000/svg" -> - <path - fill-rule="evenodd" - clip-rule="evenodd" - d="M5.13337 9.93325C7.78434 9.93325 9.93338 7.78422 9.93338 5.13325C9.93338 2.48229 7.78434 0.333252 5.13337 0.333252C2.48241 0.333252 0.333374 2.48229 0.333374 5.13325C0.333374 7.78422 2.48241 9.93325 5.13337 9.93325ZM5.80004 2.33325C5.80004 1.96506 5.50156 1.66659 5.13337 1.66659C4.76518 1.66659 4.46671 1.96506 4.46671 2.33325V5.13325V5.45367L4.71691 5.65383L6.71691 7.25383C7.00442 7.48384 7.42395 7.43722 7.65395 7.14972C7.88396 6.86221 7.83735 6.44268 7.54984 6.21267L5.80004 4.81284V2.33325Z" - fill="#F3BE08" - /> -</svg> diff --git a/packages/fuselage/src/components/StatusBullet/icons/busy.svg b/packages/fuselage/src/components/StatusBullet/icons/busy.svg deleted file mode 100644 index ae86c825f0..0000000000 --- a/packages/fuselage/src/components/StatusBullet/icons/busy.svg +++ /dev/null @@ -1,14 +0,0 @@ -<svg - width="10" - height="10" - viewBox="0 0 10 10" - fill="none" - xmlns="http://www.w3.org/2000/svg" -> - <path - fill-rule="evenodd" - clip-rule="evenodd" - d="M5.13337 9.93325C7.78434 9.93325 9.93338 7.78422 9.93338 5.13325C9.93338 2.48229 7.78434 0.333252 5.13337 0.333252C2.48241 0.333252 0.333374 2.48229 0.333374 5.13325C0.333374 7.78422 2.48241 9.93325 5.13337 9.93325ZM3.53338 4.46655C3.16519 4.46655 2.86671 4.76503 2.86671 5.13322C2.86671 5.50141 3.16519 5.79989 3.53338 5.79989H6.73338C7.10157 5.79989 7.40004 5.50141 7.40004 5.13322C7.40004 4.76503 7.10157 4.46655 6.73338 4.46655H3.53338Z" - fill="#F5455C" - /> -</svg> diff --git a/packages/fuselage/src/components/StatusBullet/icons/disabled.svg b/packages/fuselage/src/components/StatusBullet/icons/disabled.svg deleted file mode 100644 index e6be47f227..0000000000 --- a/packages/fuselage/src/components/StatusBullet/icons/disabled.svg +++ /dev/null @@ -1,14 +0,0 @@ -<svg - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - xmlns="http://www.w3.org/2000/svg" -> -<path - fill-rule="evenodd" - clip-rule="evenodd" - d="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24ZM13.3367 5.33333C13.3367 4.59695 12.7398 4 12.0034 4C11.267 4 10.67 4.59695 10.67 5.33333V14.6667C10.67 15.403 11.267 16 12.0034 16C12.7398 16 13.3367 15.403 13.3367 14.6667V5.33333ZM13.3367 18.6667C13.3367 17.9303 12.7398 17.3333 12.0034 17.3333C11.267 17.3333 10.67 17.9303 10.67 18.6667C10.67 19.403 11.267 20 12.0034 20C12.7398 20 13.3367 19.403 13.3367 18.6667Z" - fill="#F38C39" - /> -</svg> diff --git a/packages/fuselage/src/components/StatusBullet/icons/loading.svg b/packages/fuselage/src/components/StatusBullet/icons/loading.svg deleted file mode 100644 index 23b08de3fd..0000000000 --- a/packages/fuselage/src/components/StatusBullet/icons/loading.svg +++ /dev/null @@ -1,16 +0,0 @@ -<svg - width="12" - height="12" - viewBox="0 0 12 12" - fill="none" - xmlns="http://www.w3.org/2000/svg" -> - <circle - cx="6" - cy="6" - r="5" - stroke="#9EA2A8" - stroke-width="2" - stroke-dasharray="2 2" - /> -</svg> diff --git a/packages/fuselage/src/components/StatusBullet/icons/offline.svg b/packages/fuselage/src/components/StatusBullet/icons/offline.svg deleted file mode 100644 index fb7c66084a..0000000000 --- a/packages/fuselage/src/components/StatusBullet/icons/offline.svg +++ /dev/null @@ -1,9 +0,0 @@ -<svg - width="12" - height="12" - viewBox="0 0 12 12" - fill="none" - xmlns="http://www.w3.org/2000/svg" -> - <circle cx="6" cy="6" r="5" stroke="#9EA2A8" stroke-width="2" /> -</svg> diff --git a/packages/fuselage/src/components/Table/Table.tsx b/packages/fuselage/src/components/Table/Table.tsx index 39a9d99c1b..11890d28f5 100644 --- a/packages/fuselage/src/components/Table/Table.tsx +++ b/packages/fuselage/src/components/Table/Table.tsx @@ -2,8 +2,6 @@ import type { ComponentProps, CSSProperties } from 'react'; import React from 'react'; import Box from '../Box'; -import { TableSelection } from './TableSelection'; -import { TableSelectionButton } from './TableSelectionButton'; export const style: CSSProperties = { overflow: 'hidden', @@ -34,6 +32,3 @@ export const Table = ({ /> </Box> ); - -Table.Selection = TableSelection; -Table.Button = TableSelectionButton; diff --git a/packages/fuselage/src/components/Table/TableCell.tsx b/packages/fuselage/src/components/Table/TableCell.tsx index 4be5296372..7e4f5be384 100644 --- a/packages/fuselage/src/components/Table/TableCell.tsx +++ b/packages/fuselage/src/components/Table/TableCell.tsx @@ -9,8 +9,20 @@ type TableCellProps = TableProps & { clickable?: boolean; }; -export const TableCell = ({ align, clickable, ...props }: TableCellProps) => { +export const TableCell = ({ + align, + clickable, + children, + ...props +}: TableCellProps) => { const isInsideHead = useContext(TableHeadContext); + + const innerElement = + children ?? + (!isInsideHead ? ( + <Box display='inline-block' is='hr' width='x14' borderWidth={1} /> + ) : undefined); + return ( <Box is={isInsideHead ? 'th' : 'td'} @@ -18,6 +30,7 @@ export const TableCell = ({ align, clickable, ...props }: TableCellProps) => { rcx-table__cell--align={align} rcx-table__cell--header={isInsideHead} rcx-table__cell--clickable={clickable} + children={innerElement} {...props} /> ); diff --git a/packages/fuselage/src/components/Table/TableSelection.tsx b/packages/fuselage/src/components/Table/TableSelection.tsx index 02bcb69330..365008bd4b 100644 --- a/packages/fuselage/src/components/Table/TableSelection.tsx +++ b/packages/fuselage/src/components/Table/TableSelection.tsx @@ -20,9 +20,9 @@ export const TableSelection = ({ alignItems='center' justifyContent='space-between' {...props} - pi='x24' + pi={24} > - <Box fontScale='p2m' mb='x16' flexShrink={1} style={style}> + <Box fontScale='p2m' mb={16} flexShrink={1} style={style}> {text} </Box> {children && ( diff --git a/packages/fuselage/src/components/Table/index.ts b/packages/fuselage/src/components/Table/index.ts index 3a1f327ff3..04b0f60f8f 100644 --- a/packages/fuselage/src/components/Table/index.ts +++ b/packages/fuselage/src/components/Table/index.ts @@ -1,29 +1,8 @@ -import { Table } from './Table'; -import { TableBody } from './TableBody'; -import { TableCell } from './TableCell'; -import { TableFoot } from './TableFoot'; -import { TableHead } from './TableHead'; -import { TableRow } from './TableRow'; - export * from './Table'; export * from './TableBody'; export * from './TableCell'; export * from './TableFoot'; export * from './TableHead'; export * from './TableRow'; -export * from './TableRow'; export * from './TableSelection'; export * from './TableSelectionButton'; - -export default Object.assign(Table, { - /** @deprecated */ - Head: TableHead, - /** @deprecated */ - Body: TableBody, - /** @deprecated */ - Foot: TableFoot, - /** @deprecated */ - Row: TableRow, - /** @deprecated */ - Cell: TableCell, -}); diff --git a/packages/fuselage/src/components/Tabs/Tabs.stories.tsx b/packages/fuselage/src/components/Tabs/Tabs.stories.tsx index 6f5345e850..0fc9e98e69 100644 --- a/packages/fuselage/src/components/Tabs/Tabs.stories.tsx +++ b/packages/fuselage/src/components/Tabs/Tabs.stories.tsx @@ -2,7 +2,7 @@ import { Title, Description, Primary, Stories } from '@storybook/addon-docs'; import type { ComponentStory, ComponentMeta } from '@storybook/react'; import React from 'react'; -import { Tabs } from '../..'; +import { Tabs, TabsItem } from '../..'; export default { title: 'Navigation/Tabs', @@ -27,11 +27,11 @@ export default { const Template: ComponentStory<typeof Tabs> = (args) => ( <Tabs {...args}> - <Tabs.Item {...args}>Tab text 1</Tabs.Item> - <Tabs.Item>Tab text 2</Tabs.Item> - <Tabs.Item>Tab text 3</Tabs.Item> - <Tabs.Item>Tab text 4</Tabs.Item> - <Tabs.Item>Tab text 5</Tabs.Item> + <TabsItem {...args}>Tab text 1</TabsItem> + <TabsItem>Tab text 2</TabsItem> + <TabsItem>Tab text 3</TabsItem> + <TabsItem>Tab text 4</TabsItem> + <TabsItem>Tab text 5</TabsItem> </Tabs> ); diff --git a/packages/fuselage/src/components/Tabs/Tabs.tsx b/packages/fuselage/src/components/Tabs/Tabs.tsx index 3836a6cd05..10bf7060aa 100644 --- a/packages/fuselage/src/components/Tabs/Tabs.tsx +++ b/packages/fuselage/src/components/Tabs/Tabs.tsx @@ -16,4 +16,5 @@ export function Tabs({ children, divider = true, ...props }: TabsProps) { ); } +/** @deprecated use named export TabsItem instead */ Tabs.Item = TabsItem; diff --git a/packages/fuselage/src/components/Tabs/index.tsx b/packages/fuselage/src/components/Tabs/index.tsx index 856dbbb347..c0047c9cd9 100644 --- a/packages/fuselage/src/components/Tabs/index.tsx +++ b/packages/fuselage/src/components/Tabs/index.tsx @@ -1 +1,2 @@ export * from './Tabs'; +export * from './TabsItem'; diff --git a/packages/fuselage/src/components/Tag/Tag.stories.tsx b/packages/fuselage/src/components/Tag/Tag.stories.tsx index d6851d724f..737aa83ab2 100644 --- a/packages/fuselage/src/components/Tag/Tag.stories.tsx +++ b/packages/fuselage/src/components/Tag/Tag.stories.tsx @@ -71,7 +71,7 @@ Featured.args = { export const WithIcon = Template.bind({}); WithIcon.args = { - icon: <Icon size='x12' mie='x4' name='team-lock' />, + icon: <Icon size='x12' mie={4} name='team-lock' />, children: 'Team', }; diff --git a/packages/fuselage/src/components/Tag/Tag.styles.scss b/packages/fuselage/src/components/Tag/Tag.styles.scss index 3134f035e7..8f46aaf7d7 100644 --- a/packages/fuselage/src/components/Tag/Tag.styles.scss +++ b/packages/fuselage/src/components/Tag/Tag.styles.scss @@ -112,8 +112,6 @@ $tag-colors-disabled-background-color: theme( justify-content: center; align-items: center; - width: fit-content; - padding: lengths.padding(2) lengths.padding(4); white-space: nowrap; diff --git a/packages/fuselage/src/components/Tile/Tile.tsx b/packages/fuselage/src/components/Tile/Tile.tsx index 4ed51bded3..457d4a51a1 100644 --- a/packages/fuselage/src/components/Tile/Tile.tsx +++ b/packages/fuselage/src/components/Tile/Tile.tsx @@ -1,21 +1,14 @@ import type { ComponentProps, Ref } from 'react'; import React, { forwardRef } from 'react'; -import { useStyleSheet } from '../../hooks/useStyleSheet'; import Box from '../Box'; -import tileStyleSheet from './Tile.styles.scss'; -type TileProps = ComponentProps<typeof Box> & { - elevation?: '0' | '1' | '2'; -}; +type TileProps = ComponentProps<typeof Box>; const Tile = forwardRef(function Tile( - { elevation = '1', padding = 'x16', ...props }: TileProps, + { elevation = '1', padding = 16, ...props }: TileProps, ref: Ref<HTMLElement> ) { - useStyleSheet(); - useStyleSheet(tileStyleSheet); - return ( <Box ref={ref} diff --git a/packages/fuselage/src/components/ToastBar/ToastBar.spec.tsx b/packages/fuselage/src/components/ToastBar/ToastBar.spec.tsx index 9fd0b8c564..acdbd086a9 100644 --- a/packages/fuselage/src/components/ToastBar/ToastBar.spec.tsx +++ b/packages/fuselage/src/components/ToastBar/ToastBar.spec.tsx @@ -1,10 +1,20 @@ import { render } from '@testing-library/react'; +import { axe, toHaveNoViolations } from 'jest-axe'; import React from 'react'; import { ToastBar } from '.'; +expect.extend(toHaveNoViolations); + describe('[ToastBar Component]', () => { it('renders without crashing', () => { render(<ToastBar />); }); + + it('should have no a11y violations', async () => { + const { container } = render(<ToastBar />); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); }); diff --git a/packages/fuselage/src/components/ToastBar/ToastBar.stories.tsx b/packages/fuselage/src/components/ToastBar/ToastBar.stories.tsx index 38e1d21620..5de35b8734 100644 --- a/packages/fuselage/src/components/ToastBar/ToastBar.stories.tsx +++ b/packages/fuselage/src/components/ToastBar/ToastBar.stories.tsx @@ -56,27 +56,12 @@ Error.args = { variant: 'error', }; -export const Small = Template.bind({}); -Small.args = { - children: 'Lorem ipsum dolor sit amet', -}; - -export const SuccessWithCloseButton = Template.bind({}); -SuccessWithCloseButton.args = { - children: 'Lorem ipsum dolor sit amet', - variant: 'success', +export const WithCloseButton = Template.bind({}); +WithCloseButton.args = { onClose: action('clicked'), }; -export const ErrorWithCloseButton = Template.bind({}); -ErrorWithCloseButton.args = { +export const TinyText = Template.bind({}); +TinyText.args = { children: 'Lorem ipsum dolor sit amet', - variant: 'error', - onClose: action('clicked'), -}; - -export const DefaultWithCloseButton = Template.bind({}); -DefaultWithCloseButton.args = { - children: 'Lorem ipsum dolor sit amet', - onClose: action('clicked'), }; diff --git a/packages/fuselage/src/components/ToastBar/ToastBar.styles.scss b/packages/fuselage/src/components/ToastBar/ToastBar.styles.scss index c2fc8a8327..b4c00461d4 100644 --- a/packages/fuselage/src/components/ToastBar/ToastBar.styles.scss +++ b/packages/fuselage/src/components/ToastBar/ToastBar.styles.scss @@ -2,74 +2,114 @@ @use '../../styles/lengths.scss'; @use '../../styles/typography.scss'; +$toastbar-color: theme('toastbar-color', colors.font(default)); + +$toastbar-border-radius: theme( + 'toastbar-border-radius', + lengths.border-radius(medium) +); + +$toastbar-success-color: theme( + 'toastbar-success-color', + colors.status-font(on-success) +); + +$toastbar-error-color: theme( + 'toastbar-error-color', + colors.status-font(on-danger) +); + +$toastbar-background-color: theme( + 'toastbar-background-color', + colors.surface(tint) +); + +$toastbar-progressbar-background-color: theme( + 'toastbar-progressbar-background-color', + colors.surface(neutral) +); + .rcx-toastbar { position: relative; min-width: lengths.size(232); max-width: lengths.size(416); - border-radius: theme('toastbar-border-radius', lengths.border-radius(medium)); + color: $toastbar-color; + + border-radius: $toastbar-border-radius; + + background-color: $toastbar-background-color; @include typography.use-font-scale(p2); - &--info { - background-color: theme( - 'toastbar-info-background-color', - colors.surface(neutral) - ); + &::before { + position: absolute; + top: 0; + + display: block; + + width: 100%; + height: lengths.size(4); + + content: ''; + + border-radius: $toastbar-border-radius $toastbar-border-radius 0 0; + background-color: transparent; } &--success { - color: theme('toastbar-success-color', colors.status-font(on-success)); - background-color: theme( - 'toastbar-success-background-color', - colors.status-background(success) - ); + &::before { + background-color: $toastbar-success-color; + } } &--error { - color: theme('toastbar-error-color', colors.font(danger)); - background-color: theme( - 'toastbar-error-background-color', - colors.status-background(danger) - ); + &::before { + background-color: $toastbar-error-color; + } } -} -.rcx-toastbar-inner { - display: flex; + &_inner { + display: flex; - padding: lengths.padding(16); -} + padding: lengths.padding(16); + } -.rcx-toastbar-content { - width: lengths.size(full); - margin: lengths.margin(0) lengths.margin(16); -} + &_content { + width: lengths.size(full); + margin: lengths.margin(0) lengths.margin(16); + } -$toastbar-border-radius: theme( - 'toastbar-progressbar-border-radius', - lengths.border-radius(medium) -); + &_icon { + &--success { + color: $toastbar-success-color; + } -.rcx-toastbar-progressbar { - position: absolute; - bottom: 0; + &--error { + color: $toastbar-error-color; + } + } - overflow: hidden; + &_progressbar { + position: absolute; + bottom: 0; - width: 100%; - height: lengths.size(4); + overflow: hidden; - border-radius: 0 0 $toastbar-border-radius $toastbar-border-radius; + width: 100%; + height: lengths.size(4); - &::after { - display: block; + border-radius: 0 0 $toastbar-border-radius $toastbar-border-radius; - height: 100%; + &::after { + display: block; - content: ''; + height: 100%; + + content: ''; - background-color: colors.surface-neutral-alpha(10); + background-color: $toastbar-progressbar-background-color; + } } } diff --git a/packages/fuselage/src/components/ToastBar/ToastBar.tsx b/packages/fuselage/src/components/ToastBar/ToastBar.tsx index 44670ff18c..5753eb12ff 100644 --- a/packages/fuselage/src/components/ToastBar/ToastBar.tsx +++ b/packages/fuselage/src/components/ToastBar/ToastBar.tsx @@ -1,6 +1,6 @@ import { css, keyframes } from '@rocket.chat/css-in-js'; import { useUniqueId } from '@rocket.chat/fuselage-hooks'; -import type { ReactNode } from 'react'; +import type { ReactNode, AllHTMLAttributes } from 'react'; import React from 'react'; import Box from '../Box'; @@ -14,7 +14,8 @@ export type ToastBarProps = { time?: number; id?: string; onClose?: (id: string) => void; -}; + buttonLabel?: string; +} & Omit<AllHTMLAttributes<HTMLElement>, 'is'>; export function ToastBar({ children, @@ -23,10 +24,12 @@ export function ToastBar({ time = 5, id, onClose, + buttonLabel = 'Close', + ...props }: ToastBarProps) { const iconName = - (variant === 'success' && 'check') || - (variant === 'error' && 'warning') || + (variant === 'success' && 'circle-check') || + (variant === 'error' && 'ban') || 'info'; const sideOpen = keyframes` @@ -64,36 +67,36 @@ export function ToastBar({ return ( <Box - className={['rcx-toastbar-wrapper', toastBarAnimation]} + className={[ + `rcx-toastbar rcx-toastbar--${variant} ${className}`, + toastBarAnimation, + ]} + elevation='2nb' + borderRadius='x4' role='alert' - aria-labelledby={toastId} + {...props} > - <Box - className={`rcx-toastbar rcx-toastbar--${variant} ${className}`} - elevation='2nb' - borderRadius='x4' - > - <div className='rcx-toastbar-inner'> - <Icon size='x20' name={iconName} /> - <div className='rcx-toastbar-content' id={toastId}> - {children} - </div> - {onClose && ( - <div className='rcx-toastbar-close'> - <IconButton - tiny - {...{ - success: variant === 'success', - danger: variant === 'error', - }} - onClick={() => onClose(toastId)} - icon='cross' - /> - </div> - )} + <div className='rcx-toastbar_inner'> + <Icon + className={`rcx-toastbar_icon--${variant}`} + size='x20' + name={iconName} + /> + <div className='rcx-toastbar_content' id={toastId}> + {children} </div> - <Box className={[progressBarAnimation, 'rcx-toastbar-progressbar']} /> - </Box> + {onClose && ( + <div className='rcx-toastbar-close'> + <IconButton + tiny + onClick={() => onClose(toastId)} + icon='cross' + aria-label={buttonLabel} + /> + </div> + )} + </div> + <Box className={[progressBarAnimation, 'rcx-toastbar_progressbar']} /> </Box> ); } diff --git a/packages/fuselage/src/components/Tooltip/Tooltip.tsx b/packages/fuselage/src/components/Tooltip/Tooltip.tsx index 9f5d37ad8e..512d66d7e4 100644 --- a/packages/fuselage/src/components/Tooltip/Tooltip.tsx +++ b/packages/fuselage/src/components/Tooltip/Tooltip.tsx @@ -1,9 +1,7 @@ import type { ComponentProps, Ref } from 'react'; import React, { forwardRef } from 'react'; -import { useStyleSheet } from '../../hooks/useStyleSheet'; import Box from '../Box'; -import tooltipStyleSheet from './Tooltip.styles.scss'; const parsePlacement = (placement: string | null | undefined) => { const [direction, position] = placement @@ -37,9 +35,6 @@ const Tooltip = forwardRef(function Tooltip( { variation = 'dark', placement, ...props }: TooltipProps, ref: Ref<HTMLElement> ) { - useStyleSheet(); - useStyleSheet(tooltipStyleSheet); - const [direction, position] = parsePlacement(placement); return ( diff --git a/packages/fuselage/src/components/index.scss b/packages/fuselage/src/components/index.scss index 7fd1a4c7db..6be4cc4a65 100644 --- a/packages/fuselage/src/components/index.scss +++ b/packages/fuselage/src/components/index.scss @@ -1,4 +1,5 @@ @import './Accordion/Accordion.styles.scss'; +@import './Banner/Banner.styles.scss'; @import './AutoComplete/AutoComplete.styles.scss'; @import './Avatar/Avatar.styles.scss'; @import './Badge/Badge.styles.scss'; @@ -33,5 +34,8 @@ @import './Tabs/Tabs.styles.scss'; @import './Tag/Tag.styles.scss'; @import './Throbber/Throbber.styles.scss'; +@import './Tile/Tile.styles.scss'; @import './ToastBar/ToastBar.styles.scss'; @import './ToggleSwitch/ToggleSwitch.styles.scss'; +@import './Tooltip/Tooltip.styles.scss'; +@import './StatusBullet/StatusBullet.styles.scss'; diff --git a/packages/fuselage/src/components/index.ts b/packages/fuselage/src/components/index.ts index d8cc1f6533..c62997a497 100644 --- a/packages/fuselage/src/components/index.ts +++ b/packages/fuselage/src/components/index.ts @@ -1,5 +1,6 @@ export * from './Accordion'; export { default as AnimatedVisibility } from './AnimatedVisibility'; +export * from './AudioPlayer'; export * from './AutoComplete'; export * from './Avatar'; export * from './Badge'; @@ -12,6 +13,7 @@ export * from './Callout'; export * from './CheckBox'; export * from './Chevron'; export { default as CodeSnippet } from './CodeSnippet'; +export * from './Contextualbar'; export { default as Chip } from './Chip'; export * from './Divider'; export * from './Dropdown'; @@ -45,22 +47,14 @@ export * from './RadioButton'; export { default as Scrollable } from './Scrollable'; export * from './SearchInput'; export * from './Select'; +export * from './Slider'; export * from './PaginatedSelect'; export * from './SelectInput'; export { default as Sidebar } from './Sidebar'; export * from './Sidebar'; export * from './Skeleton'; export * from './States'; -export { - default as Table, - TableBody, - TableCell, - TableFoot, - TableHead, - TableRow, - TableSelection, - TableSelectionButton, -} from './Table'; +export * from './Table'; export * from './Tabs'; export * from './Tag'; export * from './TelephoneInput'; diff --git a/packages/fuselage/src/hooks/useStyleSheet.ts b/packages/fuselage/src/hooks/useStyleSheet.ts deleted file mode 100644 index e31919cec2..0000000000 --- a/packages/fuselage/src/hooks/useStyleSheet.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useLayoutEffect } from 'react'; - -import defaultStyleSheet from '../index.scss'; - -export const useStyleSheet = (styleSheet = defaultStyleSheet) => { - useLayoutEffect(() => { - styleSheet.use(); - - return () => { - styleSheet.unuse(); - }; - }, [styleSheet]); -}; diff --git a/packages/fuselage/src/index.ts b/packages/fuselage/src/index.ts index 6f761da4e1..399a10e92b 100644 --- a/packages/fuselage/src/index.ts +++ b/packages/fuselage/src/index.ts @@ -1,7 +1,6 @@ +import './index.scss'; + export * from './components'; export * from './styleTokens'; -export { Palette, __setThrowErrorOnInvalidToken__ } from './Theme'; -if (process.env.VERSION) { - console.log(`fuselage: ${process.env.VERSION}`); -} +export { Palette, __setThrowErrorOnInvalidToken__ } from './Theme'; diff --git a/packages/fuselage/src/styleTokens.ts b/packages/fuselage/src/styleTokens.ts index 764ba56d18..8f218207e1 100644 --- a/packages/fuselage/src/styleTokens.ts +++ b/packages/fuselage/src/styleTokens.ts @@ -1,5 +1,5 @@ import tokenColors from '@rocket.chat/fuselage-tokens/colors.json'; -import tokenTypography from '@rocket.chat/fuselage-tokens/dist/typography.json'; +import tokenTypography from '@rocket.chat/fuselage-tokens/typography.json'; import { memoize } from '@rocket.chat/memo'; import invariant from 'invariant'; @@ -61,6 +61,8 @@ export const borderWidth = measure((value: unknown) => { if (value === 'default') { return borderWidth('x1'); } + + return undefined; }); export const borderRadius = measure((value: unknown) => { @@ -71,6 +73,8 @@ export const borderRadius = measure((value: unknown) => { if (value === 'full') { return '9999px'; } + + return undefined; }); const mapTypeToPrefix = { @@ -281,24 +285,32 @@ export const size = measure((value: unknown) => { if (value === 'sh') { return '100vh'; } + + return undefined; }); export const inset = measure((value: unknown) => { if (value === 'none') { return '0px'; } + + return undefined; }); export const margin = measure((value: unknown) => { if (value === 'none') { return '0px'; } + + return undefined; }); export const padding = measure((value: unknown) => { if (value === 'none') { return '0px'; } + + return undefined; }); type FontFamily = keyof typeof tokenTypography.fontFamilies; diff --git a/packages/fuselage/src/styles/colors.scss b/packages/fuselage/src/styles/colors.scss index bc8375725b..725993f9c5 100644 --- a/packages/fuselage/src/styles/colors.scss +++ b/packages/fuselage/src/styles/colors.scss @@ -33,7 +33,7 @@ $-map-type-to-prefix: ( $base-color: map.get(token-colors.$colors, #{$prefix}#{$grade}); @if not $base-color { - @error 'invalid color reference'; + @error 'invalid color reference: #{$prefix}#{$grade}'; } @if ($alpha != null) { @@ -134,10 +134,24 @@ $-font: ( @return var(--rcx-color-font-#{$type}, $color); } +$-status-bullet: ( + online: success(650), + away: warning(650), + busy: danger(650), + disabled: service-1(500), + offline: neutral(700), + loading: neutral(600), +); + +@function status-bullet($type) { + $color: map.get($-status-bullet, $type); + @return var(--rcx-color-status-bullet-#{$type}, $color); +} + $-status-backgrounds: ( success: success(200), warning: warning(200), - warning-2: warning(100), + warning-2: warning(150), danger: danger(200), service-1: service-1(200), service-2: service-2(200), @@ -182,6 +196,7 @@ $-strokes: ( } $-badge-backgrounds: ( + level-0: neutral(400), level-1: neutral(600), level-2: primary(550), level-3: service-1(500), diff --git a/packages/fuselage/src/styles/lengths.scss b/packages/fuselage/src/styles/lengths.scss index d3e14388e4..992168cfd3 100644 --- a/packages/fuselage/src/styles/lengths.scss +++ b/packages/fuselage/src/styles/lengths.scss @@ -36,7 +36,7 @@ { @return functions.to-rem($value); } @else { - @error 'value must be none, or a multiple of 4'; + @error 'value must be none, or a multiple of 4. You passed `#{$value}`'; } } @@ -58,13 +58,14 @@ { @return functions.to-rem($value); } @else { - @error 'value must be none, full, sw, sh, a percentage, or a multiple of 4'; + @error 'value must be none, full, sw, sh, a percentage, or a multiple of 4. You passed `#{$value}`'; } } $border-width-sizes: ( 'default': 1, 'medium': 2, + 'large': 4, ); @function border-width($value, $scape: px) { @@ -78,7 +79,7 @@ $border-width-sizes: ( } @return functions.to-rem(map.get($border-width-sizes, $value)); } @else { - @error 'value must be none, default, medium, 1, 2, or 4'; + @error 'value must be none, default, medium, large, 1, 2, or 4'; } } diff --git a/packages/fuselage/src/styles/mixins/size.scss b/packages/fuselage/src/styles/mixins/size.scss index 3351c59aeb..078683c980 100644 --- a/packages/fuselage/src/styles/mixins/size.scss +++ b/packages/fuselage/src/styles/mixins/size.scss @@ -2,3 +2,7 @@ width: $width; height: $height; } + +@mixin resize($scale) { + transform: scale($scale); +} diff --git a/packages/fuselage/src/styles/mixins/states.scss b/packages/fuselage/src/styles/mixins/states.scss index 555f8ef25c..0fc58ec658 100644 --- a/packages/fuselage/src/styles/mixins/states.scss +++ b/packages/fuselage/src/styles/mixins/states.scss @@ -30,7 +30,8 @@ // } @at-root .js-focus-visible &:focus.focus-visible, - .js-focus-visible &.focus.focus-visible { + .js-focus-visible &.focus.focus-visible, + &.is-focused { @content; } @@ -58,3 +59,9 @@ @content; } } + +@mixin on-pressed { + &-pressed { + @content; + } +} diff --git a/packages/fuselage/src/styles/primitives/box.scss b/packages/fuselage/src/styles/primitives/box.scss index 06ecee86b0..7a9dad52c7 100644 --- a/packages/fuselage/src/styles/primitives/box.scss +++ b/packages/fuselage/src/styles/primitives/box.scss @@ -14,7 +14,7 @@ } %box--animated { - transition: all 230ms; + transition: all 0.18s; @media (prefers-reduced-motion) { transition: none; diff --git a/packages/fuselage/src/styles/primitives/button.scss b/packages/fuselage/src/styles/primitives/button.scss index 4bcd9dad1e..4cbbafef70 100644 --- a/packages/fuselage/src/styles/primitives/button.scss +++ b/packages/fuselage/src/styles/primitives/button.scss @@ -1,7 +1,6 @@ @use 'sass:map'; @use '../lengths'; -@import '../mixins/states.scss'; @import '../mixins/states.scss'; @import '../mixins/shadows.scss'; @import '../variables/button-colors'; @@ -34,6 +33,11 @@ @include use-no-shadow; } + @include on-pressed { + color: map.get($colors, pressed-color); + border-color: map.get($colors, pressed-border-color); + background-color: map.get($colors, pressed-background-color); + } @include on-disabled { color: map.get($colors, disabled-color); border-color: map.get($colors, disabled-border-color); diff --git a/packages/fuselage/src/styles/variables/buttons.scss b/packages/fuselage/src/styles/variables/buttons.scss index b7a76f2178..7b11e963a4 100644 --- a/packages/fuselage/src/styles/variables/buttons.scss +++ b/packages/fuselage/src/styles/variables/buttons.scss @@ -169,6 +169,8 @@ $icon: ( disabled-background-color: -color('icon', 'disabled-background-color', transparent), disabled-border-color: -color('icon', 'disabled-border-color', transparent), disabled-color: -color('icon', 'disabled-color', map.get($secondary, disabled-color)), + pressed-background-color: -color('icon', 'pressed-background-color', map.get($secondary, active-background-color)), + pressed-border-color: -color('icon', 'pressed-border-color', map.get($secondary, active-border-color)), ); $icon-info: ( @@ -184,7 +186,9 @@ $icon-info: ( focus-shadow-color: -color('icon-info', 'focus-shadow-color', map.get($secondary, focus-shadow-color)), disabled-background-color: -color('icon', 'disabled-background-color', transparent), disabled-border-color: -color('icon', 'disabled-border-color', transparent), - disabled-color: -color('icon-info', 'disabled-color', map.get($secondary, disabled-color)), + disabled-color: -color('icon-info', 'disabled-color', map.get($primary, disabled-background-color)), + pressed-background-color: -color('icon-info', 'pressed-background-color', map.get($secondary, active-background-color)), + pressed-border-color: -color('icon-info', 'pressed-border-color', map.get($secondary, active-background-color)), ); $icon-success: ( @@ -200,7 +204,9 @@ $icon-success: ( focus-shadow-color: -color('icon-success', 'focus-shadow-color', map.get($secondary, focus-shadow-color)), disabled-background-color: -color('icon', 'disabled-background-color', transparent), disabled-border-color: -color('icon', 'disabled-border-color', transparent), - disabled-color: -color('icon-success', 'disabled-color', map.get($secondary, disabled-color)), + disabled-color: -color('icon-success', 'disabled-color', map.get($success, disabled-background-color)), + pressed-background-color: -color('icon-success', 'pressed-background-color', map.get($secondary, active-background-color)), + pressed-border-color: -color('icon-success', 'pressed-border-color', map.get($secondary, active-border-color)), ); $icon-warning: ( @@ -216,7 +222,9 @@ $icon-warning: ( focus-shadow-color: -color('icon-warning', 'focus-shadow-color', map.get($secondary, focus-shadow-color)), disabled-background-color: -color('icon', 'disabled-background-color', transparent), disabled-border-color: -color('icon', 'disabled-border-color', transparent), - disabled-color: -color('icon-warning', 'disabled-color', map.get($secondary, disabled-color)), + disabled-color: -color('icon-warning', 'disabled-color', map.get($warning, disabled-background-color)), + pressed-background-color: -color('icon-warning', 'pressed-background-color', map.get($secondary, active-background-color)), + pressed-border-color: -color('icon-warning', 'pressed-border-color', map.get($secondary, active-border-color)), ); $icon-danger: ( @@ -232,5 +240,7 @@ $icon-danger: ( focus-shadow-color: -color('icon-danger', 'focus-shadow-color', map.get($secondary, focus-shadow-color)), disabled-background-color: -color('icon', 'disabled-background-color', transparent), disabled-border-color: -color('icon', 'disabled-border-color', transparent), - disabled-color: -color('icon-danger', 'disabled-color', map.get($secondary, disabled-color)), + disabled-color: -color('icon-danger', 'disabled-color', map.get($danger, disabled-background-color)), + pressed-background-color: -color('icon-danger', 'pressed-background-color', map.get($secondary, active-background-color)), + pressed-border-color: -color('icon-danger', 'pressed-border-color', map.get($secondary, active-border-color)), ); diff --git a/packages/fuselage/tsconfig-bundle.json b/packages/fuselage/tsconfig.build.json similarity index 100% rename from packages/fuselage/tsconfig-bundle.json rename to packages/fuselage/tsconfig.build.json diff --git a/packages/fuselage/tsconfig.json b/packages/fuselage/tsconfig.json index a74f359908..7a46333d96 100644 --- a/packages/fuselage/tsconfig.json +++ b/packages/fuselage/tsconfig.json @@ -1,29 +1,19 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDirs": ["./src/", "./.storybook/"], + "rootDirs": ["./src", "./.storybook"], "target": "ES5", - "module": "ESNext", + "module": "CommonJS", "lib": ["ES2020", "DOM"], "downlevelIteration": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "./dist/", - "strict": true, + "outDir": "./dist", "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "resolveJsonModule": true, "allowJs": true, - "jsx": "react", - "importsNotUsedAsValues": "error" + "jsx": "react" }, - "include": ["./src/", "./.jest/**/*.d.ts"], - "exclude": [ - "./dist/", - "./storybook-static/", - "./src/**/*.spec.{js,ts,tsx}", - "./*.js" - ] + "include": ["./src", "./.storybook/**/*", "./.jest/**/*"], + "exclude": ["./dist", "./storybook-static/", "./*.js"] } diff --git a/packages/fuselage/webpack.config.js b/packages/fuselage/webpack.config.js index 8496932527..9b039d9bb9 100644 --- a/packages/fuselage/webpack.config.js +++ b/packages/fuselage/webpack.config.js @@ -2,8 +2,10 @@ const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const webpack = require('webpack'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); +const WrapperPlugin = require('wrapper-webpack-plugin'); const pkg = require('./package.json'); @@ -41,23 +43,24 @@ module.exports = (env, { mode = 'production' }) => ({ use: { loader: 'ts-loader', options: { - configFile: path.resolve(__dirname, './tsconfig-bundle.json'), + configFile: path.resolve(__dirname, './tsconfig.build.json'), }, }, }, { test: /\.scss$/, use: [ + MiniCssExtractPlugin.loader, 'babel-loader', - { - loader: 'style-loader', - options: { - attributes: { - id: 'fuselage-style', - }, - injectType: 'lazySingletonStyleTag', - }, - }, + // { + // loader: 'style-loader', + // options: { + // attributes: { + // id: 'fuselage-style', + // }, + // injectType: 'lazySingletonStyleTag', + // }, + // }, { loader: 'css-loader', options: { @@ -96,19 +99,43 @@ module.exports = (env, { mode = 'production' }) => ({ root: 'React', }, }, + 'react-virtuoso', 'react-dom', '@rocket.chat/icons', '@rocket.chat/fuselage-hooks', + 'react-aria', + 'react-stately', + '@rocket.chat/css-in-js', + '@rocket.chat/css-supports', + '@rocket.chat/fuselage-tokens', + '@rocket.chat/memo', + '@rocket.chat/styled', ], plugins: [ + new MiniCssExtractPlugin(), new webpack.DefinePlugin({ 'process.env.VERSION': JSON.stringify(pkg.version), }), new BundleAnalyzerPlugin({ analyzerMode: 'static', generateStatsFile: false, - reportFilename: '../bundle-report.html', + reportFilename: + mode !== 'production' + ? '../bundle-report-dev.html' + : '../bundle-report.html', openAnalyzer: false, }), - ], + + mode !== 'production' && + new WrapperPlugin({ + test: /development\.js$/, // only wrap output of bundle files with '.js' extension + header: `'use strict'; + +if (process.env.NODE_ENV !== "production") { +(function() { +`, + footer: `\n})(); +}`, + }), + ].filter(Boolean), }); diff --git a/packages/icons/.stylelintrc b/packages/icons/.stylelintrc index a7b7d9057d..14dad838c1 100644 --- a/packages/icons/.stylelintrc +++ b/packages/icons/.stylelintrc @@ -24,22 +24,14 @@ "declaration-block-no-redundant-longhand-properties": true, "declaration-block-no-shorthand-property-overrides": true, "declaration-block-single-line-max-declarations": 1, - "declaration-block-trailing-semicolon": "always", "font-family-no-duplicate-names": true, "function-linear-gradient-no-nonstandard-direction": true, - "function-max-empty-lines": 0, "function-name-case": "lower", "keyframe-declaration-no-important": true, "length-zero-no-unit": true, - "max-empty-lines": 1, - "media-feature-name-case": "lower", "media-feature-name-no-unknown": true, "no-duplicate-selectors": true, "no-empty-source": true, - "no-extra-semicolons": true, - "number-leading-zero": "always", - "number-no-trailing-zeros": true, - "property-case": "lower", "property-no-unknown": true, "rule-empty-line-before": [ "always", @@ -55,8 +47,6 @@ "scss/at-mixin-pattern": "^-?([a-z][a-z0-9]+-)*[a-z][a-z0-9]+$", "scss/dollar-variable-pattern": "^-?([a-z][a-z0-9]+-)*[a-z][a-z0-9]+$", "scss/no-duplicate-mixins": true, - "selector-max-empty-lines": 0, - "selector-pseudo-class-case": "lower", "selector-pseudo-class-no-unknown": [ true, { @@ -65,15 +55,12 @@ ] } ], - "selector-pseudo-element-case": "lower", "selector-pseudo-element-colon-notation": "double", "selector-pseudo-element-no-unknown": true, "selector-type-case": "lower", "selector-type-no-unknown": true, "shorthand-property-no-redundant-values": true, - "unit-case": "lower", "unit-no-unknown": true, - "value-list-max-empty-lines": 1, "order/properties-order": [ [ { diff --git a/packages/icons/glyphsMapping.json b/packages/icons/glyphsMapping.json index 6cf5c536a4..af644a6af5 100644 --- a/packages/icons/glyphsMapping.json +++ b/packages/icons/glyphsMapping.json @@ -910,5 +910,47 @@ }, "desktop-text": { "start": "\ue125" + }, + "rocket": { + "start": "\ue127" + }, + "burger-menu": { + "start": "\ue129" + }, + "airplane": { + "start": "\ue12b" + }, + "lamp-bulb": { + "start": "\ue12d" + }, + "percentage": { + "start": "\ue12f" + }, + "leaf": { + "start": "\ue131" + }, + "user-lock": { + "start": "\ue137" + }, + "dashboard": { + "start": "\ue139" + }, + "pause-shape-filled": { + "start": "\ue13b" + }, + "play-shape-filled": { + "start": "\ue13d" + }, + "flask": { + "start": "\ue13f" + }, + "tag": { + "start": "\ue141" + }, + "smart": { + "start": "\ue143" + }, + "person-arms-spread": { + "start": "\ue145" } } diff --git a/packages/icons/package.json b/packages/icons/package.json index c4ea7300d5..d831958369 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -36,15 +36,15 @@ "@rocket.chat/prettier-config": "workspace:~", "build-icons": "workspace:~", "bump": "workspace:~", - "eslint": "~8.26.0", + "eslint": "~8.38.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "rimraf": "^3.0.2", - "stylelint": "~14.14.1", - "stylelint-order": "~5.0.0", - "stylelint-prettier": "~2.0.0", - "stylelint-scss": "~4.3.0" + "stylelint": "~15.4.0", + "stylelint-order": "~6.0.3", + "stylelint-prettier": "~3.0.0", + "stylelint-scss": "~4.6.0" } } diff --git a/packages/icons/src/airplane.svg b/packages/icons/src/airplane.svg new file mode 100644 index 0000000000..afdd92d974 --- /dev/null +++ b/packages/icons/src/airplane.svg @@ -0,0 +1,5 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> +<path + d="M16 6C15.592 6 15.2007 6.16209 14.9121 6.4506C14.6236 6.73912 14.4615 7.13044 14.4615 7.53846V12.6154C14.4615 12.9942 14.2475 13.3404 13.9088 13.5098L6 17.4642V19.011L13.2654 17.5579C13.5592 17.4991 13.8638 17.5752 14.0955 17.7651C14.3272 17.9551 14.4615 18.2389 14.4615 18.5385V21.9231C14.4615 22.1883 14.3562 22.4426 14.1686 22.6302L12.7692 24.0296V25.523L15.6286 24.3792C15.867 24.2839 16.133 24.2839 16.3714 24.3792L19.2308 25.523V24.0296L17.8314 22.6302C17.6438 22.4426 17.5385 22.1883 17.5385 21.9231V18.5385C17.5385 18.2389 17.6728 17.9551 17.9045 17.7651C18.1362 17.5752 18.4408 17.4991 18.7346 17.5579L26 19.011V17.4642L18.0912 13.5098C17.7525 13.3404 17.5385 12.9942 17.5385 12.6154V7.53846C17.5385 7.13044 17.3764 6.73912 17.0879 6.4506C16.7993 6.16209 16.408 6 16 6ZM13.4979 5.03639C14.1615 4.3728 15.0615 4 16 4C16.9385 4 17.8385 4.3728 18.5021 5.03639C19.1657 5.69998 19.5385 6.6 19.5385 7.53846V11.9974L27.4472 15.9517C27.786 16.1211 28 16.4674 28 16.8462V20.2308C28 20.5304 27.8657 20.8142 27.634 21.0041C27.4023 21.194 27.0977 21.2701 26.8039 21.2113L19.5385 19.7583V21.5089L20.9379 22.9083C21.1254 23.0958 21.2308 23.3502 21.2308 23.6154V27C21.2308 27.3318 21.0662 27.642 20.7914 27.8281C20.5166 28.0141 20.1675 28.0517 19.8594 27.9285L16 26.3847L12.1406 27.9285C11.8325 28.0517 11.4834 28.0141 11.2086 27.8281C10.9338 27.642 10.7692 27.3318 10.7692 27V23.6154C10.7692 23.3502 10.8746 23.0958 11.0621 22.9083L12.4615 21.5089V19.7583L5.19612 21.2113C4.90234 21.2701 4.5977 21.194 4.36601 21.0041C4.13432 20.8142 4 20.5304 4 20.2308V16.8462C4 16.4674 4.214 16.1211 4.55279 15.9517L12.4615 11.9974V7.53846C12.4615 6.6 12.8343 5.69998 13.4979 5.03639Z" + /> +</svg> diff --git a/packages/icons/src/burger-menu.svg b/packages/icons/src/burger-menu.svg new file mode 100644 index 0000000000..833278ea1f --- /dev/null +++ b/packages/icons/src/burger-menu.svg @@ -0,0 +1,5 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> +<path + d="M5.33337 7.66675C5.33337 7.11446 5.78109 6.66675 6.33337 6.66675H26.3598C26.9121 6.66675 27.3598 7.11446 27.3598 7.66675C27.3598 8.21903 26.9121 8.66675 26.3598 8.66675H6.33337C5.78109 8.66675 5.33337 8.21903 5.33337 7.66675ZM5.33337 15.6667C5.33337 15.1145 5.78109 14.6667 6.33337 14.6667H26.3598C26.9121 14.6667 27.3598 15.1145 27.3598 15.6667C27.3598 16.219 26.9121 16.6667 26.3598 16.6667H6.33337C5.78109 16.6667 5.33337 16.219 5.33337 15.6667ZM5.33337 23.6667C5.33337 23.1145 5.78109 22.6667 6.33337 22.6667H26.3598C26.9121 22.6667 27.3598 23.1145 27.3598 23.6667C27.3598 24.219 26.9121 24.6667 26.3598 24.6667H6.33337C5.78109 24.6667 5.33337 24.219 5.33337 23.6667Z" + /> +</svg> diff --git a/packages/icons/src/burger.svg b/packages/icons/src/burger.svg index 06fb57765c..8ee108701f 100644 --- a/packages/icons/src/burger.svg +++ b/packages/icons/src/burger.svg @@ -1,6 +1,6 @@ -<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> + <path - d="M5.33337 7.66675C5.33337 7.11446 5.78109 6.66675 6.33337 6.66675H26.3598C26.9121 6.66675 27.3598 7.11446 27.3598 7.66675C27.3598 8.21903 26.9121 8.66675 26.3598 8.66675H6.33337C5.78109 8.66675 5.33337 8.21903 5.33337 7.66675ZM5.33337 15.6667C5.33337 15.1145 5.78109 14.6667 6.33337 14.6667H26.3598C26.9121 14.6667 27.3598 15.1145 27.3598 15.6667C27.3598 16.219 26.9121 16.6667 26.3598 16.6667H6.33337C5.78109 16.6667 5.33337 16.219 5.33337 15.6667ZM5.33337 23.6667C5.33337 23.1145 5.78109 22.6667 6.33337 22.6667H26.3598C26.9121 22.6667 27.3598 23.1145 27.3598 23.6667C27.3598 24.219 26.9121 24.6667 26.3598 24.6667H6.33337C5.78109 24.6667 5.33337 24.219 5.33337 23.6667Z" - fill="#6C727A" + d="M6.00875 13H25.9912C26.2906 13.0015 26.5865 12.9358 26.8571 12.8077C27.1277 12.6796 27.3661 12.4924 27.5546 12.2599C27.7432 12.0274 27.8772 11.7556 27.9467 11.4644C28.0162 11.1732 28.0195 10.8701 27.9562 10.5775C27.0275 6.1875 22 3 16 3C10 3 4.9725 6.1875 4.04375 10.5775C3.9805 10.8701 3.98375 11.1732 4.05326 11.4644C4.12277 11.7556 4.25676 12.0274 4.44535 12.2599C4.63394 12.4924 4.87232 12.6796 5.14291 12.8077C5.41349 12.9358 5.70938 13.0015 6.00875 13ZM16 5C20.9775 5 25.2762 7.57625 25.9912 11H6.00875L6 10.9913C6.72375 7.57625 11.0225 5 16 5ZM28.6575 19.06L23.5163 20.935L18.875 19.0713C18.6367 18.976 18.3708 18.976 18.1325 19.0713L13.5075 20.9225L8.875 19.0713C8.64694 18.9801 8.39332 18.9761 8.1625 19.06L2.6625 21.06C2.43005 21.1623 2.24523 21.3493 2.14557 21.5829C2.04591 21.8165 2.03888 22.0792 2.12589 22.3179C2.2129 22.5565 2.38744 22.753 2.61409 22.8676C2.84073 22.9823 3.1025 23.0063 3.34625 22.935L5 22.3363V23C5 24.3261 5.52678 25.5979 6.46447 26.5355C7.40215 27.4732 8.67392 28 10 28H22C23.3261 28 24.5979 27.4732 25.5355 26.5355C26.4732 25.5979 27 24.3261 27 23V21.7913L29.3412 20.94C29.4725 20.9016 29.5945 20.8366 29.6997 20.7492C29.8049 20.6617 29.891 20.5536 29.9528 20.4315C30.0145 20.3095 30.0505 20.176 30.0586 20.0395C30.0667 19.9029 30.0467 19.7662 29.9999 19.6377C29.953 19.5091 29.8803 19.3916 29.7862 19.2923C29.6921 19.1931 29.5786 19.1141 29.4528 19.0605C29.3269 19.0068 29.1915 18.9795 29.0547 18.9803C28.9179 18.9811 28.7827 19.0099 28.6575 19.065V19.06ZM25 23C25 23.7956 24.6839 24.5587 24.1213 25.1213C23.5587 25.6839 22.7956 26 22 26H10C9.20435 26 8.44129 25.6839 7.87868 25.1213C7.31607 24.5587 7 23.7956 7 23V21.61L8.48375 21.07L13.125 22.9287C13.3633 23.024 13.6292 23.024 13.8675 22.9287L18.4925 21.0775L23.1175 22.9287C23.3456 23.0199 23.5992 23.0239 23.83 22.94L24.9888 22.5187L25 23ZM2 16C2 15.7348 2.10536 15.4804 2.29289 15.2929C2.48043 15.1054 2.73478 15 3 15H29C29.2652 15 29.5196 15.1054 29.7071 15.2929C29.8946 15.4804 30 15.7348 30 16C30 16.2652 29.8946 16.5196 29.7071 16.7071C29.5196 16.8946 29.2652 17 29 17H3C2.73478 17 2.48043 16.8946 2.29289 16.7071C2.10536 16.5196 2 16.2652 2 16Z" /> </svg> diff --git a/packages/icons/src/dashboard.svg b/packages/icons/src/dashboard.svg new file mode 100644 index 0000000000..bb235259d5 --- /dev/null +++ b/packages/icons/src/dashboard.svg @@ -0,0 +1,14 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> +<path + d="M21.3334 8.33321C21.8857 8.33321 22.3334 8.78093 22.3334 9.33321V22.6665C22.3334 23.2188 21.8857 23.6665 21.3334 23.6665C20.7811 23.6665 20.3334 23.2188 20.3334 22.6665V9.33321C20.3334 8.78093 20.7811 8.33321 21.3334 8.33321Z" + /> +<path + d="M17 13.3335C17 12.7812 16.5523 12.3335 16 12.3335C15.4477 12.3335 15 12.7812 15 13.3335V22.6668C15 23.2191 15.4477 23.6668 16 23.6668C16.5523 23.6668 17 23.2191 17 22.6668V13.3335Z" + /> +<path + d="M11.6667 17.3335C11.6667 16.7812 11.2189 16.3335 10.6667 16.3335C10.1144 16.3335 9.66665 16.7812 9.66665 17.3335V22.6669C9.66665 23.2191 10.1144 23.6669 10.6667 23.6669C11.2189 23.6669 11.6667 23.2191 11.6667 22.6669V17.3335Z" + /> +<path + d="M6.23611 4C5.00114 4 4 5.00114 4 6.23611V25.7639C4 26.9989 5.00114 28 6.23611 28H25.7639C26.9989 28 28 26.9989 28 25.7639V6.23611C28 5.00114 26.9989 4 25.7639 4H6.23611ZM25.7639 6C25.8943 6 26 6.10571 26 6.23611V25.7639C26 25.8943 25.8943 26 25.7639 26H6.23611C6.10571 26 6 25.8943 6 25.7639V6.23611C6 6.10571 6.10571 6 6.23611 6H25.7639Z" + /> +</svg> diff --git a/packages/icons/src/flask.svg b/packages/icons/src/flask.svg new file mode 100644 index 0000000000..1c824b99d1 --- /dev/null +++ b/packages/icons/src/flask.svg @@ -0,0 +1,5 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> + <path + d="M27.7113 24.9713L20 12.115V5H21C21.2652 5 21.5196 4.89464 21.7071 4.70711C21.8947 4.51957 22 4.26522 22 4C22 3.73478 21.8947 3.48043 21.7071 3.29289C21.5196 3.10536 21.2652 3 21 3H11C10.7348 3 10.4805 3.10536 10.2929 3.29289C10.1054 3.48043 10 3.73478 10 4C10 4.26522 10.1054 4.51957 10.2929 4.70711C10.4805 4.89464 10.7348 5 11 5H12V12.115L4.28878 24.9713C4.10691 25.2745 4.00867 25.6205 4.00408 25.974C3.99948 26.3276 4.08869 26.676 4.26262 26.9838C4.43655 27.2917 4.68898 27.5479 4.99418 27.7264C5.29938 27.9049 5.64646 27.9993 6.00003 28H26C26.3539 28 26.7015 27.9061 27.0072 27.7278C27.3129 27.5496 27.5659 27.2934 27.7403 26.9855C27.9146 26.6775 28.0042 26.3288 27.9997 25.9749C27.9953 25.6211 27.8971 25.2747 27.715 24.9713H27.7113ZM13.8575 12.9062C13.9511 12.7512 14.0003 12.5736 14 12.3925V5H18V12.3925C17.9997 12.5736 18.049 12.7512 18.1425 12.9062L22.92 20.875C21.42 21.1712 19.2863 21.0462 16.4513 19.6112C14.4625 18.605 12.57 18.0712 10.7988 18.01L13.8575 12.9062ZM6.00003 26L9.56753 20.0525C11.3488 19.835 13.3563 20.2838 15.545 21.3925C17.92 22.5938 19.92 23.0025 21.545 23.0025C22.3715 23.0062 23.1943 22.8925 23.9888 22.665L26 26H6.00003Z" + /> +</svg> diff --git a/packages/icons/src/lamp-bulb.svg b/packages/icons/src/lamp-bulb.svg new file mode 100644 index 0000000000..45ebaa1144 --- /dev/null +++ b/packages/icons/src/lamp-bulb.svg @@ -0,0 +1,5 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> +<path + d="M15.7854 4.00243C17.686 3.95943 19.5486 4.48895 21.1087 5.51026C22.6682 6.53119 23.8431 7.98924 24.4759 9.67149C25.1084 11.353 25.1698 13.1798 24.6524 14.8954C24.1351 16.6107 23.0629 18.1355 21.5784 19.2488C21.094 19.607 20.6975 20.069 20.4225 20.6022C20.1468 21.1368 20.0016 21.7272 20.0004 22.3277L20.0004 23H17V18.3508L20.7809 13.6247C21.1259 13.1934 21.056 12.5641 20.6247 12.2191C20.1934 11.8741 19.5641 11.944 19.2191 12.3753L16 16.3992L12.7809 12.3753C12.4359 11.944 11.8066 11.8741 11.3753 12.2191C10.944 12.5641 10.8741 13.1934 11.2191 13.6247L15 18.3508V23H12.0002L12.0002 22.3298L12.0002 22.3226C11.9959 21.7279 11.8514 21.1436 11.5795 20.6135C11.3078 20.0837 10.9167 19.6232 10.4386 19.2637L9.83769 20.063L10.4336 19.26C9.36337 18.4657 8.50058 17.4525 7.90718 16.2995C7.3139 15.1467 7.00449 13.8829 7.0001 12.6031L7.0001 12.6017C6.97792 8.00853 10.866 4.11061 15.7854 4.00243ZM20.0004 25C20.5162 25 21.0197 24.8049 21.3979 24.4439C21.7778 24.0813 22.0004 23.5794 22.0004 23.0456L22.0004 22.3318C22.0011 22.0522 22.0686 21.7738 22.2001 21.5188C22.3318 21.2633 22.5253 21.0357 22.7691 20.8558L22.7751 20.8513C24.593 19.4891 25.923 17.6091 26.5673 15.4729C27.2117 13.3361 27.1346 11.059 26.3479 8.96737C25.5614 6.87648 24.1084 5.08362 22.2041 3.83694C20.3005 2.59074 18.0393 1.95105 15.7408 2.00292C9.8108 2.13366 4.97308 6.84855 5.00011 12.61L5.00012 12.6114L6.00011 12.6066L5.00011 12.61C5.0056 14.2089 5.39239 15.7837 6.12888 17.2147C6.86472 18.6445 7.92939 19.8915 9.23891 20.8639C9.47898 21.0449 9.66957 21.2721 9.79991 21.5262C9.93003 21.78 9.99772 22.0557 10.0002 22.3337V23.0456C10.0002 23.5794 10.2228 24.0813 10.6027 24.4439C10.981 24.8049 11.4845 25 12.0002 25H20.0004ZM11 27C10.4477 27 10 27.4477 10 28C10 28.5523 10.4477 29 11 29H21C21.5523 29 22 28.5523 22 28C22 27.4477 21.5523 27 21 27H11Z" + /> +</svg> diff --git a/packages/icons/src/leaf.svg b/packages/icons/src/leaf.svg new file mode 100644 index 0000000000..e9565a7f82 --- /dev/null +++ b/packages/icons/src/leaf.svg @@ -0,0 +1,5 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> +<path + d="M7.33846 23.2471C4.98852 18.822 5.51979 14.3185 8.60431 11.0055C11.7848 7.58942 17.5079 5.62053 24.9992 6.0612C25.5052 6.09097 25.909 6.49469 25.9387 7.00075C26.3794 14.4921 24.4105 20.2151 20.9944 23.3956C17.6814 26.4802 13.1779 27.0114 8.75263 24.6614L6.70711 26.7069C6.31658 27.0974 5.68342 27.0974 5.29289 26.7069C4.90237 26.3164 4.90237 25.6832 5.29289 25.2927L7.33846 23.2471ZM8.82845 21.7571L18.2068 12.3788C18.5973 11.9883 19.2305 11.9883 19.621 12.3788C20.0115 12.7694 20.0115 13.4025 19.621 13.793L10.2426 23.1714C13.7915 24.8351 17.1404 24.2512 19.6316 21.9318C22.3372 19.4128 24.199 14.686 23.9832 8.01669C17.3139 7.80092 12.5871 9.66272 10.0681 12.3684C7.74877 14.8595 7.16486 18.2083 8.82845 21.7571Z" + /> +</svg> diff --git a/packages/icons/src/pause-shape-filled.svg b/packages/icons/src/pause-shape-filled.svg new file mode 100644 index 0000000000..3a29317741 --- /dev/null +++ b/packages/icons/src/pause-shape-filled.svg @@ -0,0 +1,5 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> + <path + d="M26 7.66667V24.3333C26 24.7754 25.8221 25.1993 25.5055 25.5118C25.1889 25.8244 24.7595 26 24.3117 26H20.0909C19.6431 26 19.2137 25.8244 18.8971 25.5118C18.5805 25.1993 18.4026 24.7754 18.4026 24.3333V7.66667C18.4026 7.22464 18.5805 6.80072 18.8971 6.48816C19.2137 6.17559 19.6431 6 20.0909 6H24.3117C24.7595 6 25.1889 6.17559 25.5055 6.48816C25.8221 6.80072 26 7.22464 26 7.66667ZM11.9091 6H7.68831C7.24054 6 6.81111 6.17559 6.49449 6.48816C6.17788 6.80072 6 7.22464 6 7.66667V24.3333C6 24.7754 6.17788 25.1993 6.49449 25.5118C6.81111 25.8244 7.24054 26 7.68831 26H11.9091C12.3569 26 12.7863 25.8244 13.1029 25.5118C13.4195 25.1993 13.5974 24.7754 13.5974 24.3333V7.66667C13.5974 7.22464 13.4195 6.80072 13.1029 6.48816C12.7863 6.17559 12.3569 6 11.9091 6Z" + /> +</svg> diff --git a/packages/icons/src/percentage.svg b/packages/icons/src/percentage.svg new file mode 100644 index 0000000000..82627c0d97 --- /dev/null +++ b/packages/icons/src/percentage.svg @@ -0,0 +1,11 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> +<path + d="M25.7071 6.29289C26.0976 6.68342 26.0976 7.31658 25.7071 7.70711L7.70711 25.7071C7.31658 26.0976 6.68342 26.0976 6.29289 25.7071C5.90237 25.3166 5.90237 24.6834 6.29289 24.2929L24.2929 6.29289C24.6834 5.90237 25.3166 5.90237 25.7071 6.29289Z" + /> +<path + d="M6 10C6 7.79086 7.79086 6 10 6C12.2091 6 14 7.79086 14 10C14 12.2091 12.2091 14 10 14C7.79086 14 6 12.2091 6 10ZM10 8C8.89543 8 8 8.89543 8 10C8 11.1046 8.89543 12 10 12C11.1046 12 12 11.1046 12 10C12 8.89543 11.1046 8 10 8Z" + /> +<path + d="M22 18C19.7909 18 18 19.7909 18 22C18 24.2091 19.7909 26 22 26C24.2091 26 26 24.2091 26 22C26 19.7909 24.2091 18 22 18ZM20 22C20 20.8954 20.8954 20 22 20C23.1046 20 24 20.8954 24 22C24 23.1046 23.1046 24 22 24C20.8954 24 20 23.1046 20 22Z" + /> +</svg> diff --git a/packages/icons/src/person-arms-spread.svg b/packages/icons/src/person-arms-spread.svg new file mode 100644 index 0000000000..3f9f55027c --- /dev/null +++ b/packages/icons/src/person-arms-spread.svg @@ -0,0 +1,5 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> +<path + d="M20.0001 5C20.0001 4.20888 19.7655 3.43552 19.326 2.77772C18.8864 2.11992 18.2617 1.60723 17.5308 1.30448C16.7999 1.00173 15.9956 0.92252 15.2197 1.07686C14.4438 1.2312 13.7311 1.61216 13.1716 2.17157C12.6122 2.73098 12.2313 3.44372 12.0769 4.21964C11.9226 4.99556 12.0018 5.79983 12.3046 6.53074C12.6073 7.26164 13.12 7.88635 13.7778 8.32588C14.4356 8.76541 15.209 9 16.0001 9C17.0609 9 18.0784 8.57857 18.8285 7.82843C19.5786 7.07828 20.0001 6.06087 20.0001 5ZM16.0001 7C15.6045 7 15.2178 6.8827 14.8889 6.66294C14.56 6.44318 14.3037 6.13082 14.1523 5.76537C14.0009 5.39992 13.9613 4.99778 14.0385 4.60982C14.1157 4.22186 14.3062 3.86549 14.5859 3.58579C14.8656 3.30608 15.2219 3.1156 15.6099 3.03843C15.9979 2.96126 16.4 3.00087 16.7654 3.15224C17.1309 3.30362 17.4433 3.55996 17.663 3.88886C17.8828 4.21776 18.0001 4.60444 18.0001 5C18.0001 5.53044 17.7894 6.03914 17.4143 6.41422C17.0392 6.78929 16.5305 7 16.0001 7ZM28.9376 10.9638C28.8237 10.4037 28.5179 9.90099 28.0728 9.54245C27.6278 9.18391 27.0715 8.99204 26.5001 9H5.50008C4.93018 8.99998 4.37738 9.19468 3.93328 9.55183C3.48918 9.90898 3.18043 10.4071 3.05819 10.9638C2.93595 11.5204 3.00756 12.1021 3.26116 12.6125C3.51475 13.1228 3.93511 13.5312 4.45258 13.77H4.46883L10.8126 16.5638L8.18758 26.5288C7.91291 27.1338 7.88984 27.8231 8.12344 28.4452C8.35704 29.0672 8.82818 29.571 9.4332 29.8456C10.0382 30.1203 10.7276 30.1434 11.3496 29.9098C11.9717 29.6762 12.4754 29.205 12.7501 28.6L16.0001 22.9913L19.2501 28.6C19.5389 29.1792 20.0411 29.6239 20.6511 29.8404C21.261 30.0569 21.9312 30.0285 22.5206 29.761C23.11 29.4935 23.5726 29.0078 23.8113 28.4062C24.0499 27.8045 24.0459 27.1338 23.8001 26.535L21.1751 16.57L27.5188 13.7763H27.5351C28.0609 13.5455 28.4891 13.1377 28.7454 12.6238C29.0016 12.11 29.0696 11.5226 28.9376 10.9638ZM26.7126 11.9513L19.5963 15.0838C19.3785 15.1797 19.202 15.3503 19.0987 15.5648C18.9953 15.7792 18.9719 16.0235 19.0326 16.2538L21.8913 27.125C21.9059 27.1832 21.9264 27.2398 21.9526 27.2938C21.9945 27.3835 22.0089 27.4836 21.9938 27.5815C21.9788 27.6794 21.935 27.7706 21.8681 27.8436C21.8011 27.9165 21.714 27.968 21.6178 27.9914C21.5216 28.0148 21.4206 28.0091 21.3276 27.975C21.2033 27.9294 21.1021 27.8363 21.0463 27.7163C21.0337 27.6892 21.0195 27.6629 21.0038 27.6375L16.8651 20.5C16.7772 20.3485 16.651 20.2227 16.4992 20.1352C16.3474 20.0478 16.1753 20.0018 16.0001 20.0018C15.8249 20.0018 15.6528 20.0478 15.501 20.1352C15.3492 20.2227 15.223 20.3485 15.1351 20.5L11.0001 27.6313C10.9844 27.6567 10.9702 27.683 10.9576 27.71C10.9018 27.8301 10.8006 27.9231 10.6763 27.9688C10.5833 28.0028 10.4823 28.0085 10.3861 27.9851C10.2899 27.9617 10.2028 27.9103 10.1358 27.8373C10.0689 27.7643 10.0251 27.6731 10.0101 27.5752C9.99502 27.4774 10.0094 27.3772 10.0513 27.2875C10.0775 27.2335 10.098 27.177 10.1126 27.1188L12.9676 16.25C13.0283 16.0198 13.0048 15.7755 12.9015 15.561C12.7981 15.3466 12.6217 15.176 12.4038 15.08L5.28758 11.9513C5.18543 11.9025 5.10284 11.8206 5.05331 11.7188C5.00379 11.617 4.99026 11.5014 5.01493 11.391C5.03961 11.2805 5.10103 11.1817 5.18916 11.1107C5.27728 11.0397 5.3869 11.0006 5.50008 11H26.5001C26.6136 10.9998 26.7238 11.0382 26.8125 11.1089C26.9013 11.1797 26.9634 11.2785 26.9885 11.3892C27.0137 11.4998 27.0005 11.6158 26.951 11.7179C26.9015 11.8201 26.8188 11.9024 26.7163 11.9513H26.7126Z" + /> +</svg> diff --git a/packages/icons/src/play-shape-filled.svg b/packages/icons/src/play-shape-filled.svg new file mode 100644 index 0000000000..34f87a3cf3 --- /dev/null +++ b/packages/icons/src/play-shape-filled.svg @@ -0,0 +1,5 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> + <path + d="M25.3333 16C25.334 16.2612 25.2654 16.5181 25.1342 16.7457C25.0031 16.9733 24.8138 17.1639 24.5848 17.299L10.3952 25.7736C10.1559 25.9166 9.88191 25.9947 9.60144 25.9997C9.32097 26.0048 9.04419 25.9367 8.7997 25.8024C8.55753 25.6702 8.3558 25.4775 8.21525 25.2439C8.0747 25.0104 8.0004 24.7445 8 24.4736V7.52637C8.0004 7.2555 8.0747 6.98963 8.21525 6.75609C8.3558 6.52255 8.55753 6.32977 8.7997 6.19759C9.04419 6.06331 9.32097 5.99519 9.60144 6.00026C9.88191 6.00533 10.1559 6.08341 10.3952 6.22643L24.5848 14.701C24.8138 14.8361 25.0031 15.0267 25.1342 15.2543C25.2654 15.4819 25.334 15.7388 25.3333 16Z" + /> +</svg> diff --git a/packages/icons/src/rocket.svg b/packages/icons/src/rocket.svg new file mode 100644 index 0000000000..326fbf39b5 --- /dev/null +++ b/packages/icons/src/rocket.svg @@ -0,0 +1,8 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> +<path + d="M17.6813 7.30819L14.6419 10.3476H8.78186L8.76762 10.3477C8.30838 10.3542 7.87022 10.541 7.54757 10.8676L4.51335 13.8925L4.51185 13.894C4.28795 14.1182 4.1291 14.3989 4.05225 14.7063C3.97541 15.0136 3.98345 15.3361 4.07552 15.6393C4.16759 15.9424 4.34024 16.2149 4.57503 16.4276C4.8096 16.6401 5.09833 16.7852 5.40873 16.8471L9.3353 17.6343C9.49478 17.6663 9.653 17.6584 9.79965 17.6175L14.8892 22.7071C14.9513 22.7692 15.0195 22.8213 15.0918 22.8637L15.8068 26.4302C15.8687 26.7407 16.0136 27.0285 16.2261 27.2631C16.4389 27.4979 16.7113 27.6705 17.0145 27.7626C17.3177 27.8547 17.6401 27.8627 17.9475 27.7859C18.2549 27.709 18.5356 27.5502 18.7598 27.3263L21.7861 24.2905C22.1127 23.9679 22.2996 23.5297 22.3061 23.0705L22.3062 23.0563V17C22.3062 16.9096 22.2942 16.8221 22.2717 16.7388L24.6918 14.3188C28.1456 10.8649 28.1441 7.34408 27.9153 5.76382C27.8571 5.33907 27.6615 4.94495 27.3583 4.64171C27.0551 4.33848 26.6609 4.14289 26.2362 4.08473C24.6559 3.85595 21.1351 3.85438 17.6813 7.30819ZM19.0955 8.7224C21.9748 5.84308 24.7875 5.89824 25.9376 6.06237C26.1018 7.21248 26.1569 10.0252 23.2776 12.9045L15.5964 20.5858L11.4142 16.4037L19.0955 8.7224ZM12.6419 12.3476L9.38497 15.6045L6.25776 14.9775L8.89587 12.3476H12.6419ZM16.9653 22.0452L20.3062 18.7044V22.9422L17.6762 25.5804L16.9804 22.1097C16.9761 22.0879 16.971 22.0664 16.9653 22.0452Z" + /> +<path + d="M8.94765 21.8656C8.0472 22.3865 7.54103 23.3395 7.26718 24.2954C7.21261 24.4858 7.16906 24.6705 7.13433 24.8438C7.30762 24.8091 7.4923 24.7655 7.68278 24.711C8.63861 24.4371 9.5916 23.9309 10.1125 23.0305C10.3891 22.5524 11.0009 22.3891 11.4789 22.6657C11.9569 22.9423 12.1203 23.554 11.8437 24.032C10.949 25.5785 9.41295 26.2957 8.23362 26.6336C7.6315 26.8061 7.08322 26.8915 6.68551 26.9342C6.48564 26.9557 6.32111 26.9666 6.20349 26.9722C6.14462 26.975 6.09729 26.9765 6.06294 26.9773L6.02127 26.978L6.00802 26.9781L6.00335 26.9781L6.00151 26.9781C6.00115 26.9781 6 26.9781 6 25.9781V26.9781C5.73478 26.9781 5.48043 26.8728 5.29289 26.6852C5.10536 26.4977 5 26.2426 5 25.9774L5 25.9748L5.00002 25.9701L5.00014 25.9569L5.00085 25.9152C5.00163 25.8809 5.00311 25.8335 5.00591 25.7746C5.01153 25.657 5.02249 25.4925 5.04395 25.2926C5.08665 24.8949 5.17203 24.3466 5.34453 23.7445C5.68241 22.5652 6.39967 21.0291 7.9461 20.1344C8.42415 19.8579 9.03588 20.0212 9.31245 20.4992C9.58902 20.9773 9.42569 21.589 8.94765 21.8656ZM6 25.9781L5 25.9774C5 25.9778 5 25.9781 6 25.9781Z" + /> +</svg> diff --git a/packages/icons/src/smart.svg b/packages/icons/src/smart.svg new file mode 100644 index 0000000000..10f220a197 --- /dev/null +++ b/packages/icons/src/smart.svg @@ -0,0 +1,8 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> + <path + d="M15.2403 17.3047L13.1308 22.6992C13.044 22.9212 13.3172 23.1043 13.478 22.9318L20.9406 14.9246C21.0695 14.7863 20.9747 14.5558 20.7888 14.5558H16.9371C16.8023 14.5558 16.7022 14.4268 16.7314 14.2908L17.8122 9.26594C17.8595 9.0457 17.5913 8.90555 17.4477 9.07551L11.0524 16.6444C10.9334 16.7853 11.0302 17.0054 11.2111 17.0054H15.0449C15.1943 17.0054 15.2962 17.1616 15.2403 17.3047Z" + /> + <path + d="M29 16C29 23.1797 23.1797 29 16 29C8.8203 29 3 23.1797 3 16C3 8.8203 8.8203 3 16 3C23.1797 3 29 8.8203 29 16ZM27 16C27 9.92487 22.0751 5 16 5C9.92487 5 5 9.92487 5 16C5 22.0751 9.92487 27 16 27C22.0751 27 27 22.0751 27 16Z" + /> +</svg> diff --git a/packages/icons/src/tag.svg b/packages/icons/src/tag.svg new file mode 100644 index 0000000000..f7bceb137e --- /dev/null +++ b/packages/icons/src/tag.svg @@ -0,0 +1,8 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> + <path + d="M17.0127 4.02281C17.6414 3.92707 18.2782 4.13609 18.7279 4.58581L29.0739 14.9317C29.8549 15.7128 29.8549 16.9791 29.0739 17.7601L17.7601 29.0739C16.9791 29.8549 15.7128 29.8549 14.9317 29.0739L4.58581 18.7279C4.13609 18.2782 3.92707 17.6414 4.02281 17.0127L4.02281 5.69897C4.15456 4.83367 4.83367 4.15456 5.69896 4.02281L17.0127 4.02281ZM17.3137 6.00002H6.00002V17.3137L16.3459 27.6596L27.6596 16.3459L17.3137 6.00002Z" + /> + <path + d="M9.5 9.5C8.91421 10.0858 8.91421 11.0355 9.5 11.6213C10.0858 12.2071 11.0355 12.2071 11.6213 11.6213C12.2071 11.0355 12.2071 10.0858 11.6213 9.5C11.0355 8.91421 10.0858 8.91421 9.5 9.5Z" + /> +</svg> diff --git a/packages/icons/src/user-lock.svg b/packages/icons/src/user-lock.svg new file mode 100644 index 0000000000..e6a64295ff --- /dev/null +++ b/packages/icons/src/user-lock.svg @@ -0,0 +1,11 @@ +<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> +<path + d="M18.75 9C18.75 8.44772 19.1977 8 19.75 8H20.5V6.25C20.5 4.45508 21.9551 3 23.75 3C25.5449 3 27 4.45507 27 6.25V8H27.75C28.3023 8 28.75 8.44772 28.75 9V15C28.75 15.5523 28.3023 16 27.75 16H19.75C19.1977 16 18.75 15.5523 18.75 15V9ZM22.5 6.25V7.97619H25V6.25C25 5.55964 24.4404 5 23.75 5C23.0596 5 22.5 5.55964 22.5 6.25Z" + /> +<path + d="M16 13.5C16.259 13.5 16.5103 13.4672 16.75 13.4055V15C16.75 15.1492 16.7609 15.2959 16.7819 15.4392C16.5272 15.4792 16.266 15.5 16 15.5C13.2386 15.5 11 13.2614 11 10.5C11 7.73858 13.2386 5.5 16 5.5C16.911 5.5 17.765 5.74362 18.5006 6.16927C18.5002 6.19613 18.5 6.22304 18.5 6.25V6.27202C17.8682 6.56201 17.3544 7.06506 17.0508 7.68917C16.7238 7.56687 16.3697 7.5 16 7.5C14.3431 7.5 13 8.84315 13 10.5C13 12.1569 14.3431 13.5 16 13.5Z" + /> +<path + d="M16.9538 17.7093L17.992 17.4312C18.486 17.789 19.0934 18 19.75 18H22.7583C24.1268 18.9055 25 20.4547 25 22.1709V24C25 25.6569 23.6569 27 22 27H10C8.34315 27 7 25.6569 7 24V21.9122C7 19.7419 8.45785 17.8421 10.5543 17.2806C11.3177 17.0761 12.1197 17.0635 12.8891 17.2438L15.0061 17.7399C15.6479 17.8904 16.317 17.8798 16.9538 17.7093ZM11.0718 19.2125C9.84977 19.5398 9 20.6471 9 21.9122V24C9 24.5523 9.44772 25 10 25H22C22.5523 25 23 24.5523 23 24V22.1709C23 20.7778 22.0409 19.5679 20.6846 19.25L20.4828 19.2028C20.0015 19.0899 19.4997 19.0978 19.0221 19.2258L17.4712 19.6412C16.5161 19.897 15.5124 19.9128 14.5497 19.6872L12.4327 19.191C11.9842 19.0859 11.5167 19.0933 11.0718 19.2125Z" + /> +</svg> diff --git a/packages/layout/.eslintignore b/packages/layout/.eslintignore index d4b5909e36..26042f8639 100644 --- a/packages/layout/.eslintignore +++ b/packages/layout/.eslintignore @@ -1,3 +1,4 @@ /dist /node_modules /storybook-static +!.storybook diff --git a/packages/layout/.storybook/main.ts b/packages/layout/.storybook/main.ts index aff086b825..237ada5846 100644 --- a/packages/layout/.storybook/main.ts +++ b/packages/layout/.storybook/main.ts @@ -1,7 +1,15 @@ -module.exports = { - addons: ['@storybook/addon-essentials', 'storybook-dark-mode'], - stories: ['../src/**/*.stories.tsx', '../src/**/stories.tsx'], +import type { StorybookConfig } from '@storybook/react/types'; + +const config: StorybookConfig = { + core: { + builder: 'webpack5', + }, features: { postcss: false, }, + addons: ['@storybook/addon-essentials', 'storybook-dark-mode'], + framework: '@storybook/react', + stories: ['../src/**/*.stories.tsx', '../src/**/stories.tsx'], }; + +export default config; diff --git a/packages/layout/.storybook/manager.ts b/packages/layout/.storybook/manager.ts deleted file mode 100644 index 194ef4014c..0000000000 --- a/packages/layout/.storybook/manager.ts +++ /dev/null @@ -1,17 +0,0 @@ -import colorTokens from '@rocket.chat/fuselage-tokens/colors.json'; -import { addons } from '@storybook/addons'; -import { create } from '@storybook/theming'; - -import manifest from '../package.json'; -import logo from './logo.svg'; - -addons.setConfig({ - theme: create({ - base: 'light', - brandTitle: manifest.name, - brandImage: logo, - brandUrl: manifest.homepage, - colorPrimary: colorTokens.n500, - colorSecondary: colorTokens.p500, - }), -}); diff --git a/packages/layout/.storybook/preview.tsx b/packages/layout/.storybook/preview.tsx index 51472a6f2d..3c8e3b76c7 100644 --- a/packages/layout/.storybook/preview.tsx +++ b/packages/layout/.storybook/preview.tsx @@ -1,18 +1,19 @@ import { DocsPage, DocsContainer } from '@storybook/addon-docs'; -import type { DecoratorFunction } from '@storybook/addons'; -import { addParameters } from '@storybook/react'; -import '@rocket.chat/icons/dist/rocketchat.css'; -import '@rocket.chat/fuselage-polyfills'; -import i18next from 'i18next'; -import type { ElementType, ReactElement } from 'react'; +import type { Parameters } from '@storybook/addons'; +import type { DecoratorFn } from '@storybook/react'; +import { themes } from '@storybook/theming'; import { Suspense } from 'react'; -import { I18nextProvider, initReactI18next } from 'react-i18next'; import { useDarkMode } from 'storybook-dark-mode'; +import manifest from '../package.json'; import DarkModeProvider from '../src/DarkModeProvider'; -import React from 'react'; +import logo from './logo.svg'; + +import '@rocket.chat/fuselage/dist/fuselage.css'; +import '@rocket.chat/icons/dist/rocketchat.css'; +import '@rocket.chat/fuselage-polyfills'; -addParameters({ +export const parameters: Parameters = { backgrounds: { grid: { cellSize: 4, @@ -27,29 +28,27 @@ addParameters({ options: { storySort: ([, a], [, b]) => a.kind.localeCompare(b.kind), }, -}); - -const getI18n = () => { - const i18n = i18next.createInstance().use(initReactI18next); - - // import('../.i18n/en.i18n.json').then((translation) => { - // i18n.init({ - // fallbackLng: 'en', - // debug: false, - // resources: { - // en: { - // translation, - // }, - // }, - // }); - // }); - - return i18n; + layout: 'fullscreen', + darkMode: { + dark: { + ...themes.dark, + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + }, + light: { + ...themes.normal, + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + }, + }, }; -export const decorators: DecoratorFunction<ReactElement>[] = [ - (Story: ElementType): ReactElement => { +export const decorators: DecoratorFn[] = [ + (Story) => { const dark = useDarkMode(); + return ( <Suspense fallback={null}> {/* <I18nextProvider i18n={getI18n()}> */} diff --git a/packages/layout/jest.config.js b/packages/layout/jest.config.js index 98a8568669..2857b4d157 100644 --- a/packages/layout/jest.config.js +++ b/packages/layout/jest.config.js @@ -3,12 +3,4 @@ module.exports = { errorOnDeprecated: true, testMatch: ['<rootDir>/src/**/*.spec.ts?(x)'], testEnvironment: 'jsdom', - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - }, - }, - }, }; diff --git a/packages/layout/package.json b/packages/layout/package.json index 3346b482aa..e96a24bbfe 100644 --- a/packages/layout/package.json +++ b/packages/layout/package.json @@ -35,7 +35,7 @@ "lint-staged": "lint-staged", "test": "jest --runInBand", "docs": "typedoc", - "storybook": "start-storybook -p 6006", + "storybook": "start-storybook -p 6006 --no-version-updates", "build-storybook": "build-storybook", "bump-next": "bump-next" }, @@ -49,24 +49,30 @@ "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/fuselage": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@storybook/addon-essentials": "~6.5.15", - "@storybook/addons": "~6.5.15", - "@storybook/react": "~6.5.15", - "@storybook/source-loader": "~6.5.15", - "@storybook/theming": "~6.5.15", - "@types/jest": "~27.4.1", + "@storybook/addon-essentials": "~6.5.16", + "@storybook/addons": "~6.5.16", + "@storybook/builder-webpack5": "~6.5.16", + "@storybook/manager-webpack5": "~6.5.16", + "@storybook/react": "~6.5.16", + "@storybook/theming": "~6.5.16", + "@types/jest": "~29.5.0", + "@types/react": "^17", + "@types/react-dom": "^17", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", + "react": "~17.0.2", + "react-dom": "~17.0.2", + "react-i18next": "~11.15.4", "rimraf": "~3.0.2", - "storybook-dark-mode": "^1.1.2", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "storybook-dark-mode": "~3.0.1", + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "eslintConfig": { "extends": "@rocket.chat/eslint-config-alt/typescript", diff --git a/packages/layout/src/ActionLink.tsx b/packages/layout/src/ActionLink.tsx index 457b8e61d3..63476edfcb 100644 --- a/packages/layout/src/ActionLink.tsx +++ b/packages/layout/src/ActionLink.tsx @@ -37,7 +37,7 @@ const ActionLink = ({ fontScale={'p2'} href={href} color='info' - mi='x4' + mi={4} textDecorationLine='none' onClick={handleClick} > diff --git a/packages/layout/src/FormPageLayout/Form.tsx b/packages/layout/src/FormPageLayout/Form.tsx index 2036a96642..e4fbb64e74 100644 --- a/packages/layout/src/FormPageLayout/Form.tsx +++ b/packages/layout/src/FormPageLayout/Form.tsx @@ -15,7 +15,7 @@ const Form = forwardRef< is='form' backgroundColor={colors.white} color={colors.n800} - padding='x40' + padding={40} width='full' maxWidth={576} textAlign='left' diff --git a/packages/layout/src/FormPageLayout/FormFooter.tsx b/packages/layout/src/FormPageLayout/FormFooter.tsx index f1c89b15f5..1298b60455 100644 --- a/packages/layout/src/FormPageLayout/FormFooter.tsx +++ b/packages/layout/src/FormPageLayout/FormFooter.tsx @@ -10,7 +10,7 @@ const FormFooter = forwardRef< >((props, ref) => ( <Box is='footer' - mbs='x24' + mbs={24} display='flex' alignItems='center' justifyContent='space-between' diff --git a/packages/layout/src/FormPageLayout/FormHeader.tsx b/packages/layout/src/FormPageLayout/FormHeader.tsx index c945f24991..7c5cf62021 100644 --- a/packages/layout/src/FormPageLayout/FormHeader.tsx +++ b/packages/layout/src/FormPageLayout/FormHeader.tsx @@ -7,6 +7,6 @@ const FormHeader = forwardRef< Omit<FormHTMLAttributes<HTMLElement>, 'is'> & { children: ReactNode; } ->((props, ref) => <Box is='header' mbe='x24' {...props} ref={ref} />); +>((props, ref) => <Box is='header' mbe={24} {...props} ref={ref} />); export default FormHeader; diff --git a/packages/layout/src/FormPageLayout/FormSteps.tsx b/packages/layout/src/FormPageLayout/FormSteps.tsx index 621a27bc93..1644c2d13a 100644 --- a/packages/layout/src/FormPageLayout/FormSteps.tsx +++ b/packages/layout/src/FormPageLayout/FormSteps.tsx @@ -14,7 +14,7 @@ const FormSteps = ({ const { t } = useTranslation(); return ( - <Box mbe='x8' fontScale='c2' color='neutral-600'> + <Box mbe={8} fontScale='c2' color='neutral-600'> {t('component.form.steps', { currentStep, stepCount })} </Box> ); diff --git a/packages/layout/src/FormPageLayout/FormTitle.tsx b/packages/layout/src/FormPageLayout/FormTitle.tsx index b431d58e06..47f03e31aa 100644 --- a/packages/layout/src/FormPageLayout/FormTitle.tsx +++ b/packages/layout/src/FormPageLayout/FormTitle.tsx @@ -8,7 +8,7 @@ const FormTitle = forwardRef< children: ReactNode; } >((props, ref) => ( - <Box mbe='x8' fontScale='h2' fontWeight={800} {...props} ref={ref} /> + <Box mbe={8} fontScale='h2' fontWeight={800} {...props} ref={ref} /> )); export default FormTitle; diff --git a/packages/layout/src/HorizontalWizardLayout/HorizontalWizardLayout.tsx b/packages/layout/src/HorizontalWizardLayout/HorizontalWizardLayout.tsx index 3f638082a6..16e4d98203 100644 --- a/packages/layout/src/HorizontalWizardLayout/HorizontalWizardLayout.tsx +++ b/packages/layout/src/HorizontalWizardLayout/HorizontalWizardLayout.tsx @@ -84,7 +84,7 @@ export const HorizontalWizardLayoutCaption = ({ flexDirection='row' fontScale='c1' color={isDark ? 'white' : 'secondary-info'} - mb='x16' + mb={16} alignItems='center' > {children} diff --git a/packages/layout/src/List/ListItem.tsx b/packages/layout/src/List/ListItem.tsx index 8a7b294fe4..0d63fb340a 100644 --- a/packages/layout/src/List/ListItem.tsx +++ b/packages/layout/src/List/ListItem.tsx @@ -13,7 +13,7 @@ const ListItem = ({ fontScale?: ComponentProps<typeof Box>['fontScale']; }): ReactElement => ( <Box display='flex' is='li' fontScale={fontScale} color='inherit'> - {icon && <Icon name={icon} color={iconColor} size='x16' mie='x4' />} + {icon && <Icon name={icon} color={iconColor} size='x16' mie={4} />} {children} </Box> ); diff --git a/packages/layout/src/ManageWorkspaceFallback.tsx b/packages/layout/src/ManageWorkspaceFallback.tsx index e86362bae8..3cc2f10f9f 100644 --- a/packages/layout/src/ManageWorkspaceFallback.tsx +++ b/packages/layout/src/ManageWorkspaceFallback.tsx @@ -11,7 +11,7 @@ type ManageWorkspaceFallbackProps = { const ManageWorkspaceFallback = ({ onManageWorkspaceClick, }: ManageWorkspaceFallbackProps): ReactElement => ( - <Box fontScale='h4' pb='x40'> + <Box fontScale='h4' pb={40}> <Trans i18nKey='component.manageWorkspaceFallback'> Already have an account? <ActionLink fontScale='h4' onClick={onManageWorkspaceClick}> diff --git a/packages/layout/src/VerticalWizardLayout/VerticalWizardLayout.tsx b/packages/layout/src/VerticalWizardLayout/VerticalWizardLayout.tsx index 878ec5032a..eae603ca22 100644 --- a/packages/layout/src/VerticalWizardLayout/VerticalWizardLayout.tsx +++ b/packages/layout/src/VerticalWizardLayout/VerticalWizardLayout.tsx @@ -15,7 +15,7 @@ export const VerticalWizardLayoutTitle = ({ <Box fontWeight={500} width='100%' - mbe='x18' + mbe={18} fontSize='x42' lineHeight='x62' fontFamily='sans' @@ -44,7 +44,7 @@ export const VerticalWizardLayoutFooter = ({ fontScale='p2' flexDirection='column' justifyContent='flex-end' - pb='x32' + pb={32} > {children} </Box> @@ -70,7 +70,7 @@ export const VerticalWizardLayout = ({ pb={32} pi={16} > - <Box mb='x12'> + <Box mb={12}> <LayoutLogo /> </Box> {children} diff --git a/packages/layout/tsconfig-cjs.json b/packages/layout/tsconfig-cjs.json index b80edf7f45..0b4e6ec415 100644 --- a/packages/layout/tsconfig-cjs.json +++ b/packages/layout/tsconfig-cjs.json @@ -1,8 +1,10 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "module": "commonjs", + "module": "CommonJS", + "rootDir": "./src", "outDir": "./dist/cjs" }, + "include": ["./src"], "exclude": ["**/*.spec.ts"] } diff --git a/packages/layout/tsconfig-esm.json b/packages/layout/tsconfig-esm.json index 8c0f74bb02..d6567ecc56 100644 --- a/packages/layout/tsconfig-esm.json +++ b/packages/layout/tsconfig-esm.json @@ -1,4 +1,10 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "rootDir": "./src", + "outDir": "./dist/esm" + }, + "include": ["./src"], "exclude": ["**/*.spec.ts"] } diff --git a/packages/layout/tsconfig.json b/packages/layout/tsconfig.json index b5e9a72566..0bfabe5210 100644 --- a/packages/layout/tsconfig.json +++ b/packages/layout/tsconfig.json @@ -1,19 +1,18 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "es5", - "module": "ESNext", - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "./dist/esm", - "strict": true, + "rootDirs": ["./src", "./.storybook"], + "target": "ES5", + "module": "CommonJS", + "outDir": "./dist", "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "resolveJsonModule": true, "jsx": "react-jsx" }, + "include": ["./src", "./.storybook/**/*"], + "exclude": ["./dist"], "typedocOptions": { "entryPoints": ["src/index.ts"], "out": "../../static/layout" diff --git a/packages/logo/jest.config.js b/packages/logo/jest.config.js index 98a8568669..2857b4d157 100644 --- a/packages/logo/jest.config.js +++ b/packages/logo/jest.config.js @@ -3,12 +3,4 @@ module.exports = { errorOnDeprecated: true, testMatch: ['<rootDir>/src/**/*.spec.ts?(x)'], testEnvironment: 'jsdom', - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - }, - }, - }, }; diff --git a/packages/logo/package.json b/packages/logo/package.json index 1c70a6fa91..5c4ea1ee4d 100644 --- a/packages/logo/package.json +++ b/packages/logo/package.json @@ -46,28 +46,27 @@ "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/fuselage-tokens": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@types/jest": "~27.4.1", - "@types/react": "~17.0.53", - "@types/react-dom": "^17.0.18", + "@types/jest": "~29.5.0", + "@types/react": "~17.0.57", + "@types/react-dom": "^17.0.19", "build-logo": "workspace:~", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "react": "^17.0.2", "react-dom": "^17.0.2", "rimraf": "^3.0.2", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "dependencies": { "@rocket.chat/fuselage-hooks": "workspace:~", - "@rocket.chat/styled": "workspace:~", - "tslib": "^2.3.1" + "@rocket.chat/styled": "workspace:~" }, "peerDependencies": { "react": "17.0.2", diff --git a/packages/logo/tsconfig.json b/packages/logo/tsconfig.json index 29fa137f6f..0c9ff42708 100644 --- a/packages/logo/tsconfig.json +++ b/packages/logo/tsconfig.json @@ -13,7 +13,6 @@ "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "resolveJsonModule": true, - "jsx": "react-jsx", - "importsNotUsedAsValues": "error" + "jsx": "react-jsx" } } diff --git a/packages/memo/package.json b/packages/memo/package.json index 4be82c88d7..de210cce5b 100644 --- a/packages/memo/package.json +++ b/packages/memo/package.json @@ -43,17 +43,17 @@ "devDependencies": { "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@types/jest": "~27.4.1", + "@types/jest": "~29.5.0", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", - "prettier": "~2.7.1", + "lint-staged": "~13.2.1", + "prettier": "~2.8.7", "rimraf": "~3.0.2", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "eslintConfig": { "extends": "@rocket.chat/eslint-config-alt/typescript", @@ -67,14 +67,6 @@ "errorOnDeprecated": true, "testMatch": [ "<rootDir>/src/**/*.spec.ts" - ], - "globals": { - "ts-jest": { - "tsconfig": { - "noUnusedLocals": false, - "noUnusedParameters": false - } - } - } + ] } } diff --git a/packages/memo/tsconfig.json b/packages/memo/tsconfig.json index ee3f8a4b68..9d0094d8a9 100644 --- a/packages/memo/tsconfig.json +++ b/packages/memo/tsconfig.json @@ -11,8 +11,7 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "node", - "resolveJsonModule": true, - "importsNotUsedAsValues": "error" + "resolveJsonModule": true }, "include": ["src/**/*"], "typedocOptions": { diff --git a/packages/message-parser/loaders/pegtransform.js b/packages/message-parser/loaders/pegtransform.js index c2fe75e5c7..9df49c61df 100644 --- a/packages/message-parser/loaders/pegtransform.js +++ b/packages/message-parser/loaders/pegtransform.js @@ -1,9 +1,10 @@ const pegjs = require('peggy'); module.exports = { - process: (content) => - pegjs.generate(content, { + process: (content) => ({ + code: pegjs.generate(content, { output: 'source', format: 'commonjs', }), + }), }; diff --git a/packages/message-parser/package.json b/packages/message-parser/package.json index 01bead84d7..1dfbb06c67 100644 --- a/packages/message-parser/package.json +++ b/packages/message-parser/package.json @@ -51,34 +51,34 @@ "bump-next": "bump-next" }, "devDependencies": { - "@babel/core": "~7.19.6", - "@babel/eslint-parser": "~7.19.1", - "@babel/preset-env": "~7.19.4", + "@babel/core": "~7.21.4", + "@babel/eslint-parser": "~7.21.3", + "@babel/preset-env": "~7.21.4", "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/peggy-loader": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@types/jest": "~27.4.1", - "@types/node": "~15.14.9", - "@typescript-eslint/parser": "~5.11.0", - "babel-loader": "~8.2.5", + "@types/jest": "~29.5.0", + "@types/node": "~14.18.42", + "@typescript-eslint/parser": "~5.58.0", + "babel-loader": "~9.1.2", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", "peggy": "3.0.2", - "prettier": "~2.7.1", - "prettier-plugin-pegjs": "~0.5.3", + "prettier": "~2.8.7", + "prettier-plugin-pegjs": "~0.5.4", "rimraf": "^3.0.2", - "ts-jest": "~27.1.5", + "ts-jest": "~29.1.0", "ts-loader": "~9.4.2", - "typedoc": "~0.22.18", - "typescript": "~4.9.4", - "webpack": "~5.76.0", - "webpack-cli": "~4.10.0" + "typedoc": "~0.24.1", + "typescript": "~5.0.4", + "webpack": "~5.78.0", + "webpack-cli": "~5.0.1" }, "dependencies": { - "tldts": "~5.7.104" + "tldts": "~5.7.112" } } diff --git a/packages/message-parser/src/definitions.ts b/packages/message-parser/src/definitions.ts index 22a8c565d8..1c4d154d48 100644 --- a/packages/message-parser/src/definitions.ts +++ b/packages/message-parser/src/definitions.ts @@ -88,17 +88,23 @@ export type MarkupExcluding<T extends Markup> = Exclude<Markup, T>; export type Bold = { type: 'BOLD'; - value: Array<MarkupExcluding<Bold> | Link>; + value: Array< + MarkupExcluding<Bold> | Link | Emoji | UserMention | ChannelMention + >; }; export type Italic = { type: 'ITALIC'; - value: Array<MarkupExcluding<Italic> | Link>; + value: Array< + MarkupExcluding<Italic> | Link | Emoji | UserMention | ChannelMention + >; }; export type Strike = { type: 'STRIKE'; - value: Array<MarkupExcluding<Strike> | Link>; + value: Array< + MarkupExcluding<Strike> | Link | Emoji | UserMention | ChannelMention + >; }; export type Plain = { diff --git a/packages/message-parser/src/grammar.pegjs b/packages/message-parser/src/grammar.pegjs index 9fba857977..fcec813fd5 100644 --- a/packages/message-parser/src/grammar.pegjs +++ b/packages/message-parser/src/grammar.pegjs @@ -207,6 +207,7 @@ InlineItem = Whitespace / AutolinkedPhone / AutolinkedEmail / AutolinkedURL + / EmphasisWithWhitespace / Emphasis / UserMention / ChannelMention @@ -315,7 +316,9 @@ Email = "mailto:"? @$(LocalPart "@" DomainName) LocalPart = $(LocalPartChar+ ("." LocalPartChar+)*) -LocalPartChar = AlphaNumericOrMarkChar / [!#$%&'*+/=?^_\`{|}~-] +LocalPartChar = AlphaNumericOrMarkChar+ LocalPartSpecialChars* + +LocalPartSpecialChars = [!#$%&'*+/=?^_\`{|}~-] AutolinkedEmail = e:Email { return autoEmail(e); } @@ -326,7 +329,7 @@ AutolinkedEmail = e:Email { return autoEmail(e); } * with customDomains options as intranet: protocol://internaltool.intranet * */ -AutolinkedURL = u:AutoLinkURL { return autoLink(u); } +AutolinkedURL = u:AutoLinkURL { return autoLink(u, options.customDomains); } AutoLinkURL = $(URLScheme URLAuthority AutoLinkURLBody*) @@ -365,23 +368,28 @@ ItalicContent = text:ItalicContentItems { return italic(text); } ItalicContentItems = text:ItalicContentItem+ { return reducePlainTexts(text); } ItalicContentItem - = References + = Whitespace + / References + / UserMention + / ChannelMention / Bold / Strikethrough - / Line + / Emoji + / Emoticon / AnyItalic + / Line /* Bold */ Bold = [\x2A] [\x2A] @BoldContent [\x2A] [\x2A] / [\x2A] @BoldContent [\x2A] BoldContent = text:BoldContentItem+ { return bold(reducePlainTexts(text)); } -BoldContentItem = References / Italic / Strikethrough / Line / AnyBold +BoldContentItem = Whitespace / References / UserMention / ChannelMention / Italic / Strikethrough / Emoji / Emoticon / AnyBold / Line /* Strike */ Strikethrough = [\x7E] [\x7E] @StrikethroughContent [\x7E] [\x7E] / [\x7E] @StrikethroughContent [\x7E] -StrikethroughContent = text:(References / Italic / Bold / Line / AnyStrike)+ { +StrikethroughContent = text:(Whitespace / References / UserMention / ChannelMention / Italic / Bold / Emoji / Emoticon / AnyStrike / Line)+ { return strike(reducePlainTexts(text)); } @@ -391,6 +399,31 @@ AnyStrike = t:[^\x0a\~ ] { return plain(t); } AnyItalic = t:[^\x0a\_ ] { return plain(t); } +/** + * Emphasis with only whitespaces return plain text + * e.g: __ __, _ _, ** **, * *, ** *, ~~ ~~ +*/ +EmphasisWithWhitespace = AsteriskWithWhitespace / UnderscoreWithWhitespace / TildeWithWhitespace + +AsteriskWithWhitespace = first:Asterisk second:Whitespace third:Asterisk +{ + return reducePlainTexts([first,second,third])[0]; +} + +UnderscoreWithWhitespace = first:Underscore second:Whitespace third:Underscore +{ + return reducePlainTexts([first,second,third])[0]; +} + +TildeWithWhitespace = first:Tilde second:Whitespace third:Tilde +{ + return reducePlainTexts([first,second,third])[0]; +} + +Asterisk = t:"*"+ {return plain(t.join(""))} +Underscore = t:"_"+ {return plain(t.join(""))} +Tilde = t:"~"+ {return plain(t.join(""))} + /** * * Mentions diff --git a/packages/message-parser/src/index.ts b/packages/message-parser/src/index.ts index 852a31473f..cbdae1382c 100644 --- a/packages/message-parser/src/index.ts +++ b/packages/message-parser/src/index.ts @@ -12,6 +12,7 @@ export type Options = { dollarSyntax?: boolean; parenthesisSyntax?: boolean; }; + customDomains?: string[]; }; export const parse = (input: string, options?: Options): Root => diff --git a/packages/message-parser/src/utils.ts b/packages/message-parser/src/utils.ts index 44f9bdc07c..df4f8e6b92 100644 --- a/packages/message-parser/src/utils.ts +++ b/packages/message-parser/src/utils.ts @@ -84,14 +84,17 @@ export const link = (src: string, label?: Markup[]): Link => ({ value: { src: plain(src), label: label ?? [plain(src)] }, }); -export const autoLink = (src: string) => { +export const autoLink = (src: string, customDomains?: string[]) => { + const validHosts = ['localhost', ...(customDomains ?? [])]; const { isIcann, isIp, isPrivate, domain } = tldParse(src, { detectIp: false, allowPrivateDomains: true, - validHosts: ['localhost'], + validHosts, }); - if (!(isIcann || isIp || isPrivate || domain === 'localhost')) { + if ( + !(isIcann || isIp || isPrivate || (domain && validHosts.includes(domain))) + ) { return plain(src); } diff --git a/packages/message-parser/tests/emphasis.test.ts b/packages/message-parser/tests/emphasis.test.ts index de5b8f491f..13afd16127 100644 --- a/packages/message-parser/tests/emphasis.test.ts +++ b/packages/message-parser/tests/emphasis.test.ts @@ -8,9 +8,48 @@ import { emoji, link, bigEmoji, + emojiUnicode, + mentionChannel, + mentionUser, } from '../src/utils'; test.each([ + ['_:smile:_', [paragraph([italic([emoji('smile')])])]], + ['_:slight_smile:_', [paragraph([italic([emoji('slight_smile')])])]], + [ + '_test :smile: test_', + [paragraph([italic([plain('test '), emoji('smile'), plain(' test')])])], + ], + [ + '_test :slight_smile: test_', + [ + paragraph([ + italic([plain('test '), emoji('slight_smile'), plain(' test')]), + ]), + ], + ], + ['_😀_', [paragraph([italic([emojiUnicode('😀')])])]], + ['_test 😀_', [paragraph([italic([plain('test '), emojiUnicode('😀')])])]], + [ + '_test @guilherme.gazzo test_', + [ + paragraph([ + italic([ + plain('test '), + mentionUser('guilherme.gazzo'), + plain(' test'), + ]), + ]), + ], + ], + [ + '_test #GENERAL test_', + [ + paragraph([ + italic([plain('test '), mentionChannel('GENERAL'), plain(' test')]), + ]), + ], + ], [ '_[A brand new Gist](https://gist.github.com/24dddfa97bef58f46ac2ce0f80c58ba4)_', [ diff --git a/packages/message-parser/tests/inlineCode.test.ts b/packages/message-parser/tests/inlineCode.test.ts index 3b2766e042..72d86adcff 100644 --- a/packages/message-parser/tests/inlineCode.test.ts +++ b/packages/message-parser/tests/inlineCode.test.ts @@ -17,6 +17,11 @@ test.each([ ]), ], ], + ['`@rocket.chat`', [paragraph([inlineCode(plain('@rocket.chat'))])]], + [ + '`@rocket.chat/message-parser`', + [paragraph([inlineCode(plain('@rocket.chat/message-parser'))])], + ], ])('parses %p', (input, output) => { expect(parse(input)).toMatchObject(output); }); diff --git a/packages/message-parser/tests/strikethrough.test.ts b/packages/message-parser/tests/strikethrough.test.ts index 6f2b49cd5f..36b3439168 100644 --- a/packages/message-parser/tests/strikethrough.test.ts +++ b/packages/message-parser/tests/strikethrough.test.ts @@ -1,7 +1,36 @@ import { parse } from '../src'; -import { link, paragraph, plain, strike } from '../src/utils'; +import { + emoji, + emojiUnicode, + link, + mentionChannel, + mentionUser, + paragraph, + plain, + strike, +} from '../src/utils'; test.each([ + ['~:smile:~', [paragraph([strike([emoji('smile')])])]], + [ + '~test :smile: test~', + [paragraph([strike([plain('test '), emoji('smile'), plain(' test')])])], + ], + ['~😀~', [paragraph([strike([emojiUnicode('😀')])])]], + ['~test 😀~', [paragraph([strike([plain('test '), emojiUnicode('😀')])])]], + [ + '~@guilherme.gazzo~', + [paragraph([strike([mentionUser('guilherme.gazzo')])])], + ], + ['~#GENERAL~', [paragraph([strike([mentionChannel('GENERAL')])])]], + [ + '~test @guilherme.gazzo~', + [paragraph([strike([plain('test '), mentionUser('guilherme.gazzo')])])], + ], + [ + '~test #GENERAL~', + [paragraph([strike([plain('test '), mentionChannel('GENERAL')])])], + ], [ '~~[A brand new Gist](https://gist.github.com/24dddfa97bef58f46ac2ce0f80c58ba4)~~', [ diff --git a/packages/message-parser/tests/strongEmphasis.test.ts b/packages/message-parser/tests/strongEmphasis.test.ts index 025ff2ebff..8384030aa1 100644 --- a/packages/message-parser/tests/strongEmphasis.test.ts +++ b/packages/message-parser/tests/strongEmphasis.test.ts @@ -1,7 +1,35 @@ import { parse } from '../src'; -import { bold, link, paragraph, plain, italic, strike } from '../src/utils'; +import { + bold, + link, + paragraph, + plain, + italic, + strike, + emoji, + emojiUnicode, + mentionChannel, + mentionUser, +} from '../src/utils'; test.each([ + ['*:smile:*', [paragraph([bold([emoji('smile')])])]], + [ + '*test :smile: test*', + [paragraph([bold([plain('test '), emoji('smile'), plain(' test')])])], + ], + ['*😀*', [paragraph([bold([emojiUnicode('😀')])])]], + ['*test 😀*', [paragraph([bold([plain('test '), emojiUnicode('😀')])])]], + ['*@guilherme.gazzo*', [paragraph([bold([mentionUser('guilherme.gazzo')])])]], + ['*#GENERAL*', [paragraph([bold([mentionChannel('GENERAL')])])]], + [ + '*test @guilherme.gazzo*', + [paragraph([bold([plain('test '), mentionUser('guilherme.gazzo')])])], + ], + [ + '*test #GENERAL*', + [paragraph([bold([plain('test '), mentionChannel('GENERAL')])])], + ], [ '*[A brand new Gist](https://gist.github.com/24dddfa97bef58f46ac2ce0f80c58ba4)*', [ diff --git a/packages/message-parser/tests/url.test.ts b/packages/message-parser/tests/url.test.ts index 6833f62fe9..0c7c03d110 100644 --- a/packages/message-parser/tests/url.test.ts +++ b/packages/message-parser/tests/url.test.ts @@ -1,12 +1,12 @@ import { parse } from '../src'; -import { lineBreak, link, autoLink, paragraph, plain } from '../src/utils'; +import { lineBreak, autoLink, paragraph, plain, link } from '../src/utils'; test.each([ [ 'https://pt.wikipedia.org/wiki/Condi%C3%A7%C3%A3o_de_corrida#:~:text=Uma%20condi%C3%A7%C3%A3o%20de%20corrida%20%C3%A9,sequ%C3%AAncia%20ou%20sincronia%20doutros%20eventos', [ paragraph([ - link( + autoLink( 'https://pt.wikipedia.org/wiki/Condi%C3%A7%C3%A3o_de_corrida#:~:text=Uma%20condi%C3%A7%C3%A3o%20de%20corrida%20%C3%A9,sequ%C3%AAncia%20ou%20sincronia%20doutros%20eventos' ), ]), @@ -14,21 +14,21 @@ test.each([ ], [ 'https://pt.wikipedia.org/', - [paragraph([link('https://pt.wikipedia.org/')])], + [paragraph([autoLink('https://pt.wikipedia.org/')])], ], [ 'https://pt.wikipedia.org/with-hyphen', - [paragraph([link('https://pt.wikipedia.org/with-hyphen')])], + [paragraph([autoLink('https://pt.wikipedia.org/with-hyphen')])], ], [ 'https://pt.wikipedia.org/with_underscore', - [paragraph([link('https://pt.wikipedia.org/with_underscore')])], + [paragraph([autoLink('https://pt.wikipedia.org/with_underscore')])], ], [ 'https://www.npmjs.com/package/@rocket.chat/message-parser', [ paragraph([ - link('https://www.npmjs.com/package/@rocket.chat/message-parser'), + autoLink('https://www.npmjs.com/package/@rocket.chat/message-parser'), ]), ], ], @@ -37,50 +37,59 @@ test.each([ ['https://test', [paragraph([plain('https://test')])]], [ 'httpsss://rocket.chat/test', - [paragraph([link('httpsss://rocket.chat/test')])], + [paragraph([autoLink('httpsss://rocket.chat/test')])], ], [ 'https://rocket.chat:3000/test', - [paragraph([link('https://rocket.chat:3000/test')])], + [paragraph([autoLink('https://rocket.chat:3000/test')])], ], [ 'https://rocket.chat/test?search', - [paragraph([link('https://rocket.chat/test?search')])], + [paragraph([autoLink('https://rocket.chat/test?search')])], ], [ 'https://rocket.chat/test?search=test', - [paragraph([link('https://rocket.chat/test?search=test')])], + [paragraph([autoLink('https://rocket.chat/test?search=test')])], ], - ['https://rocket.chat', [paragraph([link('https://rocket.chat')])]], - ['https://localhost', [paragraph([link('https://localhost')])]], - ['https://localhost:3000', [paragraph([link('https://localhost:3000')])]], + ['https://rocket.chat', [paragraph([autoLink('https://rocket.chat')])]], + ['https://localhost', [paragraph([autoLink('https://localhost')])]], + ['https://localhost:3000', [paragraph([autoLink('https://localhost:3000')])]], [ 'https://localhost:3000#fragment', - [paragraph([link('https://localhost:3000#fragment')])], + [paragraph([autoLink('https://localhost:3000#fragment')])], + ], + [ + 'https://localhost:3000#', + [paragraph([autoLink('https://localhost:3000#')])], + ], + [ + 'https://localhost:3000?', + [paragraph([autoLink('https://localhost:3000?')])], + ], + [ + 'https://localhost:3000/', + [paragraph([autoLink('https://localhost:3000/')])], ], - ['https://localhost:3000#', [paragraph([link('https://localhost:3000#')])]], - ['https://localhost:3000?', [paragraph([link('https://localhost:3000?')])]], - ['https://localhost:3000/', [paragraph([link('https://localhost:3000/')])]], [ 'ftp://user:pass@localhost:21/etc/hosts', - [paragraph([link('ftp://user:pass@localhost:21/etc/hosts')])], + [paragraph([autoLink('ftp://user:pass@localhost:21/etc/hosts')])], ], - ['ssh://test@example.com', [paragraph([link('ssh://test@example.com')])]], + ['ssh://test@example.com', [paragraph([autoLink('ssh://test@example.com')])]], [ 'custom://test@example.com', - [paragraph([link('custom://test@example.com')])], + [paragraph([autoLink('custom://test@example.com')])], ], - ['ftp://example.com', [paragraph([link('ftp://example.com')])]], + ['ftp://example.com', [paragraph([autoLink('ftp://example.com')])]], [ 'https://www.thingiverse.com/thing:5451684', - [paragraph([link('https://www.thingiverse.com/thing:5451684')])], + [paragraph([autoLink('https://www.thingiverse.com/thing:5451684')])], ], - ['http://📙.la/❤️', [paragraph([link('http://📙.la/❤️')])]], + ['http://📙.la/❤️', [paragraph([autoLink('http://📙.la/❤️')])]], [ 'https://developer.rocket.chat/reference/api/rest-api#production-security-concerns look at this', [ paragraph([ - link( + autoLink( 'https://developer.rocket.chat/reference/api/rest-api#production-security-concerns' ), plain(' look at this'), @@ -91,7 +100,7 @@ test.each([ 'https://developer.rocket.chat/reference/api/rest-api look at this', [ paragraph([ - link('https://developer.rocket.chat/reference/api/rest-api'), + autoLink('https://developer.rocket.chat/reference/api/rest-api'), plain(' look at this'), ]), ], @@ -101,7 +110,7 @@ test.each([ 'https://developer.rocket.chat/reference/api/rest-api#fragment?query=query look at this', [ paragraph([ - link( + autoLink( 'https://developer.rocket.chat/reference/api/rest-api#fragment?query=query' ), plain(' look at this'), @@ -112,7 +121,7 @@ test.each([ 'https://developer.rocket.chat look at this', [ paragraph([ - link('https://developer.rocket.chat'), + autoLink('https://developer.rocket.chat'), plain(' look at this'), ]), ], @@ -121,7 +130,7 @@ test.each([ 'https://developer.rocket.chat?query=query look at this', [ paragraph([ - link('https://developer.rocket.chat?query=query'), + autoLink('https://developer.rocket.chat?query=query'), plain(' look at this'), ]), ], @@ -129,14 +138,14 @@ test.each([ [ 'https://developer.rocket.chat?query=query\nline break', [ - paragraph([link('https://developer.rocket.chat?query=query')]), + paragraph([autoLink('https://developer.rocket.chat?query=query')]), paragraph([plain('line break')]), ], ], [ 'https://developer.rocket.chat?query=query\n\nline break', [ - paragraph([link('https://developer.rocket.chat?query=query')]), + paragraph([autoLink('https://developer.rocket.chat?query=query')]), lineBreak(), paragraph([plain('line break')]), ], @@ -145,7 +154,7 @@ test.each([ 'https://developer.rocket.chat?query=query_with_underscore look at this', [ paragraph([ - link('https://developer.rocket.chat?query=query_with_underscore'), + autoLink('https://developer.rocket.chat?query=query_with_underscore'), plain(' look at this'), ]), ], @@ -154,7 +163,7 @@ test.each([ 'https://developer.rocket.chat/path_with_underscore look at this', [ paragraph([ - link('https://developer.rocket.chat/path_with_underscore'), + autoLink('https://developer.rocket.chat/path_with_underscore'), plain(' look at this'), ]), ], @@ -163,7 +172,7 @@ test.each([ 'https://developer.rocket.chat#fragment_with_underscore look at this', [ paragraph([ - link('https://developer.rocket.chat#fragment_with_underscore'), + autoLink('https://developer.rocket.chat#fragment_with_underscore'), plain(' look at this'), ]), ], @@ -172,7 +181,7 @@ test.each([ 'https://developer.rocket.chat followed by text', [ paragraph([ - link('https://developer.rocket.chat'), + autoLink('https://developer.rocket.chat'), plain(' followed by text'), ]), ], @@ -182,59 +191,44 @@ test.each([ [ paragraph([ plain('two urls '), - link('https://developer.rocket.chat'), + autoLink('https://developer.rocket.chat'), plain(' , '), - link('https://rocket.chat'), + autoLink('https://rocket.chat'), ]), ], ], [ 'https://1developer.rocket.chat', - [paragraph([link('https://1developer.rocket.chat')])], + [paragraph([autoLink('https://1developer.rocket.chat')])], ], [ 'https://en.m.wikipedia.org/wiki/Main_Page', - [paragraph([link('https://en.m.wikipedia.org/wiki/Main_Page')])], + [paragraph([autoLink('https://en.m.wikipedia.org/wiki/Main_Page')])], ], ['test.1test.com', [paragraph([autoLink('test.1test.com')])]], - ['http://test.e-xample.com', [paragraph([link('http://test.e-xample.com')])]], - ['www.n-tv.de', [paragraph([link('//www.n-tv.de', [plain('www.n-tv.de')])])]], + [ + 'http://test.e-xample.com', + [paragraph([autoLink('http://test.e-xample.com')])], + ], + ['www.n-tv.de', [paragraph([autoLink('www.n-tv.de')])]], [ 'www.n-tv.de/test, test', - [ - paragraph([ - link('//www.n-tv.de/test', [plain('www.n-tv.de/test')]), - plain(', test'), - ]), - ], + [paragraph([autoLink('www.n-tv.de/test'), plain(', test')])], ], [ 'www.n-tv.de/, test', - [ - paragraph([ - link('//www.n-tv.de/', [plain('www.n-tv.de/')]), - plain(', test'), - ]), - ], + [paragraph([autoLink('www.n-tv.de/'), plain(', test')])], ], [ 'www.n-tv.de, test', - [ - paragraph([ - link('//www.n-tv.de', [plain('www.n-tv.de')]), - plain(', test'), - ]), - ], + [paragraph([autoLink('www.n-tv.de'), plain(', test')])], ], [ 'https://www.n-tv.de, test', - [paragraph([link('https://www.n-tv.de'), plain(', test')])], - ], - ['http://te_st.com', [paragraph([link('http://te_st.com')])]], - [ - 'www.te_st.com', - [paragraph([link('//www.te_st.com', [plain('www.te_st.com')])])], + [paragraph([autoLink('https://www.n-tv.de'), plain(', test')])], ], + ['http://te_st.com', [paragraph([autoLink('http://te_st.com')])]], + ['www.te_st.com', [paragraph([autoLink('www.te_st.com')])]], [ '[google_search](http://google.com)', [paragraph([link('http://google.com', [plain('google_search')])])], @@ -244,7 +238,7 @@ test.each([ [ paragraph([ plain('app...https://rocket.chat '), - link('https://rocket.chat'), + autoLink('https://rocket.chat'), ]), ], ], @@ -253,7 +247,7 @@ test.each([ [ paragraph([ plain('Hey check it out the best communication platform '), - link('https://rocket.chat'), + autoLink('https://rocket.chat'), plain('! There is not discussion about it.'), ]), ], @@ -266,7 +260,7 @@ test.each([ 'https://github.com/RocketChat/Rocket.Chat/releases/tag/6.0.0-rc.3', [ paragraph([ - link( + autoLink( 'https://github.com/RocketChat/Rocket.Chat/releases/tag/6.0.0-rc.3' ), ]), @@ -294,6 +288,35 @@ test.each([ expect(parse(input)).toMatchObject(output); }); +describe('autoLink with custom hosts settings comming from Rocket.Chat', () => { + test.each([ + [ + 'http://gitlab.local', + [paragraph([autoLink('http://gitlab.local', ['local'])])], + ], + ['gitlab.local', [paragraph([autoLink('gitlab.local', ['local'])])]], + [ + 'internaltool.intranet', + [paragraph([autoLink('internaltool.intranet', ['local', 'intranet'])])], + ], + ])('parses %p', (input, output) => { + expect( + parse(input, { customDomains: ['local', 'intranet'] }) + ).toMatchObject(output); + }); +}); + +describe('autoLink WITHOUT custom hosts settings comming from Rocket.Chat', () => { + test.each([ + [ + 'https://internaltool.testt', + [paragraph([plain('https://internaltool.testt')])], + ], + ])('parses %p', (input, output) => { + expect(parse(input, { customDomains: ['local'] })).toMatchObject(output); + }); +}); + describe('autoLink helper function', () => { it('should preserve the original protocol if the protocol is http or https', () => { expect(autoLink('https://rocket.chat/test')).toMatchObject( diff --git a/packages/message-parser/tsconfig.json b/packages/message-parser/tsconfig.json index eb0340933b..3e59aeae89 100644 --- a/packages/message-parser/tsconfig.json +++ b/packages/message-parser/tsconfig.json @@ -13,7 +13,6 @@ "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "noImplicitThis": true, - "alwaysStrict": true, - "importsNotUsedAsValues": "error" + "alwaysStrict": true } } diff --git a/packages/mp3-encoder/.eslintignore b/packages/mp3-encoder/.eslintignore deleted file mode 100644 index 8225baa4a7..0000000000 --- a/packages/mp3-encoder/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -/node_modules -/dist diff --git a/packages/mp3-encoder/.eslintrc.js b/packages/mp3-encoder/.eslintrc.js index d11c05a965..fb82ca10b0 100644 --- a/packages/mp3-encoder/.eslintrc.js +++ b/packages/mp3-encoder/.eslintrc.js @@ -1,6 +1,11 @@ +/** @type {import('eslint').ESLint.ConfigData} */ module.exports = { extends: '@rocket.chat/eslint-config-alt/typescript', env: { jest: true, }, + rules: { + 'new-cap': 'off', + }, + ignorePatterns: ['dist', 'node_modules', 'src/lame/*.js'], }; diff --git a/packages/mp3-encoder/jest.config.js b/packages/mp3-encoder/jest.config.js index 0dbb3c664a..0d581323df 100644 --- a/packages/mp3-encoder/jest.config.js +++ b/packages/mp3-encoder/jest.config.js @@ -3,12 +3,4 @@ module.exports = { errorOnDeprecated: true, testMatch: ['<rootDir>/src/**/*.spec.ts'], testEnvironment: 'jsdom', - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - }, - }, - }, }; diff --git a/packages/mp3-encoder/package.json b/packages/mp3-encoder/package.json index cedfd32ab7..67aebd1963 100644 --- a/packages/mp3-encoder/package.json +++ b/packages/mp3-encoder/package.json @@ -40,29 +40,27 @@ "publishConfig": { "access": "public" }, - "dependencies": { - "lamejs": "git+https://github.com/zhuker/lamejs.git#commit=582bbba6a12f981b984d8fb9e1874499fed85675" - }, "devDependencies": { - "@babel/core": "~7.19.6", - "@babel/plugin-transform-runtime": "~7.19.6", - "@babel/preset-env": "~7.19.4", - "@babel/preset-typescript": "^7.18.6", + "@babel/core": "~7.21.4", + "@babel/plugin-transform-runtime": "~7.21.4", + "@babel/preset-env": "~7.21.4", + "@babel/preset-typescript": "~7.21.4", "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@rollup/plugin-commonjs": "~21.0.3", - "@rollup/plugin-node-resolve": "~13.1.3", - "@rollup/plugin-typescript": "~8.3.4", - "@types/jest": "~27.4.1", + "@rollup/plugin-commonjs": "~24.1.0", + "@rollup/plugin-node-resolve": "~15.0.2", + "@rollup/plugin-typescript": "~11.1.0", + "@types/jest": "~29.5.0", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", + "jest-environment-jsdom": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", - "prettier": "~2.7.1", - "rollup": "~2.67.3", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "lint-staged": "~13.2.1", + "prettier": "~2.8.7", + "rollup": "~3.20.4", + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" } } diff --git a/packages/mp3-encoder/rollup.config.js b/packages/mp3-encoder/rollup.config.js index a7c6000f0c..bc0eb72e04 100644 --- a/packages/mp3-encoder/rollup.config.js +++ b/packages/mp3-encoder/rollup.config.js @@ -1,12 +1,13 @@ -import path from 'path'; +const path = require('path'); -import commonjs from '@rollup/plugin-commonjs'; -import resolve from '@rollup/plugin-node-resolve'; -import typescript from '@rollup/plugin-typescript'; +const commonjs = require('@rollup/plugin-commonjs'); +const resolve = require('@rollup/plugin-node-resolve'); +const typescript = require('@rollup/plugin-typescript').default; -import pkg from './package.json'; +const pkg = require('./package.json'); -export default [ +/** @type {import('rollup').RollupOptions[]} */ +module.exports = [ { input: 'src/index.ts', output: { @@ -17,7 +18,9 @@ export default [ strict: false, }, plugins: [ - typescript({ declaration: true, declarationDir: 'dist/' }), + typescript({ + tsconfig: 'tsconfig.build.json', + }), resolve(), commonjs(), ], @@ -29,6 +32,12 @@ export default [ format: 'esm', sourcemap: true, }, - plugins: [typescript(), resolve(), commonjs()], + plugins: [ + typescript({ + tsconfig: 'tsconfig.build.json', + }), + resolve(), + commonjs(), + ], }, ]; diff --git a/packages/mp3-encoder/src/index.ts b/packages/mp3-encoder/src/index.ts index 156b19afb4..0e382e200f 100644 --- a/packages/mp3-encoder/src/index.ts +++ b/packages/mp3-encoder/src/index.ts @@ -1,6 +1,4 @@ -import { Mp3Encoder } from 'lamejs'; - -declare const self: any; +import { Mp3Encoder } from './lame'; type Config = { numChannels?: number; diff --git a/packages/mp3-encoder/src/lame/ABRPresets.ts b/packages/mp3-encoder/src/lame/ABRPresets.ts new file mode 100644 index 0000000000..593371d429 --- /dev/null +++ b/packages/mp3-encoder/src/lame/ABRPresets.ts @@ -0,0 +1,436 @@ +import type { LameGlobalFlags } from './LameGlobalFlags'; +import { VbrMode } from './VbrMode'; +import type { Bitrate } from './bitrates'; +import { equals } from './math'; + +interface ABRPreset { + readonly kbps: number; + readonly quant_comp: number; + readonly quant_comp_s: number; + readonly safejoint: number; + readonly nsmsfix: number; + readonly st_lrm: number; + readonly st_s: number; + readonly nsbass: number; + readonly scale: number; + readonly masking_adj: number; + readonly ath_lower: number; + readonly ath_curve: number; + readonly interch: number; + readonly sfscale: 0 | 1; +} + +interface BandPass { + readonly bitrate: number; + readonly lowpass: number; +} + +type FullBitrateIndex = number & { __brand: 'FullBitrateIndex' }; + +type PresetMap = Record<FullBitrateIndex, ABRPreset>; + +export class ABRPresets { + private readonly fullBitrates = [ + 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, + ] as const satisfies readonly Bitrate[]; + + private findNearestFullBitrateIndex(bitrate: number): FullBitrateIndex { + let lowerRange = this.fullBitrates.length - 1; + let upperRange = this.fullBitrates.length - 1; + + for ( + let maybeLowerRange = 0; + maybeLowerRange < this.fullBitrates.length - 1; + maybeLowerRange++ + ) { + const maybeUpperRange = maybeLowerRange + 1; + + if (this.fullBitrates[maybeUpperRange] > bitrate) { + lowerRange = maybeLowerRange; + upperRange = maybeUpperRange; + break; + } + } + + return bitrate - this.fullBitrates[lowerRange] < + this.fullBitrates[upperRange] - bitrate + ? (lowerRange as FullBitrateIndex) + : (upperRange as FullBitrateIndex); + } + + private readonly presetMap = [ + { + kbps: 8, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 0, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: -30, + ath_curve: 11, + interch: 0.0012, + sfscale: 1, + }, + { + kbps: 16, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 0, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: -25, + ath_curve: 11, + interch: 0.001, + sfscale: 1, + }, + { + kbps: 24, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 0, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: -20, + ath_curve: 11, + interch: 0.001, + sfscale: 1, + }, + { + kbps: 32, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 0, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: -15, + ath_curve: 11, + interch: 0.001, + sfscale: 1, + }, + { + kbps: 40, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 0, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: -10, + ath_curve: 11, + interch: 0.0009, + sfscale: 1, + }, + { + kbps: 48, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 0, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: -10, + ath_curve: 11, + interch: 0.0009, + sfscale: 1, + }, + { + kbps: 56, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 0, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: -6, + ath_curve: 11, + interch: 0.0008, + sfscale: 1, + }, + { + kbps: 64, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 0, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: -2, + ath_curve: 11, + interch: 0.0008, + sfscale: 1, + }, + { + kbps: 80, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 0, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: 0, + ath_curve: 8, + interch: 0.0007, + sfscale: 1, + }, + { + kbps: 96, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 2.5, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: 1, + ath_curve: 5.5, + interch: 0.0006, + sfscale: 1, + }, + { + kbps: 112, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 2.25, + st_lrm: 6.6, + st_s: 145, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: 2, + ath_curve: 4.5, + interch: 0.0005, + sfscale: 1, + }, + { + kbps: 128, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 0, + nsmsfix: 1.95, + st_lrm: 6.4, + st_s: 140, + nsbass: 0, + scale: 0.95, + masking_adj: 0, + ath_lower: 3, + ath_curve: 4, + interch: 0.0002, + sfscale: 1, + }, + { + kbps: 160, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 1, + nsmsfix: 1.79, + st_lrm: 6, + st_s: 135, + nsbass: 0, + scale: 0.95, + masking_adj: -2, + ath_lower: 5, + ath_curve: 3.5, + interch: 0, + sfscale: 1, + }, + { + kbps: 192, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 1, + nsmsfix: 1.49, + st_lrm: 5.6, + st_s: 125, + nsbass: 0, + scale: 0.97, + masking_adj: -4, + ath_lower: 7, + ath_curve: 3, + interch: 0, + sfscale: 0, + }, + { + kbps: 224, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 1, + nsmsfix: 1.25, + st_lrm: 5.2, + st_s: 125, + nsbass: 0, + scale: 0.98, + masking_adj: -6, + ath_lower: 9, + ath_curve: 2, + interch: 0, + sfscale: 0, + }, + { + kbps: 256, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 1, + nsmsfix: 0.97, + st_lrm: 5.2, + st_s: 125, + nsbass: 0, + scale: 1, + masking_adj: -8, + ath_lower: 10, + ath_curve: 1, + interch: 0, + sfscale: 0, + }, + { + kbps: 320, + quant_comp: 9, + quant_comp_s: 9, + safejoint: 1, + nsmsfix: 0.9, + st_lrm: 5.2, + st_s: 125, + nsbass: 0, + scale: 1, + masking_adj: -10, + ath_lower: 12, + ath_curve: 0, + interch: 0, + sfscale: 0, + }, + ] as const satisfies PresetMap; + + public apply(gfp: LameGlobalFlags, bitrate: number) { + const preset = this.presetMap[this.findNearestFullBitrateIndex(bitrate)]; + + gfp.VBR = VbrMode.vbr_abr; + gfp.VBR_mean_bitrate_kbps = bitrate; + gfp.VBR_mean_bitrate_kbps = Math.min(gfp.VBR_mean_bitrate_kbps, 320); + gfp.VBR_mean_bitrate_kbps = Math.max(gfp.VBR_mean_bitrate_kbps, 8); + gfp.brate = gfp.VBR_mean_bitrate_kbps; + + if (preset.safejoint > 0) { + gfp.exp_nspsytune |= 2; + } + + if (preset.sfscale > 0) { + gfp.internal_flags.noise_shaping = 2; + } + + if (Math.abs(preset.nsbass) > 0) { + let k = Math.trunc(preset.nsbass * 4); + if (k < 0) { + k += 64; + } + gfp.exp_nspsytune |= k << 2; + } + + if (equals(gfp.quant_comp, -1)) { + gfp.quant_comp = preset.quant_comp; + } + + if (equals(gfp.quant_comp_short, -1)) { + gfp.quant_comp_short = preset.quant_comp_s; + } + + if (equals(gfp.msfix, -1)) { + gfp.msfix = preset.nsmsfix; + } + + if (equals(gfp.internal_flags.nsPsy.attackthre, -1)) { + gfp.internal_flags.nsPsy.attackthre = preset.st_lrm; + } + + if (equals(gfp.internal_flags.nsPsy.attackthre_s, -1)) { + gfp.internal_flags.nsPsy.attackthre_s = preset.st_s; + } + + if (equals(gfp.scale, -1)) { + gfp.scale = preset.scale; + } + + if (equals(gfp.maskingadjust, 0)) { + gfp.maskingadjust = preset.masking_adj; + } + + if (preset.masking_adj > 0) { + if (equals(gfp.maskingadjust_short, 0)) { + gfp.maskingadjust_short = preset.masking_adj * 0.9; + } + } else if (equals(gfp.maskingadjust_short, 0)) { + gfp.maskingadjust_short = preset.masking_adj * 1.1; + } + + if (equals(-gfp.ATHlower * 10, 0)) { + gfp.ATHlower = -preset.ath_lower / 10; + } + + if (equals(gfp.ATHcurve, -1)) { + gfp.ATHcurve = preset.ath_curve; + } + + if (equals(gfp.interChRatio, -1)) { + gfp.interChRatio = preset.interch; + } + + return bitrate; + } + + private readonly optimumBandwidths = [ + { bitrate: 8, lowpass: 2000 }, + { bitrate: 16, lowpass: 3700 }, + { bitrate: 24, lowpass: 3900 }, + { bitrate: 32, lowpass: 5500 }, + { bitrate: 40, lowpass: 7000 }, + { bitrate: 48, lowpass: 7500 }, + { bitrate: 56, lowpass: 10000 }, + { bitrate: 64, lowpass: 11000 }, + { bitrate: 80, lowpass: 13500 }, + { bitrate: 96, lowpass: 15100 }, + { bitrate: 112, lowpass: 15600 }, + { bitrate: 128, lowpass: 17000 }, + { bitrate: 160, lowpass: 17500 }, + { bitrate: 192, lowpass: 18600 }, + { bitrate: 224, lowpass: 19400 }, + { bitrate: 256, lowpass: 19700 }, + { bitrate: 320, lowpass: 20500 }, + ] as const satisfies Record<FullBitrateIndex, BandPass>; + + public getOptimumBandwidth(bitrate: number) { + const table_index = this.findNearestFullBitrateIndex(bitrate); + return this.optimumBandwidths[table_index].lowpass; + } +} diff --git a/packages/mp3-encoder/src/lame/ATH.ts b/packages/mp3-encoder/src/lame/ATH.ts new file mode 100644 index 0000000000..222fbb54a0 --- /dev/null +++ b/packages/mp3-encoder/src/lame/ATH.ts @@ -0,0 +1,29 @@ +import { BLKSIZE, CBANDS, PSFB12, PSFB21, SBMAX_l, SBMAX_s } from './constants'; + +export class ATH { + useAdjust = 0; + + aaSensitivityP = 0; + + adjust = 0; + + adjustLimit = 0; + + decay = 0; + + floor = 0; + + readonly l = new Float32Array(SBMAX_l); + + readonly s = new Float32Array(SBMAX_s); + + readonly psfb21 = new Float32Array(PSFB21); + + readonly psfb12 = new Float32Array(PSFB12); + + readonly cb_l = new Float32Array(CBANDS); + + readonly cb_s = new Float32Array(CBANDS); + + readonly eql_w = new Float32Array(BLKSIZE / 2); +} diff --git a/packages/mp3-encoder/src/lame/BitStream.ts b/packages/mp3-encoder/src/lame/BitStream.ts new file mode 100644 index 0000000000..b0e7034709 --- /dev/null +++ b/packages/mp3-encoder/src/lame/BitStream.ts @@ -0,0 +1,766 @@ +import { GainAnalysis } from './GainAnalysis'; +import type { GrInfo } from './GrInfo'; +import type { LameGlobalFlags } from './LameGlobalFlags'; +import type { LameInternalFlags } from './LameInternalFlags'; +import * as tables from './Tables'; +import { Takehiro } from './Takehiro'; +import { TotalBytes } from './TotalBytes'; +import { VBRTag } from './VBRTag'; +import { copyArray, fillArray } from './arrays'; +import { assert } from './assert'; +import { getBitrate } from './bitrates'; +import { + LAME_MAXMP3BUFFER, + MAX_HEADER_BUF, + NORM_TYPE, + SHORT_TYPE, +} from './constants'; +import { getLameShortVersion } from './getLameShortVersion'; +import { isCloseToEachOther } from './math'; + +export class BitStream { + private static readonly MAX_LENGTH = 32; + + readonly ga = new GainAnalysis(); + + private readonly vbr = new VBRTag(); + + private readonly buf = new Uint8Array(LAME_MAXMP3BUFFER); + + private totbit = 0; + + private bufByteIdx = -1; + + private bufBitIdx = 0; + + resetPointers(gfc: LameInternalFlags) { + gfc.w_ptr = 0; + gfc.h_ptr = 0; + gfc.header[gfc.h_ptr].write_timing = 0; + } + + getframebits(gfp: LameGlobalFlags) { + const gfc = gfp.internal_flags; + let bit_rate; + + if (gfc.bitrate_index !== 0) { + bit_rate = getBitrate(gfp.version, gfc.bitrate_index); + } else { + bit_rate = gfp.brate; + } + assert(bit_rate >= 8 && bit_rate <= 640); + + const bytes = Math.trunc( + ((gfp.version + 1) * 72000 * bit_rate) / gfp.out_samplerate + gfc.padding + ); + return 8 * bytes; + } + + private putheader_bits(gfc: LameInternalFlags) { + copyArray( + gfc.header[gfc.w_ptr].buf, + 0, + this.buf, + this.bufByteIdx, + gfc.sideinfo_len + ); + this.bufByteIdx += gfc.sideinfo_len; + this.totbit += gfc.sideinfo_len * 8; + gfc.w_ptr = (gfc.w_ptr + 1) & (MAX_HEADER_BUF - 1); + } + + private putbits2(gfc: LameInternalFlags, val: number, j: number) { + assert(j < BitStream.MAX_LENGTH - 2); + + while (j > 0) { + if (this.bufBitIdx === 0) { + this.bufBitIdx = 8; + this.bufByteIdx++; + assert(this.bufByteIdx < LAME_MAXMP3BUFFER); + assert(gfc.header[gfc.w_ptr].write_timing >= this.totbit); + if (gfc.header[gfc.w_ptr].write_timing === this.totbit) { + this.putheader_bits(gfc); + } + this.buf[this.bufByteIdx] = 0; + } + + const k = Math.min(j, this.bufBitIdx); + j -= k; + + this.bufBitIdx -= k; + + assert(j < BitStream.MAX_LENGTH); + + assert(this.bufBitIdx < BitStream.MAX_LENGTH); + + this.buf[this.bufByteIdx] |= (val >> j) << this.bufBitIdx; + this.totbit += k; + } + } + + private drain_into_ancillary(gfp: LameGlobalFlags, remainingBits: number) { + const gfc = gfp.internal_flags; + let i; + assert(remainingBits >= 0); + + if (remainingBits >= 8) { + this.putbits2(gfc, 0x4c, 8); + remainingBits -= 8; + } + if (remainingBits >= 8) { + this.putbits2(gfc, 0x41, 8); + remainingBits -= 8; + } + if (remainingBits >= 8) { + this.putbits2(gfc, 0x4d, 8); + remainingBits -= 8; + } + if (remainingBits >= 8) { + this.putbits2(gfc, 0x45, 8); + remainingBits -= 8; + } + + if (remainingBits >= 32) { + const version = getLameShortVersion(); + if (remainingBits >= 32) + for (i = 0; i < version.length && remainingBits >= 8; ++i) { + remainingBits -= 8; + this.putbits2(gfc, version.charCodeAt(i), 8); + } + } + + for (; remainingBits >= 1; remainingBits -= 1) { + this.putbits2(gfc, 0, 1); + } + + assert(remainingBits === 0); + } + + private writeheader(gfc: LameInternalFlags, val: number, j: number) { + let { ptr } = gfc.header[gfc.h_ptr]; + + while (j > 0) { + const k = Math.min(j, 8 - (ptr & 7)); + j -= k; + assert(j < BitStream.MAX_LENGTH); + + gfc.header[gfc.h_ptr].buf[ptr >> 3] |= (val >> j) << (8 - (ptr & 7) - k); + ptr += k; + } + gfc.header[gfc.h_ptr].ptr = ptr; + } + + private encodeSideInfo2(gfp: LameGlobalFlags, bitsPerFrame: number) { + const gfc = gfp.internal_flags; + let gr; + let ch; + + const { l3_side } = gfc; + gfc.header[gfc.h_ptr].ptr = 0; + fillArray(gfc.header[gfc.h_ptr].buf, 0, gfc.sideinfo_len, 0); + if (gfp.out_samplerate < 16000) { + this.writeheader(gfc, 0xffe, 12); + } else { + this.writeheader(gfc, 0xfff, 12); + } + this.writeheader(gfc, gfp.version, 1); + this.writeheader(gfc, 4 - 3, 2); + this.writeheader(gfc, 1, 1); + this.writeheader(gfc, gfc.bitrate_index, 4); + this.writeheader(gfc, gfc.samplerate_index, 2); + this.writeheader(gfc, gfc.padding, 1); + this.writeheader(gfc, 0, 1); + this.writeheader(gfc, gfp.mode.ordinal, 2); + this.writeheader(gfc, gfc.mode_ext, 2); + this.writeheader(gfc, 0, 1); + this.writeheader(gfc, 1, 1); + this.writeheader(gfc, 0, 2); + + if (gfp.version === 1) { + assert(l3_side.main_data_begin >= 0); + this.writeheader(gfc, l3_side.main_data_begin, 9); + + if (gfc.channels_out === 2) + this.writeheader(gfc, l3_side.private_bits, 3); + else this.writeheader(gfc, l3_side.private_bits, 5); + + for (ch = 0; ch < gfc.channels_out; ch++) { + for (let band = 0; band < 4; band++) { + this.writeheader(gfc, l3_side.scfsi[ch][band], 1); + } + } + + for (gr = 0; gr < 2; gr++) { + for (ch = 0; ch < gfc.channels_out; ch++) { + const gi = l3_side.tt[gr][ch]; + this.writeheader(gfc, gi.part2_3_length + gi.part2_length, 12); + this.writeheader(gfc, gi.big_values / 2, 9); + this.writeheader(gfc, gi.global_gain, 8); + this.writeheader(gfc, gi.scalefac_compress, 4); + + if (gi.block_type !== NORM_TYPE) { + this.writeheader(gfc, 1, 1); + + this.writeheader(gfc, gi.block_type, 2); + this.writeheader(gfc, gi.mixed_block_flag, 1); + + if (gi.table_select[0] === 14) gi.table_select[0] = 16; + this.writeheader(gfc, gi.table_select[0], 5); + if (gi.table_select[1] === 14) gi.table_select[1] = 16; + this.writeheader(gfc, gi.table_select[1], 5); + + this.writeheader(gfc, gi.subblock_gain[0], 3); + this.writeheader(gfc, gi.subblock_gain[1], 3); + this.writeheader(gfc, gi.subblock_gain[2], 3); + } else { + this.writeheader(gfc, 0, 1); + + if (gi.table_select[0] === 14) gi.table_select[0] = 16; + this.writeheader(gfc, gi.table_select[0], 5); + if (gi.table_select[1] === 14) gi.table_select[1] = 16; + this.writeheader(gfc, gi.table_select[1], 5); + if (gi.table_select[2] === 14) gi.table_select[2] = 16; + this.writeheader(gfc, gi.table_select[2], 5); + + assert(gi.region0_count >= 0 && gi.region0_count < 16); + assert(gi.region1_count >= 0 && gi.region1_count < 8); + this.writeheader(gfc, gi.region0_count, 4); + this.writeheader(gfc, gi.region1_count, 3); + } + this.writeheader(gfc, gi.preflag, 1); + this.writeheader(gfc, gi.scalefac_scale, 1); + this.writeheader(gfc, gi.count1table_select, 1); + } + } + } else { + assert(l3_side.main_data_begin >= 0); + this.writeheader(gfc, l3_side.main_data_begin, 8); + this.writeheader(gfc, l3_side.private_bits, gfc.channels_out); + + gr = 0; + for (ch = 0; ch < gfc.channels_out; ch++) { + const gi = l3_side.tt[gr][ch]; + this.writeheader(gfc, gi.part2_3_length + gi.part2_length, 12); + this.writeheader(gfc, gi.big_values / 2, 9); + this.writeheader(gfc, gi.global_gain, 8); + this.writeheader(gfc, gi.scalefac_compress, 9); + + if (gi.block_type !== NORM_TYPE) { + this.writeheader(gfc, 1, 1); + + this.writeheader(gfc, gi.block_type, 2); + this.writeheader(gfc, gi.mixed_block_flag, 1); + + if (gi.table_select[0] === 14) gi.table_select[0] = 16; + this.writeheader(gfc, gi.table_select[0], 5); + if (gi.table_select[1] === 14) gi.table_select[1] = 16; + this.writeheader(gfc, gi.table_select[1], 5); + + this.writeheader(gfc, gi.subblock_gain[0], 3); + this.writeheader(gfc, gi.subblock_gain[1], 3); + this.writeheader(gfc, gi.subblock_gain[2], 3); + } else { + this.writeheader(gfc, 0, 1); + + if (gi.table_select[0] === 14) gi.table_select[0] = 16; + this.writeheader(gfc, gi.table_select[0], 5); + if (gi.table_select[1] === 14) gi.table_select[1] = 16; + this.writeheader(gfc, gi.table_select[1], 5); + if (gi.table_select[2] === 14) gi.table_select[2] = 16; + this.writeheader(gfc, gi.table_select[2], 5); + + assert(gi.region0_count >= 0 && gi.region0_count < 16); + assert(gi.region1_count >= 0 && gi.region1_count < 8); + this.writeheader(gfc, gi.region0_count, 4); + this.writeheader(gfc, gi.region1_count, 3); + } + + this.writeheader(gfc, gi.scalefac_scale, 1); + this.writeheader(gfc, gi.count1table_select, 1); + } + } + + const old = gfc.h_ptr; + assert(gfc.header[old].ptr === gfc.sideinfo_len * 8); + + gfc.h_ptr = (old + 1) & (MAX_HEADER_BUF - 1); + gfc.header[gfc.h_ptr].write_timing = + gfc.header[old].write_timing + bitsPerFrame; + + if (gfc.h_ptr === gfc.w_ptr) { + console.warn('MAX_HEADER_BUF too small in bitstream.'); + } + } + + private huffman_coder_count1(gfc: LameInternalFlags, gi: GrInfo) { + const h = tables.ht[gi.count1table_select + 32]; + let i; + let bits = 0; + + let ix = gi.big_values; + let xr = gi.big_values; + assert(gi.count1table_select < 2); + + for (i = (gi.count1 - gi.big_values) / 4; i > 0; --i) { + let huffbits = 0; + let p = 0; + let v; + + v = gi.l3_enc[ix + 0]; + if (v !== 0) { + p += 8; + if (gi.xr[xr + 0] < 0) huffbits++; + assert(v <= 1); + } + + v = gi.l3_enc[ix + 1]; + if (v !== 0) { + p += 4; + huffbits *= 2; + if (gi.xr[xr + 1] < 0) huffbits++; + assert(v <= 1); + } + + v = gi.l3_enc[ix + 2]; + if (v !== 0) { + p += 2; + huffbits *= 2; + if (gi.xr[xr + 2] < 0) huffbits++; + assert(v <= 1); + } + + v = gi.l3_enc[ix + 3]; + if (v !== 0) { + p++; + huffbits *= 2; + if (gi.xr[xr + 3] < 0) huffbits++; + assert(v <= 1); + } + + assert(h.table !== undefined); + assert(h.hlen !== undefined); + + ix += 4; + xr += 4; + this.putbits2(gfc, huffbits + h.table[p], h.hlen[p]); + bits += h.hlen[p]; + } + return bits; + } + + private huffmancode( + gfc: LameInternalFlags, + tableindex: number, + start: number, + end: number, + gi: GrInfo + ) { + const h = tables.ht[tableindex]; + let bits = 0; + + assert(tableindex < 32); + if (tableindex === 0) return bits; + + for (let i = start; i < end; i += 2) { + let cbits = 0; + let xbits = 0; + const linbits = h.xlen; + let { xlen } = h; + let ext = 0; + let x1 = gi.l3_enc[i]; + let x2 = gi.l3_enc[i + 1]; + + if (x1 !== 0) { + if (gi.xr[i] < 0) ext++; + cbits--; + } + + if (tableindex > 15) { + if (x1 > 14) { + const linbits_x1 = x1 - 15; + assert(linbits_x1 <= h.linmax); + ext |= linbits_x1 << 1; + xbits = linbits; + x1 = 15; + } + + if (x2 > 14) { + const linbits_x2 = x2 - 15; + assert(linbits_x2 <= h.linmax); + ext <<= linbits; + ext |= linbits_x2; + xbits += linbits; + x2 = 15; + } + xlen = 16; + } + + if (x2 !== 0) { + ext <<= 1; + if (gi.xr[i + 1] < 0) ext++; + cbits--; + } + + assert((x1 | x2) < 16); + assert(h.hlen !== undefined); + assert(h.table !== undefined); + + x1 = x1 * xlen + x2; + xbits -= cbits; + cbits += h.hlen[x1]; + + assert(cbits <= BitStream.MAX_LENGTH); + assert(xbits <= BitStream.MAX_LENGTH); + assert(h.table !== undefined); + + this.putbits2(gfc, h.table[x1], cbits); + this.putbits2(gfc, ext, xbits); + bits += cbits + xbits; + } + return bits; + } + + private shortHuffmancodebits(gfc: LameInternalFlags, gi: GrInfo) { + let region1Start = 3 * gfc.scalefac_band.s[3]; + if (region1Start > gi.big_values) region1Start = gi.big_values; + + let bits = this.huffmancode(gfc, gi.table_select[0], 0, region1Start, gi); + bits += this.huffmancode( + gfc, + gi.table_select[1], + region1Start, + gi.big_values, + gi + ); + return bits; + } + + private longHuffmancodebits(gfc: LameInternalFlags, gi: GrInfo) { + let bits; + let region1Start; + let region2Start; + + const bigvalues = gi.big_values; + assert(bigvalues >= 0 && bigvalues <= 576); + + let i = gi.region0_count + 1; + assert(i >= 0); + assert(i < gfc.scalefac_band.l.length); + region1Start = gfc.scalefac_band.l[i]; + i += gi.region1_count + 1; + assert(i >= 0); + assert(i < gfc.scalefac_band.l.length); + region2Start = gfc.scalefac_band.l[i]; + + if (region1Start > bigvalues) { + region1Start = bigvalues; + } + + if (region2Start > bigvalues) { + region2Start = bigvalues; + } + + bits = this.huffmancode(gfc, gi.table_select[0], 0, region1Start, gi); + bits += this.huffmancode( + gfc, + gi.table_select[1], + region1Start, + region2Start, + gi + ); + bits += this.huffmancode( + gfc, + gi.table_select[2], + region2Start, + bigvalues, + gi + ); + return bits; + } + + private writeMainData(gfp: LameGlobalFlags) { + let gr; + let ch; + let sfb; + let data_bits; + let tot_bits = 0; + const gfc = gfp.internal_flags; + const { l3_side } = gfc; + + if (gfp.version === 1) { + for (gr = 0; gr < 2; gr++) { + for (ch = 0; ch < gfc.channels_out; ch++) { + const gi = l3_side.tt[gr][ch]; + const slen1 = Takehiro.slen1_tab[gi.scalefac_compress]; + const slen2 = Takehiro.slen2_tab[gi.scalefac_compress]; + data_bits = 0; + for (sfb = 0; sfb < gi.sfbdivide; sfb++) { + if (gi.scalefac[sfb] === -1) { + continue; + } + + this.putbits2(gfc, gi.scalefac[sfb], slen1); + data_bits += slen1; + } + for (; sfb < gi.sfbmax; sfb++) { + if (gi.scalefac[sfb] === -1) { + continue; + } + + this.putbits2(gfc, gi.scalefac[sfb], slen2); + data_bits += slen2; + } + assert(data_bits === gi.part2_length); + + if (gi.block_type === SHORT_TYPE) { + data_bits += this.shortHuffmancodebits(gfc, gi); + } else { + data_bits += this.longHuffmancodebits(gfc, gi); + } + data_bits += this.huffman_coder_count1(gfc, gi); + + assert(data_bits === gi.part2_3_length + gi.part2_length); + tot_bits += data_bits; + } + } + return tot_bits; + } + + gr = 0; + for (ch = 0; ch < gfc.channels_out; ch++) { + const gi = l3_side.tt[gr][ch]; + let i; + let sfb_partition; + let scale_bits = 0; + assert(gi.sfb_partition_table !== null); + data_bits = 0; + sfb = 0; + sfb_partition = 0; + + if (gi.block_type === SHORT_TYPE) { + for (; sfb_partition < 4; sfb_partition++) { + const sfbs = gi.sfb_partition_table[sfb_partition] / 3; + const slen = gi.slen[sfb_partition]; + for (i = 0; i < sfbs; i++, sfb++) { + this.putbits2(gfc, Math.max(gi.scalefac[sfb * 3 + 0], 0), slen); + this.putbits2(gfc, Math.max(gi.scalefac[sfb * 3 + 1], 0), slen); + this.putbits2(gfc, Math.max(gi.scalefac[sfb * 3 + 2], 0), slen); + scale_bits += 3 * slen; + } + } + data_bits += this.shortHuffmancodebits(gfc, gi); + } else { + for (; sfb_partition < 4; sfb_partition++) { + const sfbs = gi.sfb_partition_table[sfb_partition]; + const slen = gi.slen[sfb_partition]; + for (i = 0; i < sfbs; i++, sfb++) { + this.putbits2(gfc, Math.max(gi.scalefac[sfb], 0), slen); + scale_bits += slen; + } + } + data_bits += this.longHuffmancodebits(gfc, gi); + } + data_bits += this.huffman_coder_count1(gfc, gi); + + assert(data_bits === gi.part2_3_length); + assert(scale_bits === gi.part2_length); + tot_bits += scale_bits + data_bits; + } + + return tot_bits; + } + + private compute_flushbits( + gfp: LameGlobalFlags, + total_bytes_output: TotalBytes + ) { + const gfc = gfp.internal_flags; + let flushbits; + let remaining_headers; + let last_ptr; + const first_ptr = gfc.w_ptr; + + last_ptr = gfc.h_ptr - 1; + + if (last_ptr === -1) last_ptr = MAX_HEADER_BUF - 1; + + flushbits = gfc.header[last_ptr].write_timing - this.totbit; + total_bytes_output.total = flushbits; + + if (flushbits >= 0) { + remaining_headers = 1 + last_ptr - first_ptr; + if (last_ptr < first_ptr) { + remaining_headers = 1 + last_ptr - first_ptr + MAX_HEADER_BUF; + } + flushbits -= remaining_headers * 8 * gfc.sideinfo_len; + } + + const bitsPerFrame = this.getframebits(gfp); + flushbits += bitsPerFrame; + total_bytes_output.total += bitsPerFrame; + + if (total_bytes_output.total % 8 !== 0) + total_bytes_output.total = 1 + total_bytes_output.total / 8; + else total_bytes_output.total /= 8; + total_bytes_output.total += this.bufByteIdx + 1; + + if (flushbits < 0) { + console.warn('strange error flushing buffer ... '); + } + return flushbits; + } + + flush_bitstream(gfp: LameGlobalFlags) { + const gfc = gfp.internal_flags; + let flushbits; + let last_ptr = gfc.h_ptr - 1; + + if (last_ptr === -1) last_ptr = MAX_HEADER_BUF - 1; + const { l3_side } = gfc; + + if ((flushbits = this.compute_flushbits(gfp, new TotalBytes())) < 0) return; + this.drain_into_ancillary(gfp, flushbits); + + assert( + gfc.header[last_ptr].write_timing + this.getframebits(gfp) === this.totbit + ); + + gfc.ResvSize = 0; + l3_side.main_data_begin = 0; + + if (gfc.findReplayGain) { + const radioGain = this.ga.getTitleGain(gfc.rgdata); + assert( + !isCloseToEachOther(radioGain, GainAnalysis.GAIN_NOT_ENOUGH_SAMPLES) + ); + gfc.RadioGain = Math.floor(radioGain * 10.0 + 0.5); + } + + if (gfc.findPeakSample) { + gfc.noclipGainChange = Math.ceil( + Math.log10(gfc.PeakSample / 32767.0) * 20.0 * 10.0 + ); + + if (gfc.noclipGainChange > 0) { + if ( + isCloseToEachOther(gfp.scale, 1.0) || + isCloseToEachOther(gfp.scale, 0.0) + ) { + gfc.noclipScale = + Math.floor((32767.0 / gfc.PeakSample) * 100.0) / 100.0; + } else { + gfc.noclipScale = -1; + } + } else { + gfc.noclipScale = -1; + } + } + } + + format_bitstream(gfp: LameGlobalFlags) { + const gfc = gfp.internal_flags; + const { l3_side } = gfc; + + const bitsPerFrame = this.getframebits(gfp); + this.drain_into_ancillary(gfp, l3_side.resvDrain_pre); + + this.encodeSideInfo2(gfp, bitsPerFrame); + let bits = 8 * gfc.sideinfo_len; + bits += this.writeMainData(gfp); + this.drain_into_ancillary(gfp, l3_side.resvDrain_post); + bits += l3_side.resvDrain_post; + + l3_side.main_data_begin += (bitsPerFrame - bits) / 8; + + if (this.compute_flushbits(gfp, new TotalBytes()) !== gfc.ResvSize) { + console.warn('Internal buffer inconsistency. flushbits <> ResvSize'); + } + + if (l3_side.main_data_begin * 8 !== gfc.ResvSize) { + console.warn( + `bit reservoir error: \n` + + `l3_side.main_data_begin: ${8 * l3_side.main_data_begin} \n` + + `Resvoir size: ${gfc.ResvSize} \n` + + `resv drain (post) ${l3_side.resvDrain_post} \n` + + `resv drain (pre) ${l3_side.resvDrain_pre} \n` + + `header and sideinfo: ${8 * gfc.sideinfo_len} \n` + + `data bits: ${ + bits - l3_side.resvDrain_post - 8 * gfc.sideinfo_len + } \n` + + `total bits: ${bits} (remainder: ${bits % 8}) \n` + + `bitsperframe: ${bitsPerFrame}` + ); + + console.warn('This is a fatal error. It has several possible causes:'); + console.warn( + '90%% LAME compiled with buggy version of gcc using advanced optimizations' + ); + console.warn(' 9%% Your system is overclocked'); + console.warn(' 1%% bug in LAME encoding library'); + + gfc.ResvSize = l3_side.main_data_begin * 8; + } + assert(this.totbit % 8 === 0); + + if (this.totbit > 1000000000) { + for (let i = 0; i < MAX_HEADER_BUF; ++i) { + gfc.header[i].write_timing -= this.totbit; + } + this.totbit = 0; + } + + return 0; + } + + copyMetadata( + gfc: LameInternalFlags, + buffer: Uint8Array, + bufferPos: number, + size: number + ) { + return this.copyBuffer(gfc, buffer, bufferPos, size, false); + } + + copyFrameData( + gfc: LameInternalFlags, + buffer: Uint8Array, + bufferPos: number, + size: number + ) { + return this.copyBuffer(gfc, buffer, bufferPos, size, true); + } + + private copyBuffer( + gfc: LameInternalFlags, + buffer: Uint8Array, + bufferPos: number, + size: number, + mp3data: boolean + ) { + const minimum = this.bufByteIdx + 1; + if (minimum <= 0) { + return 0; + } + + if (size !== 0 && minimum > size) { + return -1; + } + + copyArray(this.buf, 0, buffer, bufferPos, minimum); + this.bufByteIdx = -1; + this.bufBitIdx = 0; + + if (mp3data) { + const crc = new Int32Array(1); + crc[0] = gfc.nMusicCRC; + this.vbr.updateMusicCRC(crc, buffer, bufferPos, minimum); + gfc.nMusicCRC = crc[0]; + + if (minimum > 0) { + gfc.VBR_seek_table.nBytesWritten += minimum; + } + } + + return minimum; + } +} diff --git a/packages/mp3-encoder/src/lame/Bits.ts b/packages/mp3-encoder/src/lame/Bits.ts new file mode 100644 index 0000000000..0c57d6de33 --- /dev/null +++ b/packages/mp3-encoder/src/lame/Bits.ts @@ -0,0 +1,7 @@ +export class Bits { + public bits: number; + + constructor(bits: number) { + this.bits = Math.trunc(bits); + } +} diff --git a/packages/mp3-encoder/src/lame/CBRNewIterationLoop.ts b/packages/mp3-encoder/src/lame/CBRNewIterationLoop.ts new file mode 100644 index 0000000000..43b26ed15a --- /dev/null +++ b/packages/mp3-encoder/src/lame/CBRNewIterationLoop.ts @@ -0,0 +1,149 @@ +import type { III_psy_ratio } from './III_psy_ratio'; +import type { LameGlobalFlags } from './LameGlobalFlags'; +import { MeanBits } from './MeanBits'; +import type { Quantize } from './Quantize'; +import { assert } from './assert'; +import { + MAX_BITS_PER_CHANNEL, + MAX_BITS_PER_GRANULE, + MPG_MD_MS_LR, + SFBMAX, + SHORT_TYPE, +} from './constants'; + +export class CBRNewIterationLoop { + constructor(public quantize: Quantize) {} + + iteration_loop( + gfp: LameGlobalFlags, + pe: number[][], + ms_ener_ratio: number[], + ratio: III_psy_ratio[][] + ) { + const gfc = gfp.internal_flags; + const l3_xmin = new Float32Array(SFBMAX); + const xrpow = new Float32Array(576); + const targ_bits = new Int32Array(2); + let mean_bits = 0; + let max_bits; + const { l3_side } = gfc; + + const mb = new MeanBits(mean_bits); + this.quantize.rv.ResvFrameBegin(gfp, mb); + mean_bits = mb.bits; + + for (let gr = 0; gr < gfc.mode_gr; gr++) { + max_bits = this.on_pe(gfp, pe, targ_bits, mean_bits, gr, gr); + + if (gfc.mode_ext === MPG_MD_MS_LR) { + this.quantize.ms_convert(gfc.l3_side, gr); + this.quantize.qupvt.reduce_side( + targ_bits, + ms_ener_ratio[gr], + mean_bits, + max_bits + ); + } + + for (let ch = 0; ch < gfc.channels_out; ch++) { + let adjust; + let masking_lower_db; + const cod_info = l3_side.tt[gr][ch]; + + if (cod_info.block_type !== SHORT_TYPE) { + // NORM, START or STOP type + adjust = 0; + masking_lower_db = gfc.PSY.mask_adjust - adjust; + } else { + adjust = 0; + masking_lower_db = gfc.PSY.mask_adjust_short - adjust; + } + gfc.masking_lower = Math.pow(10.0, masking_lower_db * 0.1); + + this.quantize.init_outer_loop(gfc, cod_info); + if (this.quantize.init_xrpow(gfc, cod_info, xrpow)) { + this.quantize.qupvt.calc_xmin(gfp, ratio[gr][ch], cod_info, l3_xmin); + this.quantize.outer_loop( + gfp, + cod_info, + l3_xmin, + xrpow, + ch, + targ_bits[ch] + ); + } + + this.quantize.iteration_finish_one(gfc, gr, ch); + assert(cod_info.part2_3_length <= MAX_BITS_PER_CHANNEL); + assert(cod_info.part2_3_length <= targ_bits[ch]); + } + } + + this.quantize.rv.ResvFrameEnd(gfc, mean_bits); + } + + private on_pe( + gfp: LameGlobalFlags, + pe: number[][], + targ_bits: Int32Array, + mean_bits: number, + gr: number, + cbr: number + ) { + const gfc = gfp.internal_flags; + let tbits = 0; + let bits; + const add_bits = new Int32Array(2); + let ch; + + const mb = new MeanBits(tbits); + let extra_bits = this.quantize.rv.ResvMaxBits(gfp, mean_bits, mb, cbr); + tbits = mb.bits; + + let max_bits = tbits + extra_bits; + if (max_bits > MAX_BITS_PER_GRANULE) { + max_bits = MAX_BITS_PER_GRANULE; + } + for (bits = 0, ch = 0; ch < gfc.channels_out; ++ch) { + targ_bits[ch] = Math.min(MAX_BITS_PER_CHANNEL, tbits / gfc.channels_out); + + add_bits[ch] = Math.trunc( + (targ_bits[ch] * pe[gr][ch]) / 700.0 - targ_bits[ch] + ); + + if (add_bits[ch] > (mean_bits * 3) / 4) + add_bits[ch] = (mean_bits * 3) / 4; + if (add_bits[ch] < 0) add_bits[ch] = 0; + + if (add_bits[ch] + targ_bits[ch] > MAX_BITS_PER_CHANNEL) + add_bits[ch] = Math.max(0, MAX_BITS_PER_CHANNEL - targ_bits[ch]); + + bits += add_bits[ch]; + } + if (bits > extra_bits) { + for (ch = 0; ch < gfc.channels_out; ++ch) { + add_bits[ch] = (extra_bits * add_bits[ch]) / bits; + } + } + + for (ch = 0; ch < gfc.channels_out; ++ch) { + targ_bits[ch] += add_bits[ch]; + extra_bits -= add_bits[ch]; + } + + for (bits = 0, ch = 0; ch < gfc.channels_out; ++ch) { + bits += targ_bits[ch]; + } + if (bits > MAX_BITS_PER_GRANULE) { + let sum = 0; + for (ch = 0; ch < gfc.channels_out; ++ch) { + targ_bits[ch] *= MAX_BITS_PER_GRANULE; + targ_bits[ch] /= bits; + sum += targ_bits[ch]; + } + assert(sum <= MAX_BITS_PER_GRANULE); + } + + return max_bits; + } +} diff --git a/packages/mp3-encoder/src/lame/CalcNoiseData.ts b/packages/mp3-encoder/src/lame/CalcNoiseData.ts new file mode 100644 index 0000000000..74ac52df22 --- /dev/null +++ b/packages/mp3-encoder/src/lame/CalcNoiseData.ts @@ -0,0 +1,11 @@ +export class CalcNoiseData { + global_gain = 0; + + sfb_count1 = 0; + + step = new Int32Array(39); + + noise = new Float32Array(39); + + noise_log = new Float32Array(39); +} diff --git a/packages/mp3-encoder/src/lame/CalcNoiseResult.ts b/packages/mp3-encoder/src/lame/CalcNoiseResult.ts new file mode 100644 index 0000000000..508efac7ad --- /dev/null +++ b/packages/mp3-encoder/src/lame/CalcNoiseResult.ts @@ -0,0 +1,13 @@ +export class CalcNoiseResult { + over_noise = 0; + + tot_noise = 0; + + max_noise = 0; + + over_count = 0; + + over_SSD = 0; + + bits = 0; +} diff --git a/packages/mp3-encoder/src/lame/Encoder.ts b/packages/mp3-encoder/src/lame/Encoder.ts new file mode 100644 index 0000000000..9c461ef1fe --- /dev/null +++ b/packages/mp3-encoder/src/lame/Encoder.ts @@ -0,0 +1,351 @@ +import type { BitStream } from './BitStream'; +import { III_psy_ratio } from './III_psy_ratio'; +import type { LameGlobalFlags } from './LameGlobalFlags'; +import type { LameInternalFlags } from './LameInternalFlags'; +import { MPEGMode } from './MPEGMode'; +import { NewMDCT } from './NewMDCT'; +import type { PsyModel } from './PsyModel'; +import { VbrMode } from './VbrMode'; +import { assert } from './assert'; +import { + BLKSIZE, + FFTOFFSET, + MPG_MD_LR_LR, + MPG_MD_MS_LR, + NORM_TYPE, + SHORT_TYPE, +} from './constants'; + +export class Encoder { + private readonly bs: BitStream; + + private readonly psy: PsyModel; + + constructor(bs: BitStream, psy: PsyModel) { + this.bs = bs; + this.psy = psy; + } + + private newMDCT = new NewMDCT(); + + private adjust_ATH(gfc: LameInternalFlags) { + if (gfc.ATH.useAdjust === 0) { + gfc.ATH.adjust = 1.0; + + return; + } + + let max_pow = gfc.loudness_sq[0][0]; + let gr2_max = gfc.loudness_sq[1][0]; + if (gfc.channels_out === 2) { + max_pow += gfc.loudness_sq[0][1]; + gr2_max += gfc.loudness_sq[1][1]; + } else { + max_pow += max_pow; + gr2_max += gr2_max; + } + if (gfc.mode_gr === 2) { + max_pow = Math.max(max_pow, gr2_max); + } + max_pow *= 0.5; + + max_pow *= gfc.ATH.aaSensitivityP; + + if (max_pow > 0.03125) { + if (gfc.ATH.adjust >= 1.0) { + gfc.ATH.adjust = 1.0; + } else if (gfc.ATH.adjust < gfc.ATH.adjustLimit) { + gfc.ATH.adjust = gfc.ATH.adjustLimit; + } + + gfc.ATH.adjustLimit = 1.0; + } else { + const adj_lim_new = 31.98 * max_pow + 0.000625; + if (gfc.ATH.adjust >= adj_lim_new) { + gfc.ATH.adjust *= adj_lim_new * 0.075 + 0.925; + if (gfc.ATH.adjust < adj_lim_new) { + gfc.ATH.adjust = adj_lim_new; + } + } else if (gfc.ATH.adjustLimit >= adj_lim_new) { + gfc.ATH.adjust = adj_lim_new; + } else if (gfc.ATH.adjust < gfc.ATH.adjustLimit) { + gfc.ATH.adjust = gfc.ATH.adjustLimit; + } + + gfc.ATH.adjustLimit = adj_lim_new; + } + } + + private updateStats(gfc: LameInternalFlags) { + let gr; + let ch; + assert(gfc.bitrate_index >= 0 && gfc.bitrate_index < 16); + assert(gfc.mode_ext >= 0 && gfc.mode_ext < 4); + + gfc.bitrate_stereoMode_Hist[gfc.bitrate_index][4]++; + gfc.bitrate_stereoMode_Hist[15][4]++; + + if (gfc.channels_out === 2) { + gfc.bitrate_stereoMode_Hist[gfc.bitrate_index][gfc.mode_ext]++; + gfc.bitrate_stereoMode_Hist[15][gfc.mode_ext]++; + } + for (gr = 0; gr < gfc.mode_gr; ++gr) { + for (ch = 0; ch < gfc.channels_out; ++ch) { + let bt = Math.trunc(gfc.l3_side.tt[gr][ch].block_type); + if (gfc.l3_side.tt[gr][ch].mixed_block_flag !== 0) bt = 4; + gfc.bitrate_blockType_Hist[gfc.bitrate_index][bt]++; + gfc.bitrate_blockType_Hist[gfc.bitrate_index][5]++; + gfc.bitrate_blockType_Hist[15][bt]++; + gfc.bitrate_blockType_Hist[15][5]++; + } + } + } + + private lame_encode_frame_init( + gfp: LameGlobalFlags, + inbuf: [Float32Array, Float32Array] + ) { + const gfc = gfp.internal_flags; + + let ch; + let gr; + + if (!gfc.lame_encode_frame_init) { + let i; + let j; + const primebuff0 = new Float32Array(286 + 1152 + 576); + const primebuff1 = new Float32Array(286 + 1152 + 576); + gfc.lame_encode_frame_init = true; + for (i = 0, j = 0; i < 286 + 576 * (1 + gfc.mode_gr); ++i) { + if (i < 576 * gfc.mode_gr) { + primebuff0[i] = 0; + if (gfc.channels_out === 2) primebuff1[i] = 0; + } else { + primebuff0[i] = inbuf[0][j]; + if (gfc.channels_out === 2) primebuff1[i] = inbuf[1][j]; + ++j; + } + } + + for (gr = 0; gr < gfc.mode_gr; gr++) { + for (ch = 0; ch < gfc.channels_out; ch++) { + gfc.l3_side.tt[gr][ch].block_type = SHORT_TYPE; + } + } + this.newMDCT.mdct_sub48(gfc, primebuff0, primebuff1); + + assert(FFTOFFSET <= 576); + + assert(gfc.mf_size >= BLKSIZE + gfp.framesize - FFTOFFSET); + + assert(gfc.mf_size >= 512 + gfp.framesize - 32); + } + } + + // eslint-disable-next-line complexity + lame_encode_mp3_frame( + gfp: LameGlobalFlags, + inbuf_l: Float32Array, + inbuf_r: Float32Array, + mp3buf: Uint8Array, + mp3bufPos: number, + mp3buf_size: number + ) { + const masking_LR = Array.from({ length: 2 }, () => + Array.from({ length: 2 }, () => new III_psy_ratio()) + ); + + const masking_MS = Array.from({ length: 2 }, () => + Array.from({ length: 2 }, () => new III_psy_ratio()) + ); + + masking_MS[0][0] = new III_psy_ratio(); + masking_MS[0][1] = new III_psy_ratio(); + masking_MS[1][0] = new III_psy_ratio(); + masking_MS[1][1] = new III_psy_ratio(); + + let masking; + + const gfc = gfp.internal_flags; + + const tot_ener = Array.from({ length: 2 }, () => new Float32Array(4)); + const ms_ener_ratio = [0.5, 0.5]; + const pe = [ + [0, 0], + [0, 0], + ]; + const pe_MS = [ + [0, 0], + [0, 0], + ]; + + let pe_use; + + let ch; + let gr; + + const inbuf: [Float32Array, Float32Array] = [inbuf_l, inbuf_r]; + + if (!gfc.lame_encode_frame_init) { + this.lame_encode_frame_init(gfp, inbuf); + } + + gfc.padding = 0; + if ((gfc.slot_lag -= gfc.frac_SpF) < 0) { + gfc.slot_lag += gfp.out_samplerate; + gfc.padding = 1; + } + + if (gfc.psymodel !== 0) { + let ret; + + let bufpPos = 0; + + const blocktype = new Int32Array(2); + + const bufp: Float32Array[] = []; + + for (gr = 0; gr < gfc.mode_gr; gr++) { + for (ch = 0; ch < gfc.channels_out; ch++) { + bufp[ch] = inbuf[ch]; + bufpPos = 576 + gr * 576 - FFTOFFSET; + } + if (gfp.VBR === VbrMode.vbr_mtrh || gfp.VBR === VbrMode.vbr_mt) { + ret = this.psy.L3psycho_anal_vbr( + gfp, + bufp, + bufpPos, + gr, + masking_LR, + masking_MS, + pe[gr], + pe_MS[gr], + tot_ener[gr], + blocktype + ); + } else { + ret = this.psy.L3psycho_anal_ns( + gfp, + bufp, + bufpPos, + gr, + masking_LR, + masking_MS, + pe[gr], + pe_MS[gr], + tot_ener[gr], + blocktype + ); + } + if (ret !== 0) return -4; + + if (gfp.mode === MPEGMode.JOINT_STEREO) { + ms_ener_ratio[gr] = tot_ener[gr][2] + tot_ener[gr][3]; + if (ms_ener_ratio[gr] > 0) + ms_ener_ratio[gr] = tot_ener[gr][3] / ms_ener_ratio[gr]; + } + + for (ch = 0; ch < gfc.channels_out; ch++) { + const cod_info = gfc.l3_side.tt[gr][ch]; + cod_info.block_type = blocktype[ch]; + cod_info.mixed_block_flag = 0; + } + } + } else { + for (gr = 0; gr < gfc.mode_gr; gr++) + for (ch = 0; ch < gfc.channels_out; ch++) { + gfc.l3_side.tt[gr][ch].block_type = NORM_TYPE; + gfc.l3_side.tt[gr][ch].mixed_block_flag = 0; + pe_MS[gr][ch] = 700; + pe[gr][ch] = 700; + } + } + + this.adjust_ATH(gfc); + + this.newMDCT.mdct_sub48(gfc, inbuf[0], inbuf[1]); + + gfc.mode_ext = MPG_MD_LR_LR; + + if (gfp.mode === MPEGMode.JOINT_STEREO) { + let sum_pe_MS = 0; + let sum_pe_LR = 0; + for (gr = 0; gr < gfc.mode_gr; gr++) { + for (ch = 0; ch < gfc.channels_out; ch++) { + sum_pe_MS += pe_MS[gr][ch]; + sum_pe_LR += pe[gr][ch]; + } + } + + if (sum_pe_MS <= 1.0 * sum_pe_LR) { + const gi0 = gfc.l3_side.tt[0]; + const gi1 = gfc.l3_side.tt[gfc.mode_gr - 1]; + + if ( + gi0[0].block_type === gi0[1].block_type && + gi1[0].block_type === gi1[1].block_type + ) { + gfc.mode_ext = MPG_MD_MS_LR; + } + } + } + + if (gfc.mode_ext === MPG_MD_MS_LR) { + masking = masking_MS; + + pe_use = pe_MS; + } else { + masking = masking_LR; + + pe_use = pe; + } + + if (gfp.VBR === VbrMode.vbr_off || gfp.VBR === VbrMode.vbr_abr) { + let i; + let f; + + for (i = 0; i < 18; i++) + gfc.nsPsy.pefirbuf[i] = gfc.nsPsy.pefirbuf[i + 1]; + + f = 0.0; + for (gr = 0; gr < gfc.mode_gr; gr++) + for (ch = 0; ch < gfc.channels_out; ch++) f += pe_use[gr][ch]; + gfc.nsPsy.pefirbuf[18] = f; + + f = gfc.nsPsy.pefirbuf[9]; + for (i = 0; i < 9; i++) + f += + (gfc.nsPsy.pefirbuf[i] + gfc.nsPsy.pefirbuf[18 - i]) * + Encoder.fircoef[i]; + + f = (670 * 5 * gfc.mode_gr * gfc.channels_out) / f; + for (gr = 0; gr < gfc.mode_gr; gr++) { + for (ch = 0; ch < gfc.channels_out; ch++) { + pe_use[gr][ch] *= f; + } + } + } + assert(gfc.iteration_loop !== null); + gfc.iteration_loop.iteration_loop(gfp, pe_use, ms_ener_ratio, masking); + + this.bs.format_bitstream(gfp); + + const mp3count = this.bs.copyFrameData(gfc, mp3buf, mp3bufPos, mp3buf_size); + + this.updateStats(gfc); + + return mp3count; + } + + static fircoef = [ + -0.0207887 * 5, + -0.0378413 * 5, + -0.0432472 * 5, + -0.031183 * 5, + 7.79609e-18 * 5, + 0.0467745 * 5, + 0.10091 * 5, + 0.151365 * 5, + 0.187098 * 5, + ] as const; +} diff --git a/packages/mp3-encoder/src/lame/FFT.ts b/packages/mp3-encoder/src/lame/FFT.ts new file mode 100644 index 0000000000..6f15d36ae1 --- /dev/null +++ b/packages/mp3-encoder/src/lame/FFT.ts @@ -0,0 +1,235 @@ +import type { LameInternalFlags } from './LameInternalFlags'; +import { BLKSIZE, BLKSIZE_s } from './constants'; + +export class FFT { + private window: Float32Array = new Float32Array(BLKSIZE); + + private window_s: Float32Array = new Float32Array(BLKSIZE_s / 2); + + private readonly costab = [ + 9.238795325112867e-1, 3.826834323650898e-1, 9.951847266721969e-1, + 9.80171403295606e-2, 9.996988186962042e-1, 2.454122852291229e-2, + 9.999811752826011e-1, 6.135884649154475e-3, + ] as const; + + private fht(fz: Float32Array, fzPos: number, n: number) { + let tri = 0; + let k4: number; + let fi: number; + let gi: number; + + n <<= 1; + + const fn = fzPos + n; + k4 = 4; + do { + let s1; + let c1; + let i; + const kx = k4 >> 1; + const k1 = k4; + const k2 = k4 << 1; + const k3 = k2 + k1; + k4 = k2 << 1; + fi = fzPos; + gi = fi + kx; + do { + let f0; + let f1; + let f2; + let f3; + f1 = fz[fi + 0] - fz[fi + k1]; + f0 = fz[fi + 0] + fz[fi + k1]; + f3 = fz[fi + k2] - fz[fi + k3]; + f2 = fz[fi + k2] + fz[fi + k3]; + fz[fi + k2] = f0 - f2; + fz[fi + 0] = f0 + f2; + fz[fi + k3] = f1 - f3; + fz[fi + k1] = f1 + f3; + f1 = fz[gi + 0] - fz[gi + k1]; + f0 = fz[gi + 0] + fz[gi + k1]; + f3 = Math.SQRT2 * fz[gi + k3]; + f2 = Math.SQRT2 * fz[gi + k2]; + fz[gi + k2] = f0 - f2; + fz[gi + 0] = f0 + f2; + fz[gi + k3] = f1 - f3; + fz[gi + k1] = f1 + f3; + gi += k4; + fi += k4; + } while (fi < fn); + c1 = this.costab[tri + 0]; + s1 = this.costab[tri + 1]; + for (i = 1; i < kx; i++) { + let c2: number = 1 - 2 * s1 * s1; + const s2 = 2 * s1 * c1; + fi = fzPos + i; + gi = fzPos + k1 - i; + do { + let a: number; + let b: number; + b = s2 * fz[fi + k1] - c2 * fz[gi + k1]; + a = c2 * fz[fi + k1] + s2 * fz[gi + k1]; + const f1 = fz[fi + 0] - a; + const f0 = fz[fi + 0] + a; + const g1 = fz[gi + 0] - b; + const g0 = fz[gi + 0] + b; + b = s2 * fz[fi + k3] - c2 * fz[gi + k3]; + a = c2 * fz[fi + k3] + s2 * fz[gi + k3]; + const f3 = fz[fi + k2] - a; + const f2 = fz[fi + k2] + a; + const g3 = fz[gi + k2] - b; + const g2 = fz[gi + k2] + b; + b = s1 * f2 - c1 * g3; + a = c1 * f2 + s1 * g3; + fz[fi + k2] = f0 - a; + fz[fi + 0] = f0 + a; + fz[gi + k3] = g1 - b; + fz[gi + k1] = g1 + b; + b = c1 * g2 - s1 * f3; + a = s1 * g2 + c1 * f3; + fz[gi + k2] = g0 - a; + fz[gi + 0] = g0 + a; + fz[fi + k3] = f1 - b; + fz[fi + k1] = f1 + b; + gi += k4; + fi += k4; + } while (fi < fn); + c2 = c1; + c1 = c2 * this.costab[tri + 0] - s1 * this.costab[tri + 1]; + s1 = c2 * this.costab[tri + 1] + s1 * this.costab[tri + 0]; + } + tri += 2; + } while (k4 < n); + } + + private readonly rv_tbl = [ + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, + 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, + 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, + 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, + 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, + 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + ] as const; + + fft_short( + _gfc: LameInternalFlags, + x_real: Float32Array[], + chn: number, + buffer: Float32Array[], + bufPos: number + ) { + for (let b = 0; b < 3; b++) { + let x = BLKSIZE_s / 2; + const k = 0xffff & ((576 / 3) * (b + 1)); + let j = BLKSIZE_s / 8 - 1; + do { + let f0; + let f1; + let f2; + let f3; + let w; + const i = this.rv_tbl[j << 2] & 0xff; + + f0 = this.window_s[i] * buffer[chn][bufPos + i + k]; + w = this.window_s[0x7f - i] * buffer[chn][bufPos + i + k + 0x80]; + f1 = f0 - w; + f0 += w; + f2 = this.window_s[i + 0x40] * buffer[chn][bufPos + i + k + 0x40]; + w = this.window_s[0x3f - i] * buffer[chn][bufPos + i + k + 0xc0]; + f3 = f2 - w; + f2 += w; + + x -= 4; + x_real[b][x + 0] = f0 + f2; + x_real[b][x + 2] = f0 - f2; + x_real[b][x + 1] = f1 + f3; + x_real[b][x + 3] = f1 - f3; + + f0 = this.window_s[i + 0x01] * buffer[chn][bufPos + i + k + 0x01]; + w = this.window_s[0x7e - i] * buffer[chn][bufPos + i + k + 0x81]; + f1 = f0 - w; + f0 += w; + f2 = this.window_s[i + 0x41] * buffer[chn][bufPos + i + k + 0x41]; + w = this.window_s[0x3e - i] * buffer[chn][bufPos + i + k + 0xc1]; + f3 = f2 - w; + f2 += w; + + x_real[b][x + BLKSIZE_s / 2 + 0] = f0 + f2; + x_real[b][x + BLKSIZE_s / 2 + 2] = f0 - f2; + x_real[b][x + BLKSIZE_s / 2 + 1] = f1 + f3; + x_real[b][x + BLKSIZE_s / 2 + 3] = f1 - f3; + } while (--j >= 0); + + this.fht(x_real[b], x, BLKSIZE_s / 2); + } + } + + fft_long( + _gfc: LameInternalFlags, + y: Float32Array, + chn: number, + buffer: Float32Array[], + bufPos: number + ) { + let jj = BLKSIZE / 8 - 1; + let x = BLKSIZE / 2; + + do { + let f0; + let f1; + let f2; + let f3; + let w; + const i = this.rv_tbl[jj] & 0xff; + f0 = this.window[i] * buffer[chn][bufPos + i]; + w = this.window[i + 0x200] * buffer[chn][bufPos + i + 0x200]; + f1 = f0 - w; + f0 += w; + f2 = this.window[i + 0x100] * buffer[chn][bufPos + i + 0x100]; + w = this.window[i + 0x300] * buffer[chn][bufPos + i + 0x300]; + f3 = f2 - w; + f2 += w; + + x -= 4; + y[x + 0] = f0 + f2; + y[x + 2] = f0 - f2; + y[x + 1] = f1 + f3; + y[x + 3] = f1 - f3; + + f0 = this.window[i + 0x001] * buffer[chn][bufPos + i + 0x001]; + w = this.window[i + 0x201] * buffer[chn][bufPos + i + 0x201]; + f1 = f0 - w; + f0 += w; + f2 = this.window[i + 0x101] * buffer[chn][bufPos + i + 0x101]; + w = this.window[i + 0x301] * buffer[chn][bufPos + i + 0x301]; + f3 = f2 - w; + f2 += w; + + y[x + BLKSIZE / 2 + 0] = f0 + f2; + y[x + BLKSIZE / 2 + 2] = f0 - f2; + y[x + BLKSIZE / 2 + 1] = f1 + f3; + y[x + BLKSIZE / 2 + 3] = f1 - f3; + } while (--jj >= 0); + + this.fht(y, x, BLKSIZE / 2); + } + + init() { + for (let i = 0; i < BLKSIZE; i++) { + this.window[i] = + 0.42 - + 0.5 * Math.cos((2 * Math.PI * (i + 0.5)) / BLKSIZE) + + 0.08 * Math.cos((4 * Math.PI * (i + 0.5)) / BLKSIZE); + } + + for (let i = 0; i < BLKSIZE_s / 2; i++) { + this.window_s[i] = + 0.5 * (1.0 - Math.cos((2.0 * Math.PI * (i + 0.5)) / BLKSIZE_s)); + } + } +} diff --git a/packages/mp3-encoder/src/lame/GainAnalysis.ts b/packages/mp3-encoder/src/lame/GainAnalysis.ts new file mode 100644 index 0000000000..d364219ec2 --- /dev/null +++ b/packages/mp3-encoder/src/lame/GainAnalysis.ts @@ -0,0 +1,577 @@ +import type { ReplayGain } from './ReplayGain'; +import { copyArray, fillArray } from './arrays'; +import { fsqr } from './math'; + +export class GainAnalysis { + static readonly STEPS_per_dB = 100; + + static readonly MAX_dB = 120; + + static readonly GAIN_NOT_ENOUGH_SAMPLES = -24601; + + static readonly GAIN_ANALYSIS_ERROR = 0; + + static readonly GAIN_ANALYSIS_OK = 1; + + static readonly INIT_GAIN_ANALYSIS_ERROR = 0; + + static readonly INIT_GAIN_ANALYSIS_OK = 1; + + static readonly YULE_ORDER = 10; + + static readonly MAX_ORDER = GainAnalysis.YULE_ORDER; + + static readonly MAX_SAMP_FREQ = 48000; + + static readonly RMS_WINDOW_TIME_NUMERATOR = 1; + + static readonly RMS_WINDOW_TIME_DENOMINATOR = 20; + + static readonly MAX_SAMPLES_PER_WINDOW = + (GainAnalysis.MAX_SAMP_FREQ * GainAnalysis.RMS_WINDOW_TIME_NUMERATOR) / + GainAnalysis.RMS_WINDOW_TIME_DENOMINATOR + + 1; + + private static readonly PINK_REF = 64.82; + + private static readonly RMS_PERCENTILE = 0.95; + + private ABYule = [ + [ + 0.038575994352, -3.84664617118067, -0.02160367184185, 7.81501653005538, + -0.00123395316851, -11.34170355132042, -0.00009291677959, + 13.05504219327545, -0.01655260341619, -12.28759895145294, + 0.02161526843274, 9.4829380631979, -0.02074045215285, -5.87257861775999, + 0.00594298065125, 2.75465861874613, 0.00306428023191, -0.86984376593551, + 0.00012025322027, 0.13919314567432, 0.00288463683916, + ], + [ + 0.0541865640643, -3.47845948550071, -0.02911007808948, 6.36317777566148, + -0.00848709379851, -8.54751527471874, -0.00851165645469, 9.4769360780128, + -0.00834990904936, -8.81498681370155, 0.02245293253339, 6.85401540936998, + -0.02596338512915, -4.39470996079559, 0.01624864962975, 2.19611684890774, + -0.00240879051584, -0.75104302451432, 0.00674613682247, 0.13149317958808, + -0.00187763777362, + ], + [ + 0.15457299681924, -2.37898834973084, -0.09331049056315, 2.84868151156327, + -0.06247880153653, -2.64577170229825, 0.02163541888798, 2.23697657451713, + -0.05588393329856, -1.67148153367602, 0.04781476674921, 1.00595954808547, + 0.00222312597743, -0.45953458054983, 0.03174092540049, 0.16378164858596, + -0.01390589421898, -0.05032077717131, 0.00651420667831, 0.0234789740702, + -0.00881362733839, + ], + [ + 0.30296907319327, -1.61273165137247, -0.22613988682123, 1.0797749225997, + -0.08587323730772, -0.2565625775407, 0.03282930172664, -0.1627671912044, + -0.00915702933434, -0.22638893773906, -0.02364141202522, 0.39120800788284, + -0.00584456039913, -0.22138138954925, 0.06276101321749, 0.04500235387352, + -0.00000828086748, 0.02005851806501, 0.00205861885564, 0.00302439095741, + -0.02950134983287, + ], + [ + 0.33642304856132, -1.49858979367799, -0.2557224142557, 0.87350271418188, + -0.11828570177555, 0.12205022308084, 0.11921148675203, -0.80774944671438, + -0.07834489609479, 0.47854794562326, -0.0046997791438, -0.12453458140019, + -0.0058950022444, -0.04067510197014, 0.05724228140351, 0.08333755284107, + 0.00832043980773, -0.04237348025746, -0.0163538138454, 0.02977207319925, + -0.0176017656815, + ], + [ + 0.4491525660845, -0.62820619233671, -0.14351757464547, 0.29661783706366, + -0.22784394429749, -0.372563729424, -0.01419140100551, 0.00213767857124, + 0.04078262797139, -0.42029820170918, -0.12398163381748, 0.22199650564824, + 0.04097565135648, 0.00613424350682, 0.10478503600251, 0.06747620744683, + -0.01863887810927, 0.05784820375801, -0.03193428438915, 0.03222754072173, + 0.00541907748707, + ], + [ + 0.56619470757641, -1.04800335126349, -0.75464456939302, 0.29156311971249, + 0.1624213774223, -0.26806001042947, 0.16744243493672, 0.00819999645858, + -0.18901604199609, 0.45054734505008, 0.3093178284183, -0.33032403314006, + -0.27562961986224, 0.0673936833311, 0.00647310677246, -0.04784254229033, + 0.08647503780351, 0.01639907836189, -0.0378898455484, 0.01807364323573, + -0.00588215443421, + ], + [ + 0.58100494960553, -0.51035327095184, -0.53174909058578, -0.31863563325245, + -0.14289799034253, -0.20256413484477, 0.17520704835522, 0.1472815413433, + 0.02377945217615, 0.38952639978999, 0.15558449135573, -0.23313271880868, + -0.25344790059353, -0.05246019024463, 0.01628462406333, -0.02505961724053, + 0.06920467763959, 0.02442357316099, -0.03721611395801, 0.01818801111503, + -0.00749618797172, + ], + [ + 0.53648789255105, -0.2504987195602, -0.42163034350696, -0.43193942311114, + -0.00275953611929, -0.03424681017675, 0.04267842219415, -0.04678328784242, + -0.10214864179676, 0.26408300200955, 0.14590772289388, 0.15113130533216, + -0.02459864859345, -0.17556493366449, -0.11202315195388, + -0.18823009262115, -0.04060034127, 0.05477720428674, 0.0478866554818, + 0.0470440968812, -0.02217936801134, + ], + ]; + + private ABButter = [ + [ + 0.98621192462708, -1.97223372919527, -1.97242384925416, 0.97261396931306, + 0.98621192462708, + ], + [ + 0.98500175787242, -1.96977855582618, -1.97000351574484, 0.9702284756635, + 0.98500175787242, + ], + [ + 0.97938932735214, -1.95835380975398, -1.95877865470428, 0.95920349965459, + 0.97938932735214, + ], + [ + 0.97531843204928, -1.95002759149878, -1.95063686409857, 0.95124613669835, + 0.97531843204928, + ], + [ + 0.97316523498161, -1.94561023566527, -1.94633046996323, 0.94705070426118, + 0.97316523498161, + ], + [ + 0.96454515552826, -1.92783286977036, -1.92909031105652, 0.93034775234268, + 0.96454515552826, + ], + [ + 0.96009142950541, -1.91858953033784, -1.92018285901082, 0.92177618768381, + 0.96009142950541, + ], + [ + 0.95856916599601, -1.9154210807478, -1.91713833199203, 0.91885558323625, + 0.95856916599601, + ], + [ + 0.94597685600279, -1.88903307939452, -1.89195371200558, 0.89487434461664, + 0.94597685600279, + ], + ]; + + private filterYule( + input: Float32Array, + inputPos: number, + output: Float32Array, + outputPos: number, + nSamples: number, + kernel: number[] + ): void { + while (nSamples-- !== 0) { + output[outputPos] = + 1e-10 + + input[inputPos + 0] * kernel[0] - + output[outputPos - 1] * kernel[1] + + input[inputPos - 1] * kernel[2] - + output[outputPos - 2] * kernel[3] + + input[inputPos - 2] * kernel[4] - + output[outputPos - 3] * kernel[5] + + input[inputPos - 3] * kernel[6] - + output[outputPos - 4] * kernel[7] + + input[inputPos - 4] * kernel[8] - + output[outputPos - 5] * kernel[9] + + input[inputPos - 5] * kernel[10] - + output[outputPos - 6] * kernel[11] + + input[inputPos - 6] * kernel[12] - + output[outputPos - 7] * kernel[13] + + input[inputPos - 7] * kernel[14] - + output[outputPos - 8] * kernel[15] + + input[inputPos - 8] * kernel[16] - + output[outputPos - 9] * kernel[17] + + input[inputPos - 9] * kernel[18] - + output[outputPos - 10] * kernel[19] + + input[inputPos - 10] * kernel[20]; + ++outputPos; + ++inputPos; + } + } + + private filterButter( + input: Float32Array, + inputPos: number, + output: Float32Array, + outputPos: number, + nSamples: number, + kernel: number[] + ) { + while (nSamples-- !== 0) { + output[outputPos] = + input[inputPos + 0] * kernel[0] - + output[outputPos - 1] * kernel[1] + + input[inputPos - 1] * kernel[2] - + output[outputPos - 2] * kernel[3] + + input[inputPos - 2] * kernel[4]; + ++outputPos; + ++inputPos; + } + } + + private resetSampleFrequency(rgData: ReplayGain, samplefreq: number) { + for (let i = 0; i < GainAnalysis.MAX_ORDER; i++) { + rgData.linprebuf[i] = 0; + rgData.lstepbuf[i] = 0; + rgData.loutbuf[i] = 0; + rgData.rinprebuf[i] = 0; + rgData.rstepbuf[i] = 0; + rgData.routbuf[i] = 0; + } + + switch (Math.trunc(samplefreq)) { + case 48000: + rgData.reqindex = 0; + break; + case 44100: + rgData.reqindex = 1; + break; + case 32000: + rgData.reqindex = 2; + break; + case 24000: + rgData.reqindex = 3; + break; + case 22050: + rgData.reqindex = 4; + break; + case 16000: + rgData.reqindex = 5; + break; + case 12000: + rgData.reqindex = 6; + break; + case 11025: + rgData.reqindex = 7; + break; + case 8000: + rgData.reqindex = 8; + break; + default: + return GainAnalysis.INIT_GAIN_ANALYSIS_ERROR; + } + + rgData.sampleWindow = Math.trunc( + (samplefreq * GainAnalysis.RMS_WINDOW_TIME_NUMERATOR + + GainAnalysis.RMS_WINDOW_TIME_DENOMINATOR - + 1) / + GainAnalysis.RMS_WINDOW_TIME_DENOMINATOR + ); + + rgData.lsum = 0; + rgData.rsum = 0; + rgData.totsamp = 0; + + fillArray(rgData.A, 0); + + return GainAnalysis.INIT_GAIN_ANALYSIS_OK; + } + + initGainAnalysis(rgData: ReplayGain, samplefreq: number) { + if ( + this.resetSampleFrequency(rgData, samplefreq) !== + GainAnalysis.INIT_GAIN_ANALYSIS_OK + ) { + return GainAnalysis.INIT_GAIN_ANALYSIS_ERROR; + } + + rgData.linpre = GainAnalysis.MAX_ORDER; + rgData.rinpre = GainAnalysis.MAX_ORDER; + rgData.lstep = GainAnalysis.MAX_ORDER; + rgData.rstep = GainAnalysis.MAX_ORDER; + rgData.lout = GainAnalysis.MAX_ORDER; + rgData.rout = GainAnalysis.MAX_ORDER; + + fillArray(rgData.B, 0); + + return GainAnalysis.INIT_GAIN_ANALYSIS_OK; + } + + analyzeSamples( + rgData: ReplayGain, + left_samples: Float32Array, + left_samplesPos: number, + right_samples: Float32Array, + right_samplesPos: number, + num_samples: number, + num_channels: number + ) { + let curleft; + let curleftBase; + let curright; + let currightBase; + let batchsamples; + let cursamples; + let cursamplepos; + + if (num_samples === 0) return GainAnalysis.GAIN_ANALYSIS_OK; + + cursamplepos = 0; + batchsamples = num_samples; + + switch (num_channels) { + case 1: + right_samples = left_samples; + right_samplesPos = left_samplesPos; + break; + case 2: + break; + default: + return GainAnalysis.GAIN_ANALYSIS_ERROR; + } + + if (num_samples < GainAnalysis.MAX_ORDER) { + copyArray( + left_samples, + left_samplesPos, + rgData.linprebuf, + GainAnalysis.MAX_ORDER, + num_samples + ); + copyArray( + right_samples, + right_samplesPos, + rgData.rinprebuf, + GainAnalysis.MAX_ORDER, + num_samples + ); + } else { + copyArray( + left_samples, + left_samplesPos, + rgData.linprebuf, + GainAnalysis.MAX_ORDER, + GainAnalysis.MAX_ORDER + ); + copyArray( + right_samples, + right_samplesPos, + rgData.rinprebuf, + GainAnalysis.MAX_ORDER, + GainAnalysis.MAX_ORDER + ); + } + + while (batchsamples > 0) { + cursamples = + batchsamples > rgData.sampleWindow - rgData.totsamp + ? rgData.sampleWindow - rgData.totsamp + : batchsamples; + if (cursamplepos < GainAnalysis.MAX_ORDER) { + curleft = rgData.linpre + cursamplepos; + curleftBase = rgData.linprebuf; + curright = rgData.rinpre + cursamplepos; + currightBase = rgData.rinprebuf; + if (cursamples > GainAnalysis.MAX_ORDER - cursamplepos) + cursamples = GainAnalysis.MAX_ORDER - cursamplepos; + } else { + curleft = left_samplesPos + cursamplepos; + curleftBase = left_samples; + curright = right_samplesPos + cursamplepos; + currightBase = right_samples; + } + + this.filterYule( + curleftBase, + curleft, + rgData.lstepbuf, + rgData.lstep + rgData.totsamp, + cursamples, + this.ABYule[rgData.reqindex] + ); + this.filterYule( + currightBase, + curright, + rgData.rstepbuf, + rgData.rstep + rgData.totsamp, + cursamples, + this.ABYule[rgData.reqindex] + ); + + this.filterButter( + rgData.lstepbuf, + rgData.lstep + rgData.totsamp, + rgData.loutbuf, + rgData.lout + rgData.totsamp, + cursamples, + this.ABButter[rgData.reqindex] + ); + this.filterButter( + rgData.rstepbuf, + rgData.rstep + rgData.totsamp, + rgData.routbuf, + rgData.rout + rgData.totsamp, + cursamples, + this.ABButter[rgData.reqindex] + ); + + curleft = rgData.lout + rgData.totsamp; + + curleftBase = rgData.loutbuf; + curright = rgData.rout + rgData.totsamp; + currightBase = rgData.routbuf; + + let i = cursamples % 8; + while (i-- !== 0) { + rgData.lsum += fsqr(curleftBase[curleft++]); + rgData.rsum += fsqr(currightBase[curright++]); + } + i = cursamples / 8; + while (i-- !== 0) { + rgData.lsum += + fsqr(curleftBase[curleft + 0]) + + fsqr(curleftBase[curleft + 1]) + + fsqr(curleftBase[curleft + 2]) + + fsqr(curleftBase[curleft + 3]) + + fsqr(curleftBase[curleft + 4]) + + fsqr(curleftBase[curleft + 5]) + + fsqr(curleftBase[curleft + 6]) + + fsqr(curleftBase[curleft + 7]); + curleft += 8; + rgData.rsum += + fsqr(currightBase[curright + 0]) + + fsqr(currightBase[curright + 1]) + + fsqr(currightBase[curright + 2]) + + fsqr(currightBase[curright + 3]) + + fsqr(currightBase[curright + 4]) + + fsqr(currightBase[curright + 5]) + + fsqr(currightBase[curright + 6]) + + fsqr(currightBase[curright + 7]); + curright += 8; + } + + batchsamples -= cursamples; + cursamplepos += cursamples; + rgData.totsamp += cursamples; + if (rgData.totsamp === rgData.sampleWindow) { + const val = + GainAnalysis.STEPS_per_dB * + 10 * + Math.log10( + ((rgData.lsum + rgData.rsum) / rgData.totsamp) * 0.5 + 1e-37 + ); + let ival = val <= 0 ? 0 : Math.trunc(val); + if (ival >= rgData.A.length) ival = rgData.A.length - 1; + rgData.A[ival]++; + rgData.lsum = 0; + rgData.rsum = 0; + + copyArray( + rgData.loutbuf, + rgData.totsamp, + rgData.loutbuf, + 0, + GainAnalysis.MAX_ORDER + ); + copyArray( + rgData.routbuf, + rgData.totsamp, + rgData.routbuf, + 0, + GainAnalysis.MAX_ORDER + ); + copyArray( + rgData.lstepbuf, + rgData.totsamp, + rgData.lstepbuf, + 0, + GainAnalysis.MAX_ORDER + ); + copyArray( + rgData.rstepbuf, + rgData.totsamp, + rgData.rstepbuf, + 0, + GainAnalysis.MAX_ORDER + ); + rgData.totsamp = 0; + } + if (rgData.totsamp > rgData.sampleWindow) { + return GainAnalysis.GAIN_ANALYSIS_ERROR; + } + } + if (num_samples < GainAnalysis.MAX_ORDER) { + copyArray( + rgData.linprebuf, + num_samples, + rgData.linprebuf, + 0, + GainAnalysis.MAX_ORDER - num_samples + ); + copyArray( + rgData.rinprebuf, + num_samples, + rgData.rinprebuf, + 0, + GainAnalysis.MAX_ORDER - num_samples + ); + copyArray( + left_samples, + left_samplesPos, + rgData.linprebuf, + GainAnalysis.MAX_ORDER - num_samples, + num_samples + ); + copyArray( + right_samples, + right_samplesPos, + rgData.rinprebuf, + GainAnalysis.MAX_ORDER - num_samples, + num_samples + ); + } else { + copyArray( + left_samples, + left_samplesPos + num_samples - GainAnalysis.MAX_ORDER, + rgData.linprebuf, + 0, + GainAnalysis.MAX_ORDER + ); + copyArray( + right_samples, + right_samplesPos + num_samples - GainAnalysis.MAX_ORDER, + rgData.rinprebuf, + 0, + GainAnalysis.MAX_ORDER + ); + } + + return GainAnalysis.GAIN_ANALYSIS_OK; + } + + private analyzeResult(Array: Int32Array, len: number) { + let i; + + let elems = 0; + for (i = 0; i < len; i++) elems += Array[i]; + if (elems === 0) return GainAnalysis.GAIN_NOT_ENOUGH_SAMPLES; + + let upper = Math.ceil(elems * (1 - GainAnalysis.RMS_PERCENTILE)); + for (i = len; i-- > 0; ) { + if ((upper -= Array[i]) <= 0) break; + } + + return GainAnalysis.PINK_REF - i / GainAnalysis.STEPS_per_dB; + } + + getTitleGain(rgData: ReplayGain) { + const retval = this.analyzeResult(rgData.A, rgData.A.length); + + for (let i = 0; i < rgData.A.length; i++) { + rgData.B[i] += rgData.A[i]; + rgData.A[i] = 0; + } + + for (let i = 0; i < GainAnalysis.MAX_ORDER; i++) { + rgData.linprebuf[i] = 0; + rgData.lstepbuf[i] = 0; + rgData.loutbuf[i] = 0; + rgData.rinprebuf[i] = 0; + rgData.rstepbuf[i] = 0; + rgData.routbuf[i] = 0; + } + + rgData.totsamp = 0; + rgData.lsum = 0; + rgData.rsum = 0; + return retval; + } +} diff --git a/packages/mp3-encoder/src/lame/GrInfo.ts b/packages/mp3-encoder/src/lame/GrInfo.ts new file mode 100644 index 0000000000..3e6c919bf8 --- /dev/null +++ b/packages/mp3-encoder/src/lame/GrInfo.ts @@ -0,0 +1,105 @@ +import { SFBMAX } from './constants'; + +export class GrInfo { + xr = new Float32Array(576); + + l3_enc = new Int32Array(576); + + scalefac = new Int32Array(SFBMAX); + + xrpow_max = 0; + + part2_3_length = 0; + + big_values = 0; + + count1 = 0; + + global_gain = 0; + + scalefac_compress = 0; + + block_type = 0; + + mixed_block_flag = 0; + + table_select = new Int32Array(3); + + subblock_gain = new Int32Array(3 + 1); + + region0_count = 0; + + region1_count = 0; + + preflag = 0; + + scalefac_scale = 0; + + count1table_select = 0; + + part2_length = 0; + + sfb_lmax = 0; + + sfb_smin = 0; + + psy_lmax = 0; + + sfbmax = 0; + + psymax = 0; + + sfbdivide = 0; + + width = new Int32Array(SFBMAX); + + window = new Int32Array(SFBMAX); + + count1bits = 0; + + sfb_partition_table: readonly number[] | null = null; + + slen = new Int32Array(4); + + max_nonzero_coeff = 0; + + assign(other: GrInfo) { + this.xr = new Float32Array(other.xr); + this.l3_enc = new Int32Array(other.l3_enc); + this.scalefac = new Int32Array(other.scalefac); + this.xrpow_max = other.xrpow_max; + + this.part2_3_length = other.part2_3_length; + this.big_values = other.big_values; + this.count1 = other.count1; + this.global_gain = other.global_gain; + this.scalefac_compress = other.scalefac_compress; + this.block_type = other.block_type; + this.mixed_block_flag = other.mixed_block_flag; + this.table_select = new Int32Array(other.table_select); + this.subblock_gain = new Int32Array(other.subblock_gain); + this.region0_count = other.region0_count; + this.region1_count = other.region1_count; + this.preflag = other.preflag; + this.scalefac_scale = other.scalefac_scale; + this.count1table_select = other.count1table_select; + + this.part2_length = other.part2_length; + this.sfb_lmax = other.sfb_lmax; + this.sfb_smin = other.sfb_smin; + this.psy_lmax = other.psy_lmax; + this.sfbmax = other.sfbmax; + this.psymax = other.psymax; + this.sfbdivide = other.sfbdivide; + this.width = new Int32Array(other.width); + this.window = new Int32Array(other.window); + this.count1bits = other.count1bits; + + this.sfb_partition_table = + other.sfb_partition_table !== null + ? Array.from(other.sfb_partition_table) + : null; + this.slen = new Int32Array(other.slen); + this.max_nonzero_coeff = other.max_nonzero_coeff; + } +} diff --git a/packages/mp3-encoder/src/lame/Header.ts b/packages/mp3-encoder/src/lame/Header.ts new file mode 100644 index 0000000000..4105be1b8b --- /dev/null +++ b/packages/mp3-encoder/src/lame/Header.ts @@ -0,0 +1,7 @@ +export class Header { + write_timing = 0; + + ptr = 0; + + readonly buf = new Uint8Array(40); +} diff --git a/packages/mp3-encoder/src/lame/HuffCodeTab.ts b/packages/mp3-encoder/src/lame/HuffCodeTab.ts new file mode 100644 index 0000000000..f89929d04f --- /dev/null +++ b/packages/mp3-encoder/src/lame/HuffCodeTab.ts @@ -0,0 +1,9 @@ +export interface HuffCodeTab { + xlen: number; + + linmax: number; + + table?: readonly number[]; + + hlen?: readonly number[]; +} diff --git a/packages/mp3-encoder/src/lame/IIISideInfo.ts b/packages/mp3-encoder/src/lame/IIISideInfo.ts new file mode 100644 index 0000000000..c48b143a46 --- /dev/null +++ b/packages/mp3-encoder/src/lame/IIISideInfo.ts @@ -0,0 +1,17 @@ +import { GrInfo } from './GrInfo'; + +export class IIISideInfo { + tt = Array.from({ length: 2 }, () => + Array.from({ length: 2 }, () => new GrInfo()) + ); + + main_data_begin = 0; + + private_bits = 0; + + resvDrain_pre = 0; + + resvDrain_post = 0; + + scfsi = Array.from({ length: 2 }, () => new Int32Array(4)); +} diff --git a/packages/mp3-encoder/src/lame/III_psy_ratio.ts b/packages/mp3-encoder/src/lame/III_psy_ratio.ts new file mode 100644 index 0000000000..0c92c14112 --- /dev/null +++ b/packages/mp3-encoder/src/lame/III_psy_ratio.ts @@ -0,0 +1,7 @@ +import { III_psy_xmin } from './III_psy_xmin'; + +export class III_psy_ratio { + thm = new III_psy_xmin(); + + en = new III_psy_xmin(); +} diff --git a/packages/mp3-encoder/src/lame/III_psy_xmin.ts b/packages/mp3-encoder/src/lame/III_psy_xmin.ts new file mode 100644 index 0000000000..0307462e4c --- /dev/null +++ b/packages/mp3-encoder/src/lame/III_psy_xmin.ts @@ -0,0 +1,17 @@ +import { copyArray } from './arrays'; +import { SBMAX_l, SBMAX_s } from './constants'; + +export class III_psy_xmin { + l = new Float32Array(SBMAX_l); + + s = Array.from({ length: SBMAX_s }, () => new Float32Array(3)); + + assign(iii_psy_xmin: Readonly<III_psy_xmin>) { + copyArray(iii_psy_xmin.l, 0, this.l, 0, SBMAX_l); + for (let i = 0; i < SBMAX_s; i++) { + for (let j = 0; j < 3; j++) { + this.s[i][j] = iii_psy_xmin.s[i][j]; + } + } + } +} diff --git a/packages/mp3-encoder/src/lame/InOut.ts b/packages/mp3-encoder/src/lame/InOut.ts new file mode 100644 index 0000000000..0f857f2426 --- /dev/null +++ b/packages/mp3-encoder/src/lame/InOut.ts @@ -0,0 +1,5 @@ +export class InOut { + n_in = 0; + + n_out = 0; +} diff --git a/packages/mp3-encoder/src/lame/Lame.ts b/packages/mp3-encoder/src/lame/Lame.ts new file mode 100644 index 0000000000..c19d7e4307 --- /dev/null +++ b/packages/mp3-encoder/src/lame/Lame.ts @@ -0,0 +1,1342 @@ +import { BitStream } from './BitStream'; +import { CBRNewIterationLoop } from './CBRNewIterationLoop'; +import { Encoder } from './Encoder'; +import { GainAnalysis } from './GainAnalysis'; +import { InOut } from './InOut'; +import { LameGlobalFlags } from './LameGlobalFlags'; +import { MPEGMode } from './MPEGMode'; +import { NumUsed } from './NumUsed'; +import { Presets } from './Presets'; +import { PsyModel } from './PsyModel'; +import { Quantize } from './Quantize'; +import { ScaleFac } from './ScaleFac'; +import { ShortBlock } from './ShortBlock'; +import { VbrMode } from './VbrMode'; +import { assert } from './assert'; +import { findBitrateIndex, findNearestBitrate, getBitrate } from './bitrates'; +import { + BLKSIZE, + BPC, + ENCDELAY, + FFTOFFSET, + LAME_ID, + MFSIZE, + MPG_MD_MS_LR, + POSTDELAY, + PSFB12, + PSFB21, + SBMAX_l, + SBMAX_s, +} from './constants'; +import { isCloseToEachOther, blackmanWindow, gcd } from './math'; +import type { SampleRate } from './sampleRates'; +import { findNearestSampleRate } from './sampleRates'; + +export class Lame { + private readonly bs: BitStream; + + private readonly p: Presets = new Presets(); + + private readonly qu: Quantize; + + private readonly enc: Encoder; + + constructor() { + this.bs = new BitStream(); + this.qu = new Quantize(this.bs); + this.enc = new Encoder(this.bs, this.qu.qupvt.psy); + } + + lame_init(channels: number, samplerate: number, kbps: number) { + const gfp = new LameGlobalFlags(channels, samplerate, kbps); + this.lame_init_params(gfp); + return gfp; + } + + // eslint-disable-next-line complexity + private lame_init_params(gfp: LameGlobalFlags): void { + const gfc = gfp.internal_flags; + + gfc.Class_ID = 0; + + gfc.channels_in = gfp.num_channels; + if (gfc.channels_in === 1) { + gfp.mode = MPEGMode.MONO; + } + gfc.channels_out = gfp.mode === MPEGMode.MONO ? 1 : 2; + gfc.mode_ext = MPG_MD_MS_LR; + + if ( + gfp.VBR === VbrMode.vbr_off && + gfp.VBR_mean_bitrate_kbps !== 128 && + gfp.brate === 0 + ) { + gfp.brate = gfp.VBR_mean_bitrate_kbps; + } + + if (gfp.VBR === VbrMode.vbr_off && gfp.brate === 0) { + if (isCloseToEachOther(gfp.compression_ratio, 0)) { + gfp.compression_ratio = 11.025; + } + } + + if (gfp.VBR === VbrMode.vbr_off && gfp.compression_ratio > 0) { + if (gfp.out_samplerate === 0) { + gfp.out_samplerate = findNearestSampleRate( + Math.trunc(0.97 * gfp.in_samplerate) + ); + } + + gfp.brate = Math.trunc( + (gfp.out_samplerate * 16 * gfc.channels_out) / + (1e3 * gfp.compression_ratio) + ); + + Lame.smpFrqIndex(gfp); + + gfp.brate = findNearestBitrate( + gfp.brate, + gfp.version, + gfp.out_samplerate + ); + } + + if (gfp.out_samplerate !== 0) { + if (gfp.out_samplerate < 16000) { + gfp.VBR_mean_bitrate_kbps = Math.max(gfp.VBR_mean_bitrate_kbps, 8); + gfp.VBR_mean_bitrate_kbps = Math.min(gfp.VBR_mean_bitrate_kbps, 64); + } else if (gfp.out_samplerate < 32000) { + gfp.VBR_mean_bitrate_kbps = Math.max(gfp.VBR_mean_bitrate_kbps, 8); + gfp.VBR_mean_bitrate_kbps = Math.min(gfp.VBR_mean_bitrate_kbps, 160); + } else { + gfp.VBR_mean_bitrate_kbps = Math.max(gfp.VBR_mean_bitrate_kbps, 32); + gfp.VBR_mean_bitrate_kbps = Math.min(gfp.VBR_mean_bitrate_kbps, 320); + } + } + + if (gfp.lowpassfreq === 0) { + let lowpass: number; + + switch (gfp.VBR) { + case VbrMode.vbr_off: { + lowpass = this.p.abrPresets.getOptimumBandwidth(gfp.brate); + break; + } + case VbrMode.vbr_abr: { + lowpass = this.p.abrPresets.getOptimumBandwidth( + gfp.VBR_mean_bitrate_kbps + ); + break; + } + case VbrMode.vbr_rh: { + const x = [ + 19500, 19000, 18600, 18000, 17500, 16000, 15600, 14900, 12500, + 10000, 3950, + ]; + if (gfp.VBR_q >= 0 && gfp.VBR_q <= 9) { + lowpass = x[gfp.VBR_q]; + } else { + lowpass = 19500; + } + break; + } + default: { + const x = [ + 19500, 19000, 18500, 18000, 17500, 16500, 15500, 14500, 12500, 9500, + 3950, + ]; + if (gfp.VBR_q >= 0 && gfp.VBR_q <= 9) { + lowpass = x[gfp.VBR_q]; + } else { + lowpass = 19500; + } + } + } + if ( + gfp.mode === MPEGMode.MONO && + (gfp.VBR === VbrMode.vbr_off || gfp.VBR === VbrMode.vbr_abr) + ) { + lowpass *= 1.5; + } + + gfp.lowpassfreq = Math.trunc(lowpass); + } + + if (gfp.out_samplerate === 0) { + if (2 * gfp.lowpassfreq > gfp.in_samplerate) { + gfp.lowpassfreq = gfp.in_samplerate / 2; + } + gfp.out_samplerate = this.optimum_samplefreq( + Math.trunc(gfp.lowpassfreq), + gfp.in_samplerate + ); + } + + gfp.lowpassfreq = Math.min(20500, gfp.lowpassfreq); + gfp.lowpassfreq = Math.min(gfp.out_samplerate / 2, gfp.lowpassfreq); + + if (gfp.VBR === VbrMode.vbr_off) { + gfp.compression_ratio = + (gfp.out_samplerate * 16 * gfc.channels_out) / (1e3 * gfp.brate); + } + if (gfp.VBR === VbrMode.vbr_abr) { + gfp.compression_ratio = + (gfp.out_samplerate * 16 * gfc.channels_out) / + (1e3 * gfp.VBR_mean_bitrate_kbps); + } + + gfc.findPeakSample = false; + + gfc.findReplayGain = false; + + if (gfc.findReplayGain) { + if ( + this.bs.ga.initGainAnalysis(gfc.rgdata, gfp.out_samplerate) === + GainAnalysis.INIT_GAIN_ANALYSIS_ERROR + ) { + throw new Error('INIT_GAIN_ANALYSIS_ERROR'); + } + } + + gfc.mode_gr = gfp.out_samplerate <= 24000 ? 1 : 2; + + gfp.framesize = 576 * gfc.mode_gr; + + gfc.resample_ratio = gfp.in_samplerate / gfp.out_samplerate; + + switch (gfp.VBR) { + case VbrMode.vbr_mt: + case VbrMode.vbr_rh: + case VbrMode.vbr_mtrh: + { + const cmp = [5.7, 6.5, 7.3, 8.2, 10, 11.9, 13, 14, 15, 16.5]; + gfp.compression_ratio = cmp[gfp.VBR_q]; + } + break; + case VbrMode.vbr_abr: + gfp.compression_ratio = + (gfp.out_samplerate * 16 * gfc.channels_out) / + (1e3 * gfp.VBR_mean_bitrate_kbps); + break; + default: + gfp.compression_ratio = + (gfp.out_samplerate * 16 * gfc.channels_out) / (1e3 * gfp.brate); + break; + } + + if (gfp.mode === MPEGMode.NOT_SET) { + gfp.mode = MPEGMode.JOINT_STEREO; + } + + gfc.highpass1 = 0; + gfc.highpass2 = 0; + + if (gfp.lowpassfreq > 0) { + gfc.lowpass2 = 2 * gfp.lowpassfreq; + + gfc.lowpass1 = (1 - 0.0) * 2 * gfp.lowpassfreq; + + gfc.lowpass1 /= gfp.out_samplerate; + gfc.lowpass2 /= gfp.out_samplerate; + } else { + gfc.lowpass1 = 0; + gfc.lowpass2 = 0; + } + + this.lame_init_params_ppflt(gfp); + + Lame.smpFrqIndex(gfp); + if (gfc.samplerate_index < 0) { + throw new Error('Invalid sample rate'); + } + + if (gfp.VBR === VbrMode.vbr_off) { + gfp.brate = findNearestBitrate( + gfp.brate, + gfp.version, + gfp.out_samplerate + ); + gfc.bitrate_index = findBitrateIndex( + gfp.brate, + gfp.version, + gfp.out_samplerate + ); + } else { + gfc.bitrate_index = 1; + } + + this.bs.resetPointers(gfc); + + const j = + gfc.samplerate_index + + 3 * gfp.version + + 6 * (gfp.out_samplerate < 16000 ? 1 : 0); + for (let i = 0; i < SBMAX_l + 1; i++) + gfc.scalefac_band.l[i] = this.sfBandIndex[j].l[i]; + + for (let i = 0; i < PSFB21 + 1; i++) { + const size = (gfc.scalefac_band.l[22] - gfc.scalefac_band.l[21]) / PSFB21; + const start = gfc.scalefac_band.l[21] + i * size; + gfc.scalefac_band.psfb21[i] = start; + } + gfc.scalefac_band.psfb21[PSFB21] = 576; + + for (let i = 0; i < SBMAX_s + 1; i++) { + gfc.scalefac_band.s[i] = this.sfBandIndex[j].s[i]; + } + + for (let i = 0; i < PSFB12 + 1; i++) { + const size = (gfc.scalefac_band.s[13] - gfc.scalefac_band.s[12]) / PSFB12; + const start = gfc.scalefac_band.s[12] + i * size; + gfc.scalefac_band.psfb12[i] = start; + } + gfc.scalefac_band.psfb12[PSFB12] = 192; + + if (gfp.version === 1) { + gfc.sideinfo_len = gfc.channels_out === 1 ? 4 + 17 : 4 + 32; + } else { + gfc.sideinfo_len = gfc.channels_out === 1 ? 4 + 9 : 4 + 17; + } + + this.lame_init_bitstream(gfp); + + gfc.Class_ID = LAME_ID; + + let k; + + for (k = 0; k < 19; k++) { + gfc.nsPsy.pefirbuf[k] = 700 * gfc.mode_gr * gfc.channels_out; + } + + if (gfp.ATHtype === -1) { + gfp.ATHtype = 4; + } + + assert(gfp.VBR_q <= 9); + assert(gfp.VBR_q >= 0); + + switch (gfp.VBR) { + case VbrMode.vbr_mt: + gfp.VBR = VbrMode.vbr_mtrh; + + // eslint-disable-next-line no-fallthrough + case VbrMode.vbr_mtrh: { + this.p.applyPresetFromQuality(gfp, gfp.VBR_q); + + if (gfp.quality < 0) gfp.quality = Lame.LAME_DEFAULT_QUALITY; + if (gfp.quality < 5) gfp.quality = 0; + if (gfp.quality > 5) gfp.quality = 5; + + gfc.PSY.mask_adjust = gfp.maskingadjust; + gfc.PSY.mask_adjust_short = gfp.maskingadjust_short; + + if (gfp.experimentalY) gfc.sfb21_extra = false; + else gfc.sfb21_extra = gfp.out_samplerate > 44000; + + gfc.iteration_loop = new CBRNewIterationLoop(this.qu); + break; + } + case VbrMode.vbr_rh: { + this.p.applyPresetFromQuality(gfp, gfp.VBR_q); + + gfc.PSY.mask_adjust = gfp.maskingadjust; + gfc.PSY.mask_adjust_short = gfp.maskingadjust_short; + + if (gfp.experimentalY) { + gfc.sfb21_extra = false; + } else gfc.sfb21_extra = gfp.out_samplerate > 44000; + + if (gfp.quality > 6) { + gfp.quality = 6; + } + + if (gfp.quality < 0) { + gfp.quality = Lame.LAME_DEFAULT_QUALITY; + } + + gfc.iteration_loop = new CBRNewIterationLoop(this.qu); + break; + } + + default: { + gfc.sfb21_extra = false; + + if (gfp.quality < 0) { + gfp.quality = Lame.LAME_DEFAULT_QUALITY; + } + + const vbrmode = gfp.VBR; + if (vbrmode === VbrMode.vbr_off) { + gfp.VBR_mean_bitrate_kbps = gfp.brate; + } + + this.p.apply(gfp, gfp.VBR_mean_bitrate_kbps); + gfp.VBR = vbrmode; + + gfc.PSY.mask_adjust = gfp.maskingadjust; + gfc.PSY.mask_adjust_short = gfp.maskingadjust_short; + + if (vbrmode === VbrMode.vbr_off) { + gfc.iteration_loop = new CBRNewIterationLoop(this.qu); + } else { + gfc.iteration_loop = new CBRNewIterationLoop(this.qu); + } + break; + } + } + assert(gfp.scale >= 0); + + if (gfp.VBR !== VbrMode.vbr_off) { + gfc.VBR_min_bitrate = 1; + + gfc.VBR_max_bitrate = 14; + + if (gfp.out_samplerate < 16000) { + gfc.VBR_max_bitrate = 8; + } + + if (gfp.VBR_min_bitrate_kbps !== 0) { + gfp.VBR_min_bitrate_kbps = findNearestBitrate( + gfp.VBR_min_bitrate_kbps, + gfp.version, + gfp.out_samplerate + ); + gfc.VBR_min_bitrate = findBitrateIndex( + gfp.VBR_min_bitrate_kbps, + gfp.version, + gfp.out_samplerate + ); + } + if (gfp.VBR_max_bitrate_kbps !== 0) { + gfp.VBR_max_bitrate_kbps = findNearestBitrate( + gfp.VBR_max_bitrate_kbps, + gfp.version, + gfp.out_samplerate + ); + gfc.VBR_max_bitrate = findBitrateIndex( + gfp.VBR_max_bitrate_kbps, + gfp.version, + gfp.out_samplerate + ); + } + gfp.VBR_min_bitrate_kbps = getBitrate(gfp.version, gfc.VBR_min_bitrate); + gfp.VBR_max_bitrate_kbps = getBitrate(gfp.version, gfc.VBR_max_bitrate); + gfp.VBR_mean_bitrate_kbps = Math.min( + getBitrate(gfp.version, gfc.VBR_max_bitrate), + gfp.VBR_mean_bitrate_kbps + ); + gfp.VBR_mean_bitrate_kbps = Math.max( + getBitrate(gfp.version, gfc.VBR_min_bitrate), + gfp.VBR_mean_bitrate_kbps + ); + } + + this.lame_init_qval(gfp); + assert(gfp.scale >= 0); + + gfc.ATH.useAdjust = 3; + + gfc.ATH.aaSensitivityP = Math.pow(10.0, gfp.athaa_sensitivity / -10.0); + + if (gfp.short_blocks === null) { + gfp.short_blocks = ShortBlock.short_block_allowed; + } + + if ( + gfp.short_blocks === ShortBlock.short_block_allowed && + (gfp.mode === MPEGMode.JOINT_STEREO || gfp.mode === MPEGMode.STEREO) + ) { + gfp.short_blocks = ShortBlock.short_block_coupled; + } + + if (gfp.quant_comp < 0) { + gfp.quant_comp = 1; + } + if (gfp.quant_comp_short < 0) { + gfp.quant_comp_short = 0; + } + + if (gfp.msfix < 0) { + gfp.msfix = 0; + } + + gfp.exp_nspsytune |= 1; + + if (gfp.internal_flags.nsPsy.attackthre < 0) { + gfp.internal_flags.nsPsy.attackthre = PsyModel.NSATTACKTHRE; + } + if (gfp.internal_flags.nsPsy.attackthre_s < 0) { + gfp.internal_flags.nsPsy.attackthre_s = PsyModel.NSATTACKTHRE_S; + } + + assert(gfp.scale >= 0); + + if (gfp.scale < 0) { + gfp.scale = 1; + } + + if (gfp.ATHtype < 0) { + gfp.ATHtype = 4; + } + + if (gfp.ATHcurve < 0) { + gfp.ATHcurve = 4; + } + + if (gfp.athaa_loudapprox < 0) { + gfp.athaa_loudapprox = 2; + } + + if (gfp.interChRatio < 0) { + gfp.interChRatio = 0; + } + + gfc.slot_lag = 0; + gfc.frac_SpF = 0; + if (gfp.VBR === VbrMode.vbr_off) { + gfc.frac_SpF = + ((gfp.version + 1) * 72000 * gfp.brate) % + Math.trunc(gfp.out_samplerate); + gfc.slot_lag = gfc.frac_SpF; + } + + this.qu.qupvt.iteration_init(gfp, this.qu.tak); + this.qu.qupvt.psy.psymodel_init(gfp); + assert(gfp.scale >= 0); + } + + private filterCoeficient(x: number) { + if (x > 1.0) { + return 0.0; + } + if (x <= 0.0) { + return 1.0; + } + + return Math.cos((Math.PI / 2) * x); + } + + private optimum_samplefreq( + lowpassfreq: number, + input_samplefreq: number + ): SampleRate { + let suggested_samplefreq: SampleRate = 44100; + + if (input_samplefreq >= 48000) suggested_samplefreq = 48000; + else if (input_samplefreq >= 44100) suggested_samplefreq = 44100; + else if (input_samplefreq >= 32000) suggested_samplefreq = 32000; + else if (input_samplefreq >= 24000) suggested_samplefreq = 24000; + else if (input_samplefreq >= 22050) suggested_samplefreq = 22050; + else if (input_samplefreq >= 16000) suggested_samplefreq = 16000; + else if (input_samplefreq >= 12000) suggested_samplefreq = 12000; + else if (input_samplefreq >= 11025) suggested_samplefreq = 11025; + else if (input_samplefreq >= 8000) suggested_samplefreq = 8000; + + if (lowpassfreq === -1) return suggested_samplefreq; + + if (lowpassfreq <= 15960) suggested_samplefreq = 44100; + if (lowpassfreq <= 15250) suggested_samplefreq = 32000; + if (lowpassfreq <= 11220) suggested_samplefreq = 24000; + if (lowpassfreq <= 9970) suggested_samplefreq = 22050; + if (lowpassfreq <= 7230) suggested_samplefreq = 16000; + if (lowpassfreq <= 5420) suggested_samplefreq = 12000; + if (lowpassfreq <= 4510) suggested_samplefreq = 11025; + if (lowpassfreq <= 3970) suggested_samplefreq = 8000; + + if (input_samplefreq < suggested_samplefreq) { + if (input_samplefreq > 44100) return 48000; + if (input_samplefreq > 32000) return 44100; + if (input_samplefreq > 24000) return 32000; + if (input_samplefreq > 22050) return 24000; + if (input_samplefreq > 16000) return 22050; + if (input_samplefreq > 12000) return 16000; + if (input_samplefreq > 11025) return 12000; + if (input_samplefreq > 8000) return 11025; + return 8000; + } + + return suggested_samplefreq; + } + + private static smpFrqIndex(gpf: LameGlobalFlags): void { + switch (gpf.out_samplerate) { + case 44100: + gpf.version = 1; + gpf.internal_flags.samplerate_index = 0; + return; + case 48000: + gpf.version = 1; + gpf.internal_flags.samplerate_index = 1; + return; + case 32000: + gpf.version = 1; + gpf.internal_flags.samplerate_index = 2; + return; + case 22050: + gpf.version = 0; + gpf.internal_flags.samplerate_index = 0; + return; + case 24000: + gpf.version = 0; + gpf.internal_flags.samplerate_index = 1; + return; + case 16000: + gpf.version = 0; + gpf.internal_flags.samplerate_index = 2; + return; + case 11025: + gpf.version = 0; + gpf.internal_flags.samplerate_index = 0; + return; + case 12000: + gpf.version = 0; + gpf.internal_flags.samplerate_index = 1; + return; + case 8000: + gpf.version = 0; + gpf.internal_flags.samplerate_index = 2; + return; + default: + gpf.version = 0; + gpf.internal_flags.samplerate_index = -1; + } + } + + private lame_init_params_ppflt(gfp: LameGlobalFlags) { + const gfc = gfp.internal_flags; + + let lowpass_band = 32; + let highpass_band = -1; + + if (gfc.lowpass1 > 0) { + let minband = 999; + for (let band = 0; band <= 31; band++) { + const freq = band / 31.0; + + if (freq >= gfc.lowpass2) { + lowpass_band = Math.min(lowpass_band, band); + } + if (gfc.lowpass1 < freq && freq < gfc.lowpass2) { + minband = Math.min(minband, band); + } + } + + if (minband === 999) { + gfc.lowpass1 = (lowpass_band - 0.75) / 31.0; + } else { + gfc.lowpass1 = (minband - 0.75) / 31.0; + } + gfc.lowpass2 = lowpass_band / 31.0; + } + + if (gfc.highpass2 > 0) { + if (gfc.highpass2 < 0.9 * (0.75 / 31.0)) { + gfc.highpass1 = 0; + gfc.highpass2 = 0; + console.warn( + 'Warning: highpass filter disabled. highpass frequency too small' + ); + } + } + + if (gfc.highpass2 > 0) { + let maxband = -1; + for (let band = 0; band <= 31; band++) { + const freq = band / 31.0; + + if (freq <= gfc.highpass1) { + highpass_band = Math.max(highpass_band, band); + } + if (gfc.highpass1 < freq && freq < gfc.highpass2) { + maxband = Math.max(maxband, band); + } + } + + gfc.highpass1 = highpass_band / 31.0; + if (maxband === -1) { + gfc.highpass2 = (highpass_band + 0.75) / 31.0; + } else { + gfc.highpass2 = (maxband + 0.75) / 31.0; + } + } + + for (let band = 0; band < 32; band++) { + let fc1; + let fc2; + const freq = band / 31.0; + if (gfc.highpass2 > gfc.highpass1) { + fc1 = this.filterCoeficient( + (gfc.highpass2 - freq) / (gfc.highpass2 - gfc.highpass1 + 1e-20) + ); + } else { + fc1 = 1.0; + } + if (gfc.lowpass2 > gfc.lowpass1) { + fc2 = this.filterCoeficient( + (freq - gfc.lowpass1) / (gfc.lowpass2 - gfc.lowpass1 + 1e-20) + ); + } else { + fc2 = 1.0; + } + gfc.amp_filter[band] = fc1 * fc2; + } + } + + private lame_init_qval(gfp: LameGlobalFlags) { + const gfc = gfp.internal_flags; + + switch (gfp.quality) { + default: + case 9: + gfc.psymodel = 0; + gfc.noise_shaping = 0; + gfc.noise_shaping_amp = 0; + gfc.noise_shaping_stop = 0; + gfc.use_best_huffman = 0; + gfc.full_outer_loop = 0; + break; + + case 8: + gfp.quality = 7; + + // eslint-disable-next-line no-fallthrough + case 7: + gfc.psymodel = 1; + gfc.noise_shaping = 0; + gfc.noise_shaping_amp = 0; + gfc.noise_shaping_stop = 0; + gfc.use_best_huffman = 0; + gfc.full_outer_loop = 0; + break; + + case 6: + gfc.psymodel = 1; + if (gfc.noise_shaping === 0) gfc.noise_shaping = 1; + gfc.noise_shaping_amp = 0; + gfc.noise_shaping_stop = 0; + if (gfc.subblock_gain === -1) gfc.subblock_gain = 1; + gfc.use_best_huffman = 0; + gfc.full_outer_loop = 0; + break; + + case 5: + gfc.psymodel = 1; + if (gfc.noise_shaping === 0) gfc.noise_shaping = 1; + gfc.noise_shaping_amp = 0; + gfc.noise_shaping_stop = 0; + if (gfc.subblock_gain === -1) gfc.subblock_gain = 1; + gfc.use_best_huffman = 0; + gfc.full_outer_loop = 0; + break; + + case 4: + gfc.psymodel = 1; + if (gfc.noise_shaping === 0) gfc.noise_shaping = 1; + gfc.noise_shaping_amp = 0; + gfc.noise_shaping_stop = 0; + if (gfc.subblock_gain === -1) gfc.subblock_gain = 1; + gfc.use_best_huffman = 1; + gfc.full_outer_loop = 0; + break; + + case 3: + gfc.psymodel = 1; + if (gfc.noise_shaping === 0) gfc.noise_shaping = 1; + gfc.noise_shaping_amp = 1; + gfc.noise_shaping_stop = 1; + if (gfc.subblock_gain === -1) gfc.subblock_gain = 1; + gfc.use_best_huffman = 1; + gfc.full_outer_loop = 0; + break; + + case 2: + gfc.psymodel = 1; + if (gfc.noise_shaping === 0) gfc.noise_shaping = 1; + if (gfc.substep_shaping === 0) gfc.substep_shaping = 2; + gfc.noise_shaping_amp = 1; + gfc.noise_shaping_stop = 1; + if (gfc.subblock_gain === -1) gfc.subblock_gain = 1; + gfc.use_best_huffman = 1; + + gfc.full_outer_loop = 0; + break; + + case 1: + gfc.psymodel = 1; + if (gfc.noise_shaping === 0) gfc.noise_shaping = 1; + if (gfc.substep_shaping === 0) gfc.substep_shaping = 2; + gfc.noise_shaping_amp = 2; + gfc.noise_shaping_stop = 1; + if (gfc.subblock_gain === -1) gfc.subblock_gain = 1; + gfc.use_best_huffman = 1; + gfc.full_outer_loop = 0; + break; + + case 0: + gfc.psymodel = 1; + if (gfc.noise_shaping === 0) gfc.noise_shaping = 1; + if (gfc.substep_shaping === 0) gfc.substep_shaping = 2; + gfc.noise_shaping_amp = 2; + gfc.noise_shaping_stop = 1; + if (gfc.subblock_gain === -1) gfc.subblock_gain = 1; + gfc.use_best_huffman = 1; + + gfc.full_outer_loop = 0; + + break; + } + } + + private lame_init_bitstream(gfp: LameGlobalFlags) { + const gfc = gfp.internal_flags; + gfp.frameNum = 0; + gfc.PeakSample = 0.0; + } + + private static readonly LAME_DEFAULT_QUALITY = 3; + + lame_encode_flush( + gfp: LameGlobalFlags, + mp3buffer: Uint8Array, + mp3bufferPos: number, + mp3buffer_size: number + ) { + const gfc = gfp.internal_flags; + + if (gfc.mf_samples_to_encode < 1) { + return 0; + } + + let samples_to_encode = gfc.mf_samples_to_encode - POSTDELAY; + if (gfp.in_samplerate !== gfp.out_samplerate) { + samples_to_encode += (16 * gfp.out_samplerate) / gfp.in_samplerate; + } + let end_padding = gfp.framesize - (samples_to_encode % gfp.framesize); + if (end_padding < 576) end_padding += gfp.framesize; + + let frames_left = (samples_to_encode + end_padding) / gfp.framesize; + + const buffer = Array.from({ length: 2 }, () => new Int16Array(1152)); + const mf_needed = Lame.calcNeeded(gfp); + let mp3count = 0; + let imp3 = 0; + let mp3buffer_size_remaining; + + while (frames_left > 0 && imp3 >= 0) { + let bunch = mf_needed - gfc.mf_size; + const frame_num = gfp.frameNum; + + bunch *= gfp.in_samplerate; + bunch /= gfp.out_samplerate; + if (bunch > 1152) bunch = 1152; + if (bunch < 1) bunch = 1; + + mp3buffer_size_remaining = mp3buffer_size - mp3count; + + if (mp3buffer_size === 0) mp3buffer_size_remaining = 0; + + imp3 = this.lame_encode_buffer( + gfp, + buffer[0], + buffer[1], + bunch, + mp3buffer, + mp3bufferPos, + mp3buffer_size_remaining + ); + + mp3bufferPos += imp3; + mp3count += imp3; + frames_left -= frame_num !== gfp.frameNum ? 1 : 0; + } + + gfc.mf_samples_to_encode = 0; + + if (imp3 < 0) { + return imp3; + } + + mp3buffer_size_remaining = mp3buffer_size - mp3count; + + if (mp3buffer_size === 0) mp3buffer_size_remaining = 0; + + this.bs.flush_bitstream(gfp); + imp3 = this.bs.copyFrameData( + gfc, + mp3buffer, + mp3bufferPos, + mp3buffer_size_remaining + ); + if (imp3 < 0) { + return imp3; + } + mp3bufferPos += imp3; + mp3count += imp3; + + return mp3count; + } + + lame_encode_buffer( + gfp: LameGlobalFlags, + buffer_l: Int16Array, + buffer_r: Int16Array, + nsamples: number, + mp3buf: Uint8Array, + mp3bufPos: number, + mp3buf_size: number + ) { + const gfc = gfp.internal_flags; + + if (gfc.Class_ID !== LAME_ID) return -3; + + if (nsamples === 0) return 0; + + if ( + gfc.in_buffer_0 === null || + gfc.in_buffer_1 === null || + gfc.in_buffer_nsamples < nsamples + ) { + gfc.in_buffer_0 = new Float32Array(nsamples); + gfc.in_buffer_1 = new Float32Array(nsamples); + gfc.in_buffer_nsamples = nsamples; + } + + const in_buffer = [gfc.in_buffer_0, gfc.in_buffer_1] as const; + + for (let i = 0; i < nsamples; i++) { + in_buffer[0][i] = buffer_l[i]; + if (gfc.channels_in > 1) { + in_buffer[1][i] = buffer_r[i]; + } + } + + return this.lame_encode_buffer_sample( + gfp, + in_buffer[0], + in_buffer[1], + nsamples, + mp3buf, + mp3bufPos, + mp3buf_size + ); + } + + private static calcNeeded(gfp: LameGlobalFlags) { + let mf_needed = BLKSIZE + gfp.framesize - FFTOFFSET; + + mf_needed = Math.max(mf_needed, 512 + gfp.framesize - 32); + assert(MFSIZE >= mf_needed); + + return mf_needed; + } + + private lame_encode_buffer_sample( + gfp: LameGlobalFlags, + buffer_l: Float32Array, + buffer_r: Float32Array, + nsamples: number, + mp3buf: Uint8Array, + mp3bufPos: number, + mp3buf_size: number + ) { + const gfc = gfp.internal_flags; + + if (gfc.Class_ID !== LAME_ID) return -3; + + if (nsamples === 0) return 0; + + const mp3out = this.bs.copyMetadata(gfc, mp3buf, mp3bufPos, mp3buf_size); + if (mp3out < 0) return mp3out; + + mp3bufPos += mp3out; + let mp3size = mp3out; + + const in_buffer = [buffer_l, buffer_r] as const; + + if ( + !isCloseToEachOther(gfp.scale, 0) && + !isCloseToEachOther(gfp.scale, 1.0) + ) { + for (let i = 0; i < nsamples; ++i) { + in_buffer[0][i] *= gfp.scale; + if (gfc.channels_out === 2) in_buffer[1][i] *= gfp.scale; + } + } + + if (gfp.num_channels === 2 && gfc.channels_out === 1) { + for (let i = 0; i < nsamples; ++i) { + in_buffer[0][i] = 0.5 * (in_buffer[0][i] + in_buffer[1][i]); + in_buffer[1][i] = 0.0; + } + } + + const mf_needed = Lame.calcNeeded(gfp); + + const mfbuf: [Float32Array, Float32Array] = [gfc.mfbuf[0], gfc.mfbuf[1]]; + + let in_bufferPos = 0; + while (nsamples > 0) { + const in_buffer_ptr: [Float32Array, Float32Array] = [ + in_buffer[0], + in_buffer[1], + ]; + + const inOut = new InOut(); + Lame.fill_buffer( + gfp, + mfbuf, + in_buffer_ptr, + in_bufferPos, + nsamples, + inOut + ); + const { n_in } = inOut; + const { n_out } = inOut; + + if (gfc.findReplayGain) + if ( + this.bs.ga.analyzeSamples( + gfc.rgdata, + mfbuf[0], + gfc.mf_size, + mfbuf[1], + gfc.mf_size, + n_out, + gfc.channels_out + ) === GainAnalysis.GAIN_ANALYSIS_ERROR + ) { + return -6; + } + + nsamples -= n_in; + in_bufferPos += n_in; + + gfc.mf_size += n_out; + assert(gfc.mf_size <= MFSIZE); + + if (gfc.mf_samples_to_encode < 1) { + gfc.mf_samples_to_encode = ENCDELAY + POSTDELAY; + } + gfc.mf_samples_to_encode += n_out; + + if (gfc.mf_size >= mf_needed) { + let buf_size = mp3buf_size - mp3size; + if (mp3buf_size === 0) buf_size = 0; + + const ret = this.lame_encode_frame( + gfp, + mfbuf[0], + mfbuf[1], + mp3buf, + mp3bufPos, + buf_size + ); + + if (ret < 0) return ret; + mp3bufPos += ret; + mp3size += ret; + + gfc.mf_size -= gfp.framesize; + gfc.mf_samples_to_encode -= gfp.framesize; + for (let ch = 0; ch < gfc.channels_out; ch++) { + for (let i = 0; i < gfc.mf_size; i++) { + mfbuf[ch][i] = mfbuf[ch][i + gfp.framesize]; + } + } + } + } + assert(nsamples === 0); + + return mp3size; + } + + private lame_encode_frame( + gfp: LameGlobalFlags, + inbuf_l: Float32Array, + inbuf_r: Float32Array, + mp3buf: Uint8Array, + mp3bufPos: number, + mp3buf_size: number + ) { + const ret = this.enc.lame_encode_mp3_frame( + gfp, + inbuf_l, + inbuf_r, + mp3buf, + mp3bufPos, + mp3buf_size + ); + gfp.frameNum++; + return ret; + } + + private static fill_buffer_resample( + gfp: LameGlobalFlags, + outbuf: Float32Array, + outbufPos: number, + desired_len: number, + inbuf: Float32Array, + in_bufferPos: number, + len: number, + num_used: NumUsed, + ch: number + ) { + const gfc = gfp.internal_flags; + let i; + let j = 0; + let k; + + let bpc = gfp.out_samplerate / gcd(gfp.out_samplerate, gfp.in_samplerate); + if (bpc > BPC) bpc = BPC; + + const intratio = + Math.abs(gfc.resample_ratio - Math.floor(0.5 + gfc.resample_ratio)) < + 0.0001 + ? 1 + : 0; + let fcn = 1.0 / gfc.resample_ratio; + if (fcn > 1.0) fcn = 1.0; + let filter_l = 31; + if (filter_l % 2 === 0) --filter_l; + + filter_l += intratio; + + const BLACKSIZE = filter_l + 1; + + if (!gfc.fill_buffer_resample_init) { + gfc.inbuf_old[0] = new Float32Array(BLACKSIZE); + gfc.inbuf_old[1] = new Float32Array(BLACKSIZE); + for (i = 0; i <= 2 * bpc; ++i) + gfc.blackfilt[i] = new Float32Array(BLACKSIZE); + + gfc.itime[0] = 0; + gfc.itime[1] = 0; + + for (j = 0; j <= 2 * bpc; j++) { + let sum = 0; + const offset = (j - bpc) / (2 * bpc); + for (i = 0; i <= filter_l; i++) { + gfc.blackfilt[j][i] = blackmanWindow(i - offset, fcn, filter_l); + sum += gfc.blackfilt[j][i]; + } + for (i = 0; i <= filter_l; i++) gfc.blackfilt[j][i] /= sum; + } + gfc.fill_buffer_resample_init = true; + } + + const inbuf_old = gfc.inbuf_old[ch]; + + for (k = 0; k < desired_len; k++) { + const time0 = k * gfc.resample_ratio; + + j = Math.floor(time0 - gfc.itime[ch]); + + if (filter_l + j - filter_l / 2 >= len) break; + + const offset = time0 - gfc.itime[ch] - (j + 0.5 * (filter_l % 2)); + assert(Math.abs(offset) <= 0.501); + + const joff = Math.floor(offset * 2 * bpc + bpc + 0.5); + let xvalue = 0; + for (i = 0; i <= filter_l; ++i) { + const j2 = Math.trunc(i + j - filter_l / 2); + assert(j2 < len); + assert(j2 + BLACKSIZE >= 0); + const y = j2 < 0 ? inbuf_old[BLACKSIZE + j2] : inbuf[in_bufferPos + j2]; + xvalue += y * gfc.blackfilt[joff][i]; + } + outbuf[outbufPos + k] = xvalue; + } + + num_used.num_used = Math.min(len, filter_l + j - filter_l / 2); + + gfc.itime[ch] += num_used.num_used - k * gfc.resample_ratio; + + if (num_used.num_used >= BLACKSIZE) { + for (i = 0; i < BLACKSIZE; i++) { + inbuf_old[i] = inbuf[in_bufferPos + num_used.num_used + i - BLACKSIZE]; + } + } else { + const n_shift = BLACKSIZE - num_used.num_used; + + for (i = 0; i < n_shift; ++i) { + inbuf_old[i] = inbuf_old[i + num_used.num_used]; + } + + for (j = 0; i < BLACKSIZE; ++i, ++j) { + inbuf_old[i] = inbuf[in_bufferPos + j]; + } + + assert(j === num_used.num_used); + } + return k; + } + + private static fill_buffer( + gfp: LameGlobalFlags, + mfbuf: [Float32Array, Float32Array], + in_buffer: Float32Array[], + in_bufferPos: number, + nsamples: number, + io: InOut + ) { + const gfc = gfp.internal_flags; + + if (gfc.resample_ratio < 0.9999 || gfc.resample_ratio > 1.0001) { + for (let ch = 0; ch < gfc.channels_out; ch++) { + const numUsed = new NumUsed(); + io.n_out = Lame.fill_buffer_resample( + gfp, + mfbuf[ch], + gfc.mf_size, + gfp.framesize, + in_buffer[ch], + in_bufferPos, + nsamples, + numUsed, + ch + ); + io.n_in = numUsed.num_used; + } + } else { + io.n_out = Math.min(gfp.framesize, nsamples); + io.n_in = io.n_out; + for (let i = 0; i < io.n_out; ++i) { + mfbuf[0][gfc.mf_size + i] = in_buffer[0][in_bufferPos + i]; + if (gfc.channels_out === 2) + mfbuf[1][gfc.mf_size + i] = in_buffer[1][in_bufferPos + i]; + } + } + } + + private readonly sfBandIndex = [ + new ScaleFac( + [ + 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576, + ], + [0, 4, 8, 12, 18, 24, 32, 42, 56, 74, 100, 132, 174, 192], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0] + ), + + new ScaleFac( + [ + 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, + 278, 332, 394, 464, 540, 576, + ], + [0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 136, 180, 192], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0] + ), + new ScaleFac( + [ + 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576, + ], + [0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0] + ), + new ScaleFac( + [ + 0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, + 238, 288, 342, 418, 576, + ], + [0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0] + ), + new ScaleFac( + [ + 0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, + 230, 276, 330, 384, 576, + ], + [0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0] + ), + new ScaleFac( + [ + 0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, + 240, 296, 364, 448, 550, 576, + ], + [0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0] + ), + new ScaleFac( + [ + 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576, + ], + [ + 0 / 3, + 12 / 3, + 24 / 3, + 36 / 3, + 54 / 3, + 78 / 3, + 108 / 3, + 144 / 3, + 186 / 3, + 240 / 3, + 312 / 3, + 402 / 3, + 522 / 3, + 576 / 3, + ], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0] + ), + new ScaleFac( + [ + 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576, + ], + [ + 0 / 3, + 12 / 3, + 24 / 3, + 36 / 3, + 54 / 3, + 78 / 3, + 108 / 3, + 144 / 3, + 186 / 3, + 240 / 3, + 312 / 3, + 402 / 3, + 522 / 3, + 576 / 3, + ], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0] + ), + + new ScaleFac( + [ + 0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, + 476, 566, 568, 570, 572, 574, 576, + ], + [ + 0 / 3, + 24 / 3, + 48 / 3, + 72 / 3, + 108 / 3, + 156 / 3, + 216 / 3, + 288 / 3, + 372 / 3, + 480 / 3, + 486 / 3, + 492 / 3, + 498 / 3, + 576 / 3, + ], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0] + ), + ] as const; +} diff --git a/packages/mp3-encoder/src/lame/LameGlobalFlags.ts b/packages/mp3-encoder/src/lame/LameGlobalFlags.ts new file mode 100644 index 0000000000..656d038c8e --- /dev/null +++ b/packages/mp3-encoder/src/lame/LameGlobalFlags.ts @@ -0,0 +1,80 @@ +import { LameInternalFlags } from './LameInternalFlags'; +import { MPEGMode } from './MPEGMode'; +import type { Quality } from './Quality'; +import type { ShortBlock } from './ShortBlock'; +import { VbrMode } from './VbrMode'; +import type { SampleRate } from './sampleRates'; + +export class LameGlobalFlags { + num_channels: number; + + in_samplerate: number; + + out_samplerate: SampleRate | 0 = 0; + + scale = -1; + + quality: Quality = 3; + + mode = MPEGMode.STEREO; + + brate: number; + + compression_ratio = 0; + + quant_comp = -1; + + quant_comp_short = -1; + + experimentalY = false; + + exp_nspsytune = 0; + + preset = 0; + + VBR = VbrMode.vbr_off; + + VBR_q: Quality = 4; + + VBR_mean_bitrate_kbps = 128; + + VBR_min_bitrate_kbps = 0; + + VBR_max_bitrate_kbps = 0; + + lowpassfreq = 0; + + maskingadjust = 0; + + maskingadjust_short = 0; + + ATHtype = -1; + + ATHcurve = -1; + + ATHlower = 0; + + athaa_loudapprox = -1; + + athaa_sensitivity = 0; + + short_blocks: ShortBlock | null = null; + + interChRatio = -1; + + msfix = -1; + + version: 0 | 1 = 0; + + framesize = 0; + + frameNum = 0; + + readonly internal_flags = new LameInternalFlags(); + + constructor(channels: number, samplerate: number, kbps: number) { + this.num_channels = channels; + this.in_samplerate = samplerate; + this.brate = kbps; + } +} diff --git a/packages/mp3-encoder/src/lame/LameInternalFlags.ts b/packages/mp3-encoder/src/lame/LameInternalFlags.ts new file mode 100644 index 0000000000..73c4441b7b --- /dev/null +++ b/packages/mp3-encoder/src/lame/LameInternalFlags.ts @@ -0,0 +1,237 @@ +import { ATH } from './ATH'; +import type { CBRNewIterationLoop } from './CBRNewIterationLoop'; +import { Header } from './Header'; +import { IIISideInfo } from './IIISideInfo'; +import { III_psy_xmin } from './III_psy_xmin'; +import { NsPsy } from './NsPsy'; +import { PSY } from './PSY'; +import { ReplayGain } from './ReplayGain'; +import { ScaleFac } from './ScaleFac'; +import { + BPC, + CBANDS, + ENCDELAY, + MAX_HEADER_BUF, + MDCTDELAY, + MFSIZE, + POSTDELAY, + SBLIMIT, + SBMAX_l, + SBMAX_s, + SFBMAX, +} from './constants'; + +export class LameInternalFlags { + Class_ID = 0; + + lame_encode_frame_init = false; + + iteration_init_init = false; + + fill_buffer_resample_init = false; + + readonly mfbuf = Array.from({ length: 2 }, () => new Float32Array(MFSIZE)); + + mode_gr = 0; + + channels_in = 0; + + channels_out = 0; + + resample_ratio = 1; + + mf_samples_to_encode = ENCDELAY + POSTDELAY; + + mf_size = ENCDELAY - MDCTDELAY; + + VBR_min_bitrate = 1; + + VBR_max_bitrate = 13; + + bitrate_index = 0; + + samplerate_index = 0; + + mode_ext = 0; + + lowpass1 = 0; + + lowpass2 = 0; + + highpass1 = 0; + + highpass2 = 0; + + noise_shaping = 0; + + noise_shaping_amp = 0; + + substep_shaping = 0; + + psymodel = 0; + + noise_shaping_stop = 0; + + subblock_gain = -1; + + use_best_huffman = 0; + + full_outer_loop = 0; + + readonly l3_side = new IIISideInfo(); + + padding = 0; + + frac_SpF = 0; + + slot_lag = 0; + + nMusicCRC = 0; + + oldValue = new Int32Array([180, 180]); + + currentStep = new Int32Array([4, 4]); + + masking_lower = 1; + + readonly bv_scf = new Int32Array(576); + + readonly pseudohalf = new Int32Array(SFBMAX); + + sfb21_extra = false; + + readonly inbuf_old = Array.from({ length: 2 }, () => new Float32Array()); + + readonly blackfilt = Array.from( + { length: 2 * BPC + 1 }, + () => new Float32Array() + ); + + readonly itime = new Float64Array(2); + + sideinfo_len = 0; + + readonly sb_sample = Array.from({ length: 2 }, () => + Array.from({ length: 2 }, () => + Array.from({ length: 18 }, () => new Float32Array(SBLIMIT)) + ) + ); + + readonly amp_filter = new Float32Array(32); + + readonly header = Array.from({ length: MAX_HEADER_BUF }, () => new Header()); + + h_ptr = 0; + + w_ptr = 0; + + ResvSize = 0; + + readonly scalefac_band = new ScaleFac(); + + readonly minval_l = new Float32Array(CBANDS); + + readonly minval_s = new Float32Array(CBANDS); + + readonly nb_1 = Array.from({ length: 4 }, () => new Float32Array(CBANDS)); + + readonly nb_2 = Array.from({ length: 4 }, () => new Float32Array(CBANDS)); + + readonly nb_s1 = Array.from({ length: 4 }, () => new Float32Array(CBANDS)); + + readonly nb_s2 = Array.from({ length: 4 }, () => new Float32Array(CBANDS)); + + s3_ss: Float32Array | null = null; + + s3_ll: Float32Array | null = null; + + decay = 0; + + readonly thm = Array.from({ length: 4 }, () => new III_psy_xmin()); + + readonly en = Array.from({ length: 4 }, () => new III_psy_xmin()); + + readonly tot_ener = new Float32Array(4); + + readonly loudness_sq = Array.from({ length: 2 }, () => new Float32Array(2)); + + readonly loudness_sq_save = new Float32Array(2); + + readonly mld_l = new Float32Array(SBMAX_l); + + readonly mld_s = new Float32Array(SBMAX_s); + + readonly bm_l = new Int32Array(SBMAX_l); + + readonly bo_l = new Int32Array(SBMAX_l); + + readonly bm_s = new Int32Array(SBMAX_s); + + readonly bo_s = new Int32Array(SBMAX_s); + + npart_l = 0; + + npart_s = 0; + + readonly s3ind = Array.from({ length: CBANDS }, () => new Int32Array(2)); + + readonly s3ind_s = Array.from({ length: CBANDS }, () => new Int32Array(2)); + + readonly numlines_s = new Int32Array(CBANDS); + + readonly numlines_l = new Int32Array(CBANDS); + + readonly rnumlines_l = new Float32Array(CBANDS); + + readonly mld_cb_l = new Float32Array(CBANDS); + + readonly mld_cb_s = new Float32Array(CBANDS); + + ms_ener_ratio_old = 0; + + readonly blocktype_old = new Int32Array(2); + + readonly nsPsy = new NsPsy(); + + readonly VBR_seek_table = { + nBytesWritten: 0, + }; + + readonly ATH = new ATH(); + + readonly PSY = new PSY(); + + findReplayGain = false; + + findPeakSample = false; + + PeakSample = 0; + + RadioGain = 0; + + AudiophileGain = 0; + + readonly rgdata = new ReplayGain(); + + noclipGainChange = 0; + + noclipScale = -1.0; + + readonly bitrate_stereoMode_Hist = Array.from( + { length: 16 }, + () => new Int32Array(4 + 1) + ); + + readonly bitrate_blockType_Hist = Array.from( + { length: 16 }, + () => new Int32Array(4 + 1 + 1) + ); + + in_buffer_nsamples = 0; + + in_buffer_0: Float32Array | null = null; + + in_buffer_1: Float32Array | null = null; + + iteration_loop: CBRNewIterationLoop | null = null; +} diff --git a/packages/mp3-encoder/src/lame/MPEGMode.ts b/packages/mp3-encoder/src/lame/MPEGMode.ts new file mode 100644 index 0000000000..4f9e5ea594 --- /dev/null +++ b/packages/mp3-encoder/src/lame/MPEGMode.ts @@ -0,0 +1,13 @@ +export class MPEGMode { + private constructor(public readonly ordinal: number) {} + + static readonly STEREO = new MPEGMode(0); + + static readonly JOINT_STEREO = new MPEGMode(1); + + static readonly DUAL_CHANNEL = new MPEGMode(2); + + static readonly MONO = new MPEGMode(3); + + static readonly NOT_SET = new MPEGMode(4); +} diff --git a/packages/mp3-encoder/src/lame/MeanBits.ts b/packages/mp3-encoder/src/lame/MeanBits.ts new file mode 100644 index 0000000000..a27c47b6d2 --- /dev/null +++ b/packages/mp3-encoder/src/lame/MeanBits.ts @@ -0,0 +1,3 @@ +export class MeanBits { + constructor(public bits: number) {} +} diff --git a/packages/mp3-encoder/src/lame/Mp3Encoder.spec.ts b/packages/mp3-encoder/src/lame/Mp3Encoder.spec.ts new file mode 100644 index 0000000000..1d9258fbb9 --- /dev/null +++ b/packages/mp3-encoder/src/lame/Mp3Encoder.spec.ts @@ -0,0 +1,102 @@ +import { createHash } from 'crypto'; +import { readFile } from 'fs/promises'; +import { join } from 'path'; + +import { Mp3Encoder } from './Mp3Encoder'; +import { WavHeader } from './WavHeader'; + +let leftSampleBuffer: ArrayBufferLike; +let rightSampleBuffer: ArrayBufferLike; + +beforeAll(async () => { + const leftPath = join('testdata', 'Left44100.wav'); + const rightPath = join('testdata', 'Right44100.wav'); + + leftSampleBuffer = new Uint8Array(await readFile(leftPath)).buffer; + rightSampleBuffer = new Uint8Array(await readFile(rightPath)).buffer; +}); + +test('mono', async () => { + const waveHeader = WavHeader.readHeader(new DataView(leftSampleBuffer)); + const samples = new Int16Array( + leftSampleBuffer, + waveHeader.dataOffset, + waveHeader.dataLen / 2 + ); + + const hash = createHash('sha1'); + hash.setEncoding('hex'); + + let remainingSamples = samples.length; + + const encoder = new Mp3Encoder(); + const maxSamples = 1152; + + for (let i = 0; remainingSamples >= maxSamples; i += maxSamples) { + const left = samples.subarray(i, i + maxSamples); + const right = samples.subarray(i, i + maxSamples); + + const mp3buf = encoder.encodeBuffer(left, right); + if (mp3buf.length > 0) { + hash.write(Buffer.from(mp3buf)); + } + remainingSamples -= maxSamples; + } + + const mp3buf = encoder.flush(); + if (mp3buf.length > 0) { + hash.write(Buffer.from(mp3buf)); + } + + hash.end(); + + expect(hash.read()).toBe('ca9292fc5fea3ba4cb07c4a0ba60cf0c267b783b'); +}); + +test('stereo', async () => { + const leftWaveHeader = WavHeader.readHeader(new DataView(leftSampleBuffer)); + const rightWaveHeader = WavHeader.readHeader(new DataView(rightSampleBuffer)); + + expect(leftWaveHeader.sampleRate).toBe(rightWaveHeader.sampleRate); + + const leftSamples = new Int16Array( + leftSampleBuffer, + leftWaveHeader.dataOffset, + leftWaveHeader.dataLen / 2 + ); + const rightSamples = new Int16Array( + rightSampleBuffer, + rightWaveHeader.dataOffset, + rightWaveHeader.dataLen / 2 + ); + + expect(leftSamples.length).toBe(rightSamples.length); + + const hash = createHash('sha1'); + hash.setEncoding('hex'); + + let remainingSamples = leftSamples.length; + + const encoder = new Mp3Encoder(2, leftWaveHeader.sampleRate, 128); + const maxSamples = 1152; + + for (let i = 0; remainingSamples >= maxSamples; i += maxSamples) { + const left = leftSamples.subarray(i, i + maxSamples); + const right = rightSamples.subarray(i, i + maxSamples); + + const mp3buf = encoder.encodeBuffer(left, right); + if (mp3buf.length > 0) { + hash.write(Buffer.from(mp3buf)); + } + remainingSamples -= maxSamples; + } + + const mp3buf = encoder.flush(); + if (mp3buf.length > 0) { + hash.write(Buffer.from(mp3buf)); + } + + hash.end(); + + expect(hash.read()).toBe('ab6daeb1c563389cafacc0ec4ed963ee8ae8e8d7'); +}); diff --git a/packages/mp3-encoder/src/lame/Mp3Encoder.ts b/packages/mp3-encoder/src/lame/Mp3Encoder.ts new file mode 100644 index 0000000000..55e2ad8154 --- /dev/null +++ b/packages/mp3-encoder/src/lame/Mp3Encoder.ts @@ -0,0 +1,57 @@ +import { Lame } from './Lame'; +import type { LameGlobalFlags } from './LameGlobalFlags'; + +export class Mp3Encoder { + private readonly lame: Lame; + + private readonly gfp: LameGlobalFlags; + + private mp3buf: Uint8Array; + + private mp3buf_size: number; + + private maxSamples: number; + + constructor(channels = 1, samplerate = 44100, kbps = 128) { + this.lame = new Lame(); + + this.gfp = this.lame.lame_init(channels, samplerate, kbps); + + this.maxSamples = 1152; + this.mp3buf_size = Math.trunc(1.25 * this.maxSamples + 7200); + this.mp3buf = new Uint8Array(this.mp3buf_size); + } + + encodeBuffer(left: Int16Array, right: Int16Array = left) { + if (left.length !== right.length) { + throw new Error('left and right channel buffers must be the same length'); + } + + if (left.length > this.maxSamples) { + this.maxSamples = left.length; + this.mp3buf_size = Math.trunc(1.25 * this.maxSamples + 7200); + this.mp3buf = new Uint8Array(this.mp3buf_size); + } + + const size = this.lame.lame_encode_buffer( + this.gfp, + left, + right, + left.length, + this.mp3buf, + 0, + this.mp3buf_size + ); + return new Uint8Array(this.mp3buf.subarray(0, size)); + } + + flush() { + const size = this.lame.lame_encode_flush( + this.gfp, + this.mp3buf, + 0, + this.mp3buf_size + ); + return new Uint8Array(this.mp3buf.subarray(0, size)); + } +} diff --git a/packages/mp3-encoder/src/lame/NewMDCT.ts b/packages/mp3-encoder/src/lame/NewMDCT.ts new file mode 100644 index 0000000000..28ea42c83a --- /dev/null +++ b/packages/mp3-encoder/src/lame/NewMDCT.ts @@ -0,0 +1,1050 @@ +import type { LameInternalFlags } from './LameInternalFlags'; +import { copyArray, fillArray } from './arrays'; +import { SHORT_TYPE } from './constants'; + +export class NewMDCT { + private readonly enwindow = [ + (-4.77e-7 * 0.740951125354959) / 2.384e-6, + (1.03951e-4 * 0.740951125354959) / 2.384e-6, + (9.53674e-4 * 0.740951125354959) / 2.384e-6, + (2.841473e-3 * 0.740951125354959) / 2.384e-6, + (3.5758972e-2 * 0.740951125354959) / 2.384e-6, + (3.401756e-3 * 0.740951125354959) / 2.384e-6, + (9.83715e-4 * 0.740951125354959) / 2.384e-6, + (9.9182e-5 * 0.740951125354959) / 2.384e-6, + (1.2398e-5 * 0.740951125354959) / 2.384e-6, + (1.91212e-4 * 0.740951125354959) / 2.384e-6, + (2.283096e-3 * 0.740951125354959) / 2.384e-6, + (1.6994476e-2 * 0.740951125354959) / 2.384e-6, + (-1.8756866e-2 * 0.740951125354959) / 2.384e-6, + (-2.630711e-3 * 0.740951125354959) / 2.384e-6, + (-2.47478e-4 * 0.740951125354959) / 2.384e-6, + (-1.4782e-5 * 0.740951125354959) / 2.384e-6, + 9.063471690191471e-1, + 1.960342806591213e-1, + + (-4.77e-7 * 0.773010453362737) / 2.384e-6, + (1.05858e-4 * 0.773010453362737) / 2.384e-6, + (9.30786e-4 * 0.773010453362737) / 2.384e-6, + (2.521515e-3 * 0.773010453362737) / 2.384e-6, + (3.5694122e-2 * 0.773010453362737) / 2.384e-6, + (3.643036e-3 * 0.773010453362737) / 2.384e-6, + (9.91821e-4 * 0.773010453362737) / 2.384e-6, + (9.6321e-5 * 0.773010453362737) / 2.384e-6, + (1.1444e-5 * 0.773010453362737) / 2.384e-6, + (1.65462e-4 * 0.773010453362737) / 2.384e-6, + (2.110004e-3 * 0.773010453362737) / 2.384e-6, + (1.6112804e-2 * 0.773010453362737) / 2.384e-6, + (-1.9634247e-2 * 0.773010453362737) / 2.384e-6, + (-2.803326e-3 * 0.773010453362737) / 2.384e-6, + (-2.77042e-4 * 0.773010453362737) / 2.384e-6, + (-1.6689e-5 * 0.773010453362737) / 2.384e-6, + 8.206787908286602e-1, + 3.901806440322567e-1, + + (-4.77e-7 * 0.803207531480645) / 2.384e-6, + (1.07288e-4 * 0.803207531480645) / 2.384e-6, + (9.02653e-4 * 0.803207531480645) / 2.384e-6, + (2.174854e-3 * 0.803207531480645) / 2.384e-6, + (3.5586357e-2 * 0.803207531480645) / 2.384e-6, + (3.858566e-3 * 0.803207531480645) / 2.384e-6, + (9.95159e-4 * 0.803207531480645) / 2.384e-6, + (9.346e-5 * 0.803207531480645) / 2.384e-6, + (1.0014e-5 * 0.803207531480645) / 2.384e-6, + (1.4019e-4 * 0.803207531480645) / 2.384e-6, + (1.937389e-3 * 0.803207531480645) / 2.384e-6, + (1.5233517e-2 * 0.803207531480645) / 2.384e-6, + (-2.0506859e-2 * 0.803207531480645) / 2.384e-6, + (-2.974033e-3 * 0.803207531480645) / 2.384e-6, + (-3.0756e-4 * 0.803207531480645) / 2.384e-6, + (-1.812e-5 * 0.803207531480645) / 2.384e-6, + 7.416505462720353e-1, + 5.805693545089249e-1, + + (-4.77e-7 * 0.831469612302545) / 2.384e-6, + (1.08242e-4 * 0.831469612302545) / 2.384e-6, + (8.68797e-4 * 0.831469612302545) / 2.384e-6, + (1.800537e-3 * 0.831469612302545) / 2.384e-6, + (3.54352e-2 * 0.831469612302545) / 2.384e-6, + (4.049301e-3 * 0.831469612302545) / 2.384e-6, + (9.94205e-4 * 0.831469612302545) / 2.384e-6, + (9.0599e-5 * 0.831469612302545) / 2.384e-6, + (9.06e-6 * 0.831469612302545) / 2.384e-6, + (1.16348e-4 * 0.831469612302545) / 2.384e-6, + (1.766682e-3 * 0.831469612302545) / 2.384e-6, + (1.4358521e-2 * 0.831469612302545) / 2.384e-6, + (-2.1372318e-2 * 0.831469612302545) / 2.384e-6, + (-3.14188e-3 * 0.831469612302545) / 2.384e-6, + (-3.39031e-4 * 0.831469612302545) / 2.384e-6, + (-1.955e-5 * 0.831469612302545) / 2.384e-6, + 6.681786379192989e-1, + 7.653668647301797e-1, + + (-4.77e-7 * 0.857728610000272) / 2.384e-6, + (1.08719e-4 * 0.857728610000272) / 2.384e-6, + (8.2922e-4 * 0.857728610000272) / 2.384e-6, + (1.399517e-3 * 0.857728610000272) / 2.384e-6, + (3.5242081e-2 * 0.857728610000272) / 2.384e-6, + (4.21524e-3 * 0.857728610000272) / 2.384e-6, + (9.89437e-4 * 0.857728610000272) / 2.384e-6, + (8.7261e-5 * 0.857728610000272) / 2.384e-6, + (8.106e-6 * 0.857728610000272) / 2.384e-6, + (9.3937e-5 * 0.857728610000272) / 2.384e-6, + (1.597881e-3 * 0.857728610000272) / 2.384e-6, + (1.3489246e-2 * 0.857728610000272) / 2.384e-6, + (-2.2228718e-2 * 0.857728610000272) / 2.384e-6, + (-3.306866e-3 * 0.857728610000272) / 2.384e-6, + (-3.71456e-4 * 0.857728610000272) / 2.384e-6, + (-2.1458e-5 * 0.857728610000272) / 2.384e-6, + 5.993769336819237e-1, + 9.427934736519954e-1, + + (-4.77e-7 * 0.881921264348355) / 2.384e-6, + (1.08719e-4 * 0.881921264348355) / 2.384e-6, + (7.8392e-4 * 0.881921264348355) / 2.384e-6, + (9.71317e-4 * 0.881921264348355) / 2.384e-6, + (3.5007e-2 * 0.881921264348355) / 2.384e-6, + (4.357815e-3 * 0.881921264348355) / 2.384e-6, + (9.80854e-4 * 0.881921264348355) / 2.384e-6, + (8.3923e-5 * 0.881921264348355) / 2.384e-6, + (7.629e-6 * 0.881921264348355) / 2.384e-6, + (7.2956e-5 * 0.881921264348355) / 2.384e-6, + (1.432419e-3 * 0.881921264348355) / 2.384e-6, + (1.2627602e-2 * 0.881921264348355) / 2.384e-6, + (-2.307415e-2 * 0.881921264348355) / 2.384e-6, + (-3.467083e-3 * 0.881921264348355) / 2.384e-6, + (-4.04358e-4 * 0.881921264348355) / 2.384e-6, + (-2.3365e-5 * 0.881921264348355) / 2.384e-6, + 5.345111359507916e-1, + 1.111140466039205, + + (-9.54e-7 * 0.903989293123443) / 2.384e-6, + (1.08242e-4 * 0.903989293123443) / 2.384e-6, + (7.31945e-4 * 0.903989293123443) / 2.384e-6, + (5.15938e-4 * 0.903989293123443) / 2.384e-6, + (3.4730434e-2 * 0.903989293123443) / 2.384e-6, + (4.477024e-3 * 0.903989293123443) / 2.384e-6, + (9.68933e-4 * 0.903989293123443) / 2.384e-6, + (8.0585e-5 * 0.903989293123443) / 2.384e-6, + (6.676e-6 * 0.903989293123443) / 2.384e-6, + (5.2929e-5 * 0.903989293123443) / 2.384e-6, + (1.269817e-3 * 0.903989293123443) / 2.384e-6, + (1.1775017e-2 * 0.903989293123443) / 2.384e-6, + (-2.3907185e-2 * 0.903989293123443) / 2.384e-6, + (-3.622532e-3 * 0.903989293123443) / 2.384e-6, + (-4.38213e-4 * 0.903989293123443) / 2.384e-6, + (-2.5272e-5 * 0.903989293123443) / 2.384e-6, + 4.729647758913199e-1, + 1.268786568327291, + + (-9.54e-7 * 0.9238795325112867) / 2.384e-6, + (1.06812e-4 * 0.9238795325112867) / 2.384e-6, + (6.74248e-4 * 0.9238795325112867) / 2.384e-6, + (3.3379e-5 * 0.9238795325112867) / 2.384e-6, + (3.4412861e-2 * 0.9238795325112867) / 2.384e-6, + (4.573822e-3 * 0.9238795325112867) / 2.384e-6, + (9.54151e-4 * 0.9238795325112867) / 2.384e-6, + (7.6771e-5 * 0.9238795325112867) / 2.384e-6, + (6.199e-6 * 0.9238795325112867) / 2.384e-6, + (3.4332e-5 * 0.9238795325112867) / 2.384e-6, + (1.111031e-3 * 0.9238795325112867) / 2.384e-6, + (1.0933399e-2 * 0.9238795325112867) / 2.384e-6, + (-2.4725437e-2 * 0.9238795325112867) / 2.384e-6, + (-3.771782e-3 * 0.9238795325112867) / 2.384e-6, + (-4.72546e-4 * 0.9238795325112867) / 2.384e-6, + (-2.7657e-5 * 0.9238795325112867) / 2.384e-6, + 0.41421356237309503, + 1.414213562373095, + + (-9.54e-7 * 0.941544065183021) / 2.384e-6, + (1.05381e-4 * 0.941544065183021) / 2.384e-6, + (6.10352e-4 * 0.941544065183021) / 2.384e-6, + (-4.75883e-4 * 0.941544065183021) / 2.384e-6, + (3.405571e-2 * 0.941544065183021) / 2.384e-6, + (4.649162e-3 * 0.941544065183021) / 2.384e-6, + (9.35555e-4 * 0.941544065183021) / 2.384e-6, + (7.3433e-5 * 0.941544065183021) / 2.384e-6, + (5.245e-6 * 0.941544065183021) / 2.384e-6, + (1.7166e-5 * 0.941544065183021) / 2.384e-6, + (9.56535e-4 * 0.941544065183021) / 2.384e-6, + (1.0103703e-2 * 0.941544065183021) / 2.384e-6, + (-2.5527e-2 * 0.941544065183021) / 2.384e-6, + (-3.914356e-3 * 0.941544065183021) / 2.384e-6, + (-5.07355e-4 * 0.941544065183021) / 2.384e-6, + (-3.0041e-5 * 0.941544065183021) / 2.384e-6, + 3.578057213145241e-1, + 1.546020906725474, + + (-9.54e-7 * 0.956940335732209) / 2.384e-6, + (1.0252e-4 * 0.956940335732209) / 2.384e-6, + (5.39303e-4 * 0.956940335732209) / 2.384e-6, + (-1.011848e-3 * 0.956940335732209) / 2.384e-6, + (3.3659935e-2 * 0.956940335732209) / 2.384e-6, + (4.703045e-3 * 0.956940335732209) / 2.384e-6, + (9.15051e-4 * 0.956940335732209) / 2.384e-6, + (7.0095e-5 * 0.956940335732209) / 2.384e-6, + (4.768e-6 * 0.956940335732209) / 2.384e-6, + (9.54e-7 * 0.956940335732209) / 2.384e-6, + (8.06808e-4 * 0.956940335732209) / 2.384e-6, + (9.287834e-3 * 0.956940335732209) / 2.384e-6, + (-2.6310921e-2 * 0.956940335732209) / 2.384e-6, + (-4.048824e-3 * 0.956940335732209) / 2.384e-6, + (-5.42164e-4 * 0.956940335732209) / 2.384e-6, + (-3.2425e-5 * 0.956940335732209) / 2.384e-6, + 3.033466836073424e-1, + 1.66293922460509, + + (-1.431e-6 * 0.970031253194544) / 2.384e-6, + (9.9182e-5 * 0.970031253194544) / 2.384e-6, + (4.62532e-4 * 0.970031253194544) / 2.384e-6, + (-1.573563e-3 * 0.970031253194544) / 2.384e-6, + (3.3225536e-2 * 0.970031253194544) / 2.384e-6, + (4.737377e-3 * 0.970031253194544) / 2.384e-6, + (8.91685e-4 * 0.970031253194544) / 2.384e-6, + (6.628e-5 * 0.970031253194544) / 2.384e-6, + (4.292e-6 * 0.970031253194544) / 2.384e-6, + (-1.3828e-5 * 0.970031253194544) / 2.384e-6, + (6.6185e-4 * 0.970031253194544) / 2.384e-6, + (8.487225e-3 * 0.970031253194544) / 2.384e-6, + (-2.707386e-2 * 0.970031253194544) / 2.384e-6, + (-4.174709e-3 * 0.970031253194544) / 2.384e-6, + (-5.76973e-4 * 0.970031253194544) / 2.384e-6, + (-3.4809e-5 * 0.970031253194544) / 2.384e-6, + 2.504869601913055e-1, + 1.76384252869671, + + (-1.431e-6 * 0.98078528040323) / 2.384e-6, + (9.5367e-5 * 0.98078528040323) / 2.384e-6, + (3.78609e-4 * 0.98078528040323) / 2.384e-6, + (-2.161503e-3 * 0.98078528040323) / 2.384e-6, + (3.2754898e-2 * 0.98078528040323) / 2.384e-6, + (4.752159e-3 * 0.98078528040323) / 2.384e-6, + (8.66413e-4 * 0.98078528040323) / 2.384e-6, + (6.2943e-5 * 0.98078528040323) / 2.384e-6, + (3.815e-6 * 0.98078528040323) / 2.384e-6, + (-2.718e-5 * 0.98078528040323) / 2.384e-6, + (5.22137e-4 * 0.98078528040323) / 2.384e-6, + (7.703304e-3 * 0.98078528040323) / 2.384e-6, + (-2.7815342e-2 * 0.98078528040323) / 2.384e-6, + (-4.290581e-3 * 0.98078528040323) / 2.384e-6, + (-6.11782e-4 * 0.98078528040323) / 2.384e-6, + (-3.767e-5 * 0.98078528040323) / 2.384e-6, + 1.98912367379658e-1, + 1.847759065022573, + + (-1.907e-6 * 0.989176509964781) / 2.384e-6, + (9.0122e-5 * 0.989176509964781) / 2.384e-6, + (2.88486e-4 * 0.989176509964781) / 2.384e-6, + (-2.774239e-3 * 0.989176509964781) / 2.384e-6, + (3.224802e-2 * 0.989176509964781) / 2.384e-6, + (4.748821e-3 * 0.989176509964781) / 2.384e-6, + (8.38757e-4 * 0.989176509964781) / 2.384e-6, + (5.9605e-5 * 0.989176509964781) / 2.384e-6, + (3.338e-6 * 0.989176509964781) / 2.384e-6, + (-3.9577e-5 * 0.989176509964781) / 2.384e-6, + (3.88145e-4 * 0.989176509964781) / 2.384e-6, + (6.937027e-3 * 0.989176509964781) / 2.384e-6, + (-2.8532982e-2 * 0.989176509964781) / 2.384e-6, + (-4.395962e-3 * 0.989176509964781) / 2.384e-6, + (-6.46591e-4 * 0.989176509964781) / 2.384e-6, + (-4.0531e-5 * 0.989176509964781) / 2.384e-6, + 1.483359875383474e-1, + 1.913880671464418, + + (-1.907e-6 * 0.995184726672197) / 2.384e-6, + (8.44e-5 * 0.995184726672197) / 2.384e-6, + (1.91689e-4 * 0.995184726672197) / 2.384e-6, + (-3.411293e-3 * 0.995184726672197) / 2.384e-6, + (3.170681e-2 * 0.995184726672197) / 2.384e-6, + (4.728317e-3 * 0.995184726672197) / 2.384e-6, + (8.09669e-4 * 0.995184726672197) / 2.384e-6, + (5.579e-5 * 0.995184726672197) / 2.384e-6, + (3.338e-6 * 0.995184726672197) / 2.384e-6, + (-5.0545e-5 * 0.995184726672197) / 2.384e-6, + (2.59876e-4 * 0.995184726672197) / 2.384e-6, + (6.189346e-3 * 0.995184726672197) / 2.384e-6, + (-2.9224873e-2 * 0.995184726672197) / 2.384e-6, + (-4.489899e-3 * 0.995184726672197) / 2.384e-6, + (-6.80923e-4 * 0.995184726672197) / 2.384e-6, + (-4.3392e-5 * 0.995184726672197) / 2.384e-6, + 9.849140335716425e-2, + 1.961570560806461, + + (-2.384e-6 * 0.998795456205172) / 2.384e-6, + (7.7724e-5 * 0.998795456205172) / 2.384e-6, + (8.8215e-5 * 0.998795456205172) / 2.384e-6, + (-4.072189e-3 * 0.998795456205172) / 2.384e-6, + (3.1132698e-2 * 0.998795456205172) / 2.384e-6, + (4.691124e-3 * 0.998795456205172) / 2.384e-6, + (7.79152e-4 * 0.998795456205172) / 2.384e-6, + (5.2929e-5 * 0.998795456205172) / 2.384e-6, + (2.861e-6 * 0.998795456205172) / 2.384e-6, + (-6.0558e-5 * 0.998795456205172) / 2.384e-6, + (1.37329e-4 * 0.998795456205172) / 2.384e-6, + (5.46217e-3 * 0.998795456205172) / 2.384e-6, + (-2.989006e-2 * 0.998795456205172) / 2.384e-6, + (-4.570484e-3 * 0.998795456205172) / 2.384e-6, + (-7.14302e-4 * 0.998795456205172) / 2.384e-6, + (-4.6253e-5 * 0.998795456205172) / 2.384e-6, + 4.912684976946725e-2, + 1.990369453344394, + + (3.5780907e-2 * Math.SQRT2 * 0.5) / 2.384e-6, + (1.7876148e-2 * Math.SQRT2 * 0.5) / 2.384e-6, + (3.134727e-3 * Math.SQRT2 * 0.5) / 2.384e-6, + (2.457142e-3 * Math.SQRT2 * 0.5) / 2.384e-6, + (9.71317e-4 * Math.SQRT2 * 0.5) / 2.384e-6, + (2.18868e-4 * Math.SQRT2 * 0.5) / 2.384e-6, + (1.01566e-4 * Math.SQRT2 * 0.5) / 2.384e-6, + (1.3828e-5 * Math.SQRT2 * 0.5) / 2.384e-6, + + 3.0526638e-2 / 2.384e-6, + 4.638195e-3 / 2.384e-6, + 7.47204e-4 / 2.384e-6, + 4.9591e-5 / 2.384e-6, + 4.756451e-3 / 2.384e-6, + 2.1458e-5 / 2.384e-6, + -6.9618e-5 / 2.384e-6, + ] as const; + + private static readonly NS = 12; + + private static readonly NL = 36; + + private readonly win = [ + [ + 2.382191739347913e-13, 6.423305872147834e-13, 9.400849094049688e-13, + 1.122435026096556e-12, 1.183840321267481e-12, 1.122435026096556e-12, + 9.40084909404969e-13, 6.423305872147839e-13, 2.382191739347918e-13, + + 5.456116108943412e-12, 4.878985199565852e-12, 4.240448995017367e-12, + 3.559909094758252e-12, 2.858043359288075e-12, 2.156177623817898e-12, + 1.475637723558783e-12, 8.371015190102974e-13, 2.599706096327376e-13, + + -5.456116108943412e-12, -4.878985199565852e-12, -4.240448995017367e-12, + -3.559909094758252e-12, -2.858043359288076e-12, -2.156177623817898e-12, + -1.475637723558783e-12, -8.371015190102975e-13, -2.599706096327376e-13, + + -2.382191739347923e-13, -6.423305872147843e-13, -9.400849094049696e-13, + -1.122435026096556e-12, -1.183840321267481e-12, -1.122435026096556e-12, + -9.400849094049694e-13, -6.42330587214784e-13, -2.382191739347918e-13, + ], + [ + 2.382191739347913e-13, 6.423305872147834e-13, 9.400849094049688e-13, + 1.122435026096556e-12, 1.183840321267481e-12, 1.122435026096556e-12, + 9.400849094049688e-13, 6.423305872147841e-13, 2.382191739347918e-13, + + 5.456116108943413e-12, 4.878985199565852e-12, 4.240448995017367e-12, + 3.559909094758253e-12, 2.858043359288075e-12, 2.156177623817898e-12, + 1.475637723558782e-12, 8.371015190102975e-13, 2.599706096327376e-13, + + -5.461314069809755e-12, -4.921085770524055e-12, -4.343405037091838e-12, + -3.732668368707687e-12, -3.093523840190885e-12, -2.430835727329465e-12, + -1.734679010007751e-12, -9.748253656609281e-13, -2.797435120168326e-13, + + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.283748241799531e-13, + -4.037858874020686e-13, -2.146547464825323e-13, + ], + [ + 1.316524975873958e-1, 4.14213562373095e-1, 7.673269879789602e-1, + + 1.091308501069271, 1.303225372841206, 1.56968557711749, 1.920982126971166, + 2.414213562373094, 3.171594802363212, 4.510708503662055, + 7.595754112725146, 2.290376554843115e1, + + 0.98480775301220802032, 0.64278760968653936292, 0.34202014332566882393, + 0.93969262078590842791, -0.17364817766693030343, -0.76604444311897790243, + 0.86602540378443870761, 0.5, + + -5.144957554275265e-1, -4.717319685649723e-1, -3.133774542039019e-1, + -1.819131996109812e-1, -9.457419252642064e-2, -4.096558288530405e-2, + -1.419856857247115e-2, -3.699974673760037e-3, + + 8.574929257125442e-1, 8.817419973177052e-1, 9.496286491027329e-1, + 9.833145924917901e-1, 9.955178160675857e-1, 9.991605581781475e-1, + 9.99899195244447e-1, 9.999931550702802e-1, + ], + [ + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.283748241799531e-13, + 4.037858874020686e-13, 2.146547464825323e-13, + + 5.461314069809755e-12, 4.921085770524055e-12, 4.343405037091838e-12, + 3.732668368707687e-12, 3.093523840190885e-12, 2.430835727329466e-12, + 1.734679010007751e-12, 9.748253656609281e-13, 2.797435120168326e-13, + + -5.456116108943413e-12, -4.878985199565852e-12, -4.240448995017367e-12, + -3.559909094758253e-12, -2.858043359288075e-12, -2.156177623817898e-12, + -1.475637723558782e-12, -8.371015190102975e-13, -2.599706096327376e-13, + + -2.382191739347913e-13, -6.423305872147834e-13, -9.400849094049688e-13, + -1.122435026096556e-12, -1.183840321267481e-12, -1.122435026096556e-12, + -9.400849094049688e-13, -6.423305872147841e-13, -2.382191739347918e-13, + ], + ] as const; + + private readonly tantab_l = this.win[SHORT_TYPE]; + + private readonly cx = this.win[SHORT_TYPE]; + + private readonly ca = this.win[SHORT_TYPE]; + + private readonly cs = this.win[SHORT_TYPE]; + + private readonly order = [ + 0, 1, 16, 17, 8, 9, 24, 25, 4, 5, 20, 21, 12, 13, 28, 29, 2, 3, 18, 19, 10, + 11, 26, 27, 6, 7, 22, 23, 14, 15, 30, 31, + ] as const; + + private window_subband(x1: Float32Array, x1Pos: number, a: Float32Array) { + let wp = 10; + + let x2 = x1Pos + 238 - 14 - 286; + + for (let i = -15; i < 0; i++) { + let w; + let s; + let t; + + w = this.enwindow[wp + -10]; + s = x1[x2 + -224] * w; + t = x1[x1Pos + 224] * w; + w = this.enwindow[wp + -9]; + s += x1[x2 + -160] * w; + t += x1[x1Pos + 160] * w; + w = this.enwindow[wp + -8]; + s += x1[x2 + -96] * w; + t += x1[x1Pos + 96] * w; + w = this.enwindow[wp + -7]; + s += x1[x2 + -32] * w; + t += x1[x1Pos + 32] * w; + w = this.enwindow[wp + -6]; + s += x1[x2 + 32] * w; + t += x1[x1Pos + -32] * w; + w = this.enwindow[wp + -5]; + s += x1[x2 + 96] * w; + t += x1[x1Pos + -96] * w; + w = this.enwindow[wp + -4]; + s += x1[x2 + 160] * w; + t += x1[x1Pos + -160] * w; + w = this.enwindow[wp + -3]; + s += x1[x2 + 224] * w; + t += x1[x1Pos + -224] * w; + + w = this.enwindow[wp + -2]; + s += x1[x1Pos + -256] * w; + t -= x1[x2 + 256] * w; + w = this.enwindow[wp + -1]; + s += x1[x1Pos + -192] * w; + t -= x1[x2 + 192] * w; + w = this.enwindow[wp + 0]; + s += x1[x1Pos + -128] * w; + t -= x1[x2 + 128] * w; + w = this.enwindow[wp + 1]; + s += x1[x1Pos + -64] * w; + t -= x1[x2 + 64] * w; + w = this.enwindow[wp + 2]; + s += x1[x1Pos + 0] * w; + t -= x1[x2 + 0] * w; + w = this.enwindow[wp + 3]; + s += x1[x1Pos + 64] * w; + t -= x1[x2 + -64] * w; + w = this.enwindow[wp + 4]; + s += x1[x1Pos + 128] * w; + t -= x1[x2 + -128] * w; + w = this.enwindow[wp + 5]; + s += x1[x1Pos + 192] * w; + t -= x1[x2 + -192] * w; + + s *= this.enwindow[wp + 6]; + w = t - s; + a[30 + i * 2] = t + s; + a[31 + i * 2] = this.enwindow[wp + 7] * w; + wp += 18; + x1Pos--; + x2++; + } + + let s; + let t; + t = x1[x1Pos + -16] * this.enwindow[wp + -10]; + s = x1[x1Pos + -32] * this.enwindow[wp + -2]; + t += (x1[x1Pos + -48] - x1[x1Pos + 16]) * this.enwindow[wp + -9]; + s += x1[x1Pos + -96] * this.enwindow[wp + -1]; + t += (x1[x1Pos + -80] + x1[x1Pos + 48]) * this.enwindow[wp + -8]; + s += x1[x1Pos + -160] * this.enwindow[wp + 0]; + t += (x1[x1Pos + -112] - x1[x1Pos + 80]) * this.enwindow[wp + -7]; + s += x1[x1Pos + -224] * this.enwindow[wp + 1]; + t += (x1[x1Pos + -144] + x1[x1Pos + 112]) * this.enwindow[wp + -6]; + s -= x1[x1Pos + 32] * this.enwindow[wp + 2]; + t += (x1[x1Pos + -176] - x1[x1Pos + 144]) * this.enwindow[wp + -5]; + s -= x1[x1Pos + 96] * this.enwindow[wp + 3]; + t += (x1[x1Pos + -208] + x1[x1Pos + 176]) * this.enwindow[wp + -4]; + s -= x1[x1Pos + 160] * this.enwindow[wp + 4]; + t += (x1[x1Pos + -240] - x1[x1Pos + 208]) * this.enwindow[wp + -3]; + s -= x1[x1Pos + 224]; + + const u = s - t; + const v = s + t; + + t = a[14]; + s = a[15] - t; + + a[31] = v + t; + a[30] = u + s; + a[15] = u - s; + a[14] = v - t; + + let xr; + xr = a[28] - a[0]; + a[0] += a[28]; + a[28] = xr * this.enwindow[wp + -2 * 18 + 7]; + xr = a[29] - a[1]; + a[1] += a[29]; + a[29] = xr * this.enwindow[wp + -2 * 18 + 7]; + + xr = a[26] - a[2]; + a[2] += a[26]; + a[26] = xr * this.enwindow[wp + -4 * 18 + 7]; + xr = a[27] - a[3]; + a[3] += a[27]; + a[27] = xr * this.enwindow[wp + -4 * 18 + 7]; + + xr = a[24] - a[4]; + a[4] += a[24]; + a[24] = xr * this.enwindow[wp + -6 * 18 + 7]; + xr = a[25] - a[5]; + a[5] += a[25]; + a[25] = xr * this.enwindow[wp + -6 * 18 + 7]; + + xr = a[22] - a[6]; + a[6] += a[22]; + a[22] = xr * Math.SQRT2; + xr = a[23] - a[7]; + a[7] += a[23]; + a[23] = xr * Math.SQRT2 - a[7]; + a[7] -= a[6]; + a[22] -= a[7]; + a[23] -= a[22]; + + xr = a[6]; + a[6] = a[31] - xr; + a[31] += xr; + xr = a[7]; + a[7] = a[30] - xr; + a[30] += xr; + xr = a[22]; + a[22] = a[15] - xr; + a[15] += xr; + xr = a[23]; + a[23] = a[14] - xr; + a[14] += xr; + + xr = a[20] - a[8]; + a[8] += a[20]; + a[20] = xr * this.enwindow[wp + -10 * 18 + 7]; + xr = a[21] - a[9]; + a[9] += a[21]; + a[21] = xr * this.enwindow[wp + -10 * 18 + 7]; + + xr = a[18] - a[10]; + a[10] += a[18]; + a[18] = xr * this.enwindow[wp + -12 * 18 + 7]; + xr = a[19] - a[11]; + a[11] += a[19]; + a[19] = xr * this.enwindow[wp + -12 * 18 + 7]; + + xr = a[16] - a[12]; + a[12] += a[16]; + a[16] = xr * this.enwindow[wp + -14 * 18 + 7]; + xr = a[17] - a[13]; + a[13] += a[17]; + a[17] = xr * this.enwindow[wp + -14 * 18 + 7]; + + xr = -a[20] + a[24]; + a[20] += a[24]; + a[24] = xr * this.enwindow[wp + -12 * 18 + 7]; + xr = -a[21] + a[25]; + a[21] += a[25]; + a[25] = xr * this.enwindow[wp + -12 * 18 + 7]; + + xr = a[4] - a[8]; + a[4] += a[8]; + a[8] = xr * this.enwindow[wp + -12 * 18 + 7]; + xr = a[5] - a[9]; + a[5] += a[9]; + a[9] = xr * this.enwindow[wp + -12 * 18 + 7]; + + xr = a[0] - a[12]; + a[0] += a[12]; + a[12] = xr * this.enwindow[wp + -4 * 18 + 7]; + xr = a[1] - a[13]; + a[1] += a[13]; + a[13] = xr * this.enwindow[wp + -4 * 18 + 7]; + xr = a[16] - a[28]; + a[16] += a[28]; + a[28] = xr * this.enwindow[wp + -4 * 18 + 7]; + xr = -a[17] + a[29]; + a[17] += a[29]; + a[29] = xr * this.enwindow[wp + -4 * 18 + 7]; + + xr = Math.SQRT2 * (a[2] - a[10]); + a[2] += a[10]; + a[10] = xr; + xr = Math.SQRT2 * (a[3] - a[11]); + a[3] += a[11]; + a[11] = xr; + xr = Math.SQRT2 * (-a[18] + a[26]); + a[18] += a[26]; + a[26] = xr - a[18]; + xr = Math.SQRT2 * (-a[19] + a[27]); + a[19] += a[27]; + a[27] = xr - a[19]; + + xr = a[2]; + a[19] -= a[3]; + a[3] -= xr; + a[2] = a[31] - xr; + a[31] += xr; + xr = a[3]; + a[11] -= a[19]; + a[18] -= xr; + a[3] = a[30] - xr; + a[30] += xr; + xr = a[18]; + a[27] -= a[11]; + a[19] -= xr; + a[18] = a[15] - xr; + a[15] += xr; + + xr = a[19]; + a[10] -= xr; + a[19] = a[14] - xr; + a[14] += xr; + xr = a[10]; + a[11] -= xr; + a[10] = a[23] - xr; + a[23] += xr; + xr = a[11]; + a[26] -= xr; + a[11] = a[22] - xr; + a[22] += xr; + xr = a[26]; + a[27] -= xr; + a[26] = a[7] - xr; + a[7] += xr; + + xr = a[27]; + a[27] = a[6] - xr; + a[6] += xr; + + xr = Math.SQRT2 * (a[0] - a[4]); + a[0] += a[4]; + a[4] = xr; + xr = Math.SQRT2 * (a[1] - a[5]); + a[1] += a[5]; + a[5] = xr; + xr = Math.SQRT2 * (a[16] - a[20]); + a[16] += a[20]; + a[20] = xr; + xr = Math.SQRT2 * (a[17] - a[21]); + a[17] += a[21]; + a[21] = xr; + + xr = -Math.SQRT2 * (a[8] - a[12]); + a[8] += a[12]; + a[12] = xr - a[8]; + xr = -Math.SQRT2 * (a[9] - a[13]); + a[9] += a[13]; + a[13] = xr - a[9]; + xr = -Math.SQRT2 * (a[25] - a[29]); + a[25] += a[29]; + a[29] = xr - a[25]; + xr = -Math.SQRT2 * (a[24] + a[28]); + a[24] -= a[28]; + a[28] = xr - a[24]; + + xr = a[24] - a[16]; + a[24] = xr; + xr = a[20] - xr; + a[20] = xr; + xr = a[28] - xr; + a[28] = xr; + + xr = a[25] - a[17]; + a[25] = xr; + xr = a[21] - xr; + a[21] = xr; + xr = a[29] - xr; + a[29] = xr; + + xr = a[17] - a[1]; + a[17] = xr; + xr = a[9] - xr; + a[9] = xr; + xr = a[25] - xr; + a[25] = xr; + xr = a[5] - xr; + a[5] = xr; + xr = a[21] - xr; + a[21] = xr; + xr = a[13] - xr; + a[13] = xr; + xr = a[29] - xr; + a[29] = xr; + + xr = a[1] - a[0]; + a[1] = xr; + xr = a[16] - xr; + a[16] = xr; + xr = a[17] - xr; + a[17] = xr; + xr = a[8] - xr; + a[8] = xr; + xr = a[9] - xr; + a[9] = xr; + xr = a[24] - xr; + a[24] = xr; + xr = a[25] - xr; + a[25] = xr; + xr = a[4] - xr; + a[4] = xr; + xr = a[5] - xr; + a[5] = xr; + xr = a[20] - xr; + a[20] = xr; + xr = a[21] - xr; + a[21] = xr; + xr = a[12] - xr; + a[12] = xr; + xr = a[13] - xr; + a[13] = xr; + xr = a[28] - xr; + a[28] = xr; + xr = a[29] - xr; + a[29] = xr; + + xr = a[0]; + a[0] += a[31]; + a[31] -= xr; + xr = a[1]; + a[1] += a[30]; + a[30] -= xr; + xr = a[16]; + a[16] += a[15]; + a[15] -= xr; + xr = a[17]; + a[17] += a[14]; + a[14] -= xr; + xr = a[8]; + a[8] += a[23]; + a[23] -= xr; + xr = a[9]; + a[9] += a[22]; + a[22] -= xr; + xr = a[24]; + a[24] += a[7]; + a[7] -= xr; + xr = a[25]; + a[25] += a[6]; + a[6] -= xr; + xr = a[4]; + a[4] += a[27]; + a[27] -= xr; + xr = a[5]; + a[5] += a[26]; + a[26] -= xr; + xr = a[20]; + a[20] += a[11]; + a[11] -= xr; + xr = a[21]; + a[21] += a[10]; + a[10] -= xr; + xr = a[12]; + a[12] += a[19]; + a[19] -= xr; + xr = a[13]; + a[13] += a[18]; + a[18] -= xr; + xr = a[28]; + a[28] += a[3]; + a[3] -= xr; + xr = a[29]; + a[29] += a[2]; + a[2] -= xr; + } + + private mdct_short(inout: Float32Array, inoutPos: number) { + for (let l = 0; l < 3; l++) { + let tc0; + let tc1; + let tc2; + let ts0; + let ts1; + let ts2; + + ts0 = + inout[inoutPos + 2 * 3] * this.win[SHORT_TYPE][0] - + inout[inoutPos + 5 * 3]; + tc0 = + inout[inoutPos + 0 * 3] * this.win[SHORT_TYPE][2] - + inout[inoutPos + 3 * 3]; + tc1 = ts0 + tc0; + tc2 = ts0 - tc0; + + ts0 = + inout[inoutPos + 5 * 3] * this.win[SHORT_TYPE][0] + + inout[inoutPos + 2 * 3]; + tc0 = + inout[inoutPos + 3 * 3] * this.win[SHORT_TYPE][2] + + inout[inoutPos + 0 * 3]; + ts1 = ts0 + tc0; + ts2 = -ts0 + tc0; + + tc0 = + (inout[inoutPos + 1 * 3] * this.win[SHORT_TYPE][1] - + inout[inoutPos + 4 * 3]) * + 2.069978111953089e-11; + + ts0 = + (inout[inoutPos + 4 * 3] * this.win[SHORT_TYPE][1] + + inout[inoutPos + 1 * 3]) * + 2.069978111953089e-11; + + inout[inoutPos + 3 * 0] = tc1 * 1.90752519173728e-11 + tc0; + + inout[inoutPos + 3 * 5] = -ts1 * 1.90752519173728e-11 + ts0; + + tc2 = tc2 * 0.86602540378443870761 * 1.907525191737281e-11; + + ts1 = ts1 * 0.5 * 1.907525191737281e-11 + ts0; + inout[inoutPos + 3 * 1] = tc2 - ts1; + inout[inoutPos + 3 * 2] = tc2 + ts1; + + tc1 = tc1 * 0.5 * 1.907525191737281e-11 - tc0; + ts2 = ts2 * 0.86602540378443870761 * 1.907525191737281e-11; + + inout[inoutPos + 3 * 3] = tc1 + ts2; + inout[inoutPos + 3 * 4] = tc1 - ts2; + + inoutPos++; + } + } + + private mdct_long(out: Float32Array, outPos: number, _in: Float32Array) { + let ct; + let st; + { + const tc1 = _in[17] - _in[9]; + const tc3 = _in[15] - _in[11]; + const tc4 = _in[14] - _in[12]; + const ts5 = _in[0] + _in[8]; + let ts6 = _in[1] + _in[7]; + const ts7 = _in[2] + _in[6]; + const ts8 = _in[3] + _in[5]; + + out[outPos + 17] = ts5 + ts7 - ts8 - (ts6 - _in[4]); + st = (ts5 + ts7 - ts8) * this.cx[12 + 7] + (ts6 - _in[4]); + ct = (tc1 - tc3 - tc4) * this.cx[12 + 6]; + out[outPos + 5] = ct + st; + out[outPos + 6] = ct - st; + + const tc2 = (_in[16] - _in[10]) * this.cx[12 + 6]; + ts6 = ts6 * this.cx[12 + 7] + _in[4]; + ct = + tc1 * this.cx[12 + 0] + + tc2 + + tc3 * this.cx[12 + 1] + + tc4 * this.cx[12 + 2]; + st = + -ts5 * this.cx[12 + 4] + + ts6 - + ts7 * this.cx[12 + 5] + + ts8 * this.cx[12 + 3]; + out[outPos + 1] = ct + st; + out[outPos + 2] = ct - st; + + ct = + tc1 * this.cx[12 + 1] - + tc2 - + tc3 * this.cx[12 + 2] + + tc4 * this.cx[12 + 0]; + st = + -ts5 * this.cx[12 + 5] + + ts6 - + ts7 * this.cx[12 + 3] + + ts8 * this.cx[12 + 4]; + out[outPos + 9] = ct + st; + out[outPos + 10] = ct - st; + + ct = + tc1 * this.cx[12 + 2] - + tc2 + + tc3 * this.cx[12 + 0] - + tc4 * this.cx[12 + 1]; + st = + ts5 * this.cx[12 + 3] - + ts6 + + ts7 * this.cx[12 + 4] - + ts8 * this.cx[12 + 5]; + out[outPos + 13] = ct + st; + out[outPos + 14] = ct - st; + } + { + const ts1 = _in[8] - _in[0]; + const ts3 = _in[6] - _in[2]; + const ts4 = _in[5] - _in[3]; + const tc5 = _in[17] + _in[9]; + let tc6 = _in[16] + _in[10]; + const tc7 = _in[15] + _in[11]; + const tc8 = _in[14] + _in[12]; + + out[outPos + 0] = tc5 + tc7 + tc8 + (tc6 + _in[13]); + ct = (tc5 + tc7 + tc8) * this.cx[12 + 7] - (tc6 + _in[13]); + st = (ts1 - ts3 + ts4) * this.cx[12 + 6]; + out[outPos + 11] = ct + st; + out[outPos + 12] = ct - st; + + const ts2 = (_in[7] - _in[1]) * this.cx[12 + 6]; + tc6 = _in[13] - tc6 * this.cx[12 + 7]; + ct = + tc5 * this.cx[12 + 3] - + tc6 + + tc7 * this.cx[12 + 4] + + tc8 * this.cx[12 + 5]; + st = + ts1 * this.cx[12 + 2] + + ts2 + + ts3 * this.cx[12 + 0] + + ts4 * this.cx[12 + 1]; + out[outPos + 3] = ct + st; + out[outPos + 4] = ct - st; + + ct = + -tc5 * this.cx[12 + 5] + + tc6 - + tc7 * this.cx[12 + 3] - + tc8 * this.cx[12 + 4]; + st = + ts1 * this.cx[12 + 1] + + ts2 - + ts3 * this.cx[12 + 2] - + ts4 * this.cx[12 + 0]; + out[outPos + 7] = ct + st; + out[outPos + 8] = ct - st; + + ct = + -tc5 * this.cx[12 + 4] + + tc6 - + tc7 * this.cx[12 + 5] - + tc8 * this.cx[12 + 3]; + st = + ts1 * this.cx[12 + 0] - + ts2 + + ts3 * this.cx[12 + 1] - + ts4 * this.cx[12 + 2]; + out[outPos + 15] = ct + st; + out[outPos + 16] = ct - st; + } + } + + mdct_sub48(gfc: LameInternalFlags, w0: Float32Array, w1: Float32Array) { + let wk = w0; + let wkPos = 286; + + for (let ch = 0; ch < gfc.channels_out; ch++) { + for (let gr = 0; gr < gfc.mode_gr; gr++) { + let band; + const gi = gfc.l3_side.tt[gr][ch]; + const mdct_enc = gi.xr; + let mdct_encPos = 0; + const samp = gfc.sb_sample[ch][1 - gr]; + let sampPos = 0; + + for (let k = 0; k < 18 / 2; k++) { + this.window_subband(wk, wkPos, samp[sampPos]); + this.window_subband(wk, wkPos + 32, samp[sampPos + 1]); + sampPos += 2; + wkPos += 64; + + for (band = 1; band < 32; band += 2) { + samp[sampPos - 1][band] *= -1; + } + } + + for (band = 0; band < 32; band++, mdct_encPos += 18) { + let type = gi.block_type; + const band0 = gfc.sb_sample[ch][gr]; + const band1 = gfc.sb_sample[ch][1 - gr]; + if (gi.mixed_block_flag !== 0 && band < 2) type = 0; + if (gfc.amp_filter[band] < 1e-12) { + fillArray(mdct_enc, mdct_encPos + 0, mdct_encPos + 18, 0); + } else { + if (gfc.amp_filter[band] < 1.0) { + for (let k = 0; k < 18; k++) + band1[k][this.order[band]] *= gfc.amp_filter[band]; + } + if (type === SHORT_TYPE) { + for (let k = -NewMDCT.NS / 4; k < 0; k++) { + const w = this.win[SHORT_TYPE][k + 3]; + mdct_enc[mdct_encPos + k * 3 + 9] = + band0[9 + k][this.order[band]] * w - + band0[8 - k][this.order[band]]; + mdct_enc[mdct_encPos + k * 3 + 18] = + band0[14 - k][this.order[band]] * w + + band0[15 + k][this.order[band]]; + mdct_enc[mdct_encPos + k * 3 + 10] = + band0[15 + k][this.order[band]] * w - + band0[14 - k][this.order[band]]; + mdct_enc[mdct_encPos + k * 3 + 19] = + band1[2 - k][this.order[band]] * w + + band1[3 + k][this.order[band]]; + mdct_enc[mdct_encPos + k * 3 + 11] = + band1[3 + k][this.order[band]] * w - + band1[2 - k][this.order[band]]; + mdct_enc[mdct_encPos + k * 3 + 20] = + band1[8 - k][this.order[band]] * w + + band1[9 + k][this.order[band]]; + } + this.mdct_short(mdct_enc, mdct_encPos); + } else { + const work = new Float32Array(18); + for (let k = -NewMDCT.NL / 4; k < 0; k++) { + const a = + this.win[type][k + 27] * band1[k + 9][this.order[band]] + + this.win[type][k + 36] * band1[8 - k][this.order[band]]; + const b = + this.win[type][k + 9] * band0[k + 9][this.order[band]] - + this.win[type][k + 18] * band0[8 - k][this.order[band]]; + work[k + 9] = a - b * this.tantab_l[3 + k + 9]; + work[k + 18] = a * this.tantab_l[3 + k + 9] + b; + } + + this.mdct_long(mdct_enc, mdct_encPos, work); + } + } + + if (type !== SHORT_TYPE && band !== 0) { + for (let k = 7; k >= 0; --k) { + const bu = + mdct_enc[mdct_encPos + k] * this.ca[20 + k] + + mdct_enc[mdct_encPos + -1 - k] * this.cs[28 + k]; + const bd = + mdct_enc[mdct_encPos + k] * this.cs[28 + k] - + mdct_enc[mdct_encPos + -1 - k] * this.ca[20 + k]; + + mdct_enc[mdct_encPos + -1 - k] = bu; + mdct_enc[mdct_encPos + k] = bd; + } + } + } + } + wk = w1; + wkPos = 286; + if (gfc.mode_gr === 1) { + for (let i = 0; i < 18; i++) { + copyArray(gfc.sb_sample[ch][1][i], 0, gfc.sb_sample[ch][0][i], 0, 32); + } + } + } + } +} diff --git a/packages/mp3-encoder/src/lame/NsPsy.ts b/packages/mp3-encoder/src/lame/NsPsy.ts new file mode 100644 index 0000000000..c742e44fde --- /dev/null +++ b/packages/mp3-encoder/src/lame/NsPsy.ts @@ -0,0 +1,17 @@ +import { SBMAX_l, SBMAX_s } from './constants'; + +export class NsPsy { + last_en_subshort = Array.from({ length: 4 }, () => new Float32Array(9)); + + lastAttacks = new Int32Array(4); + + pefirbuf = new Float32Array(19); + + longfact = new Float32Array(SBMAX_l); + + shortfact = new Float32Array(SBMAX_s); + + attackthre = -1; + + attackthre_s = -1; +} diff --git a/packages/mp3-encoder/src/lame/NumUsed.ts b/packages/mp3-encoder/src/lame/NumUsed.ts new file mode 100644 index 0000000000..75abad4f18 --- /dev/null +++ b/packages/mp3-encoder/src/lame/NumUsed.ts @@ -0,0 +1,3 @@ +export class NumUsed { + num_used = 0; +} diff --git a/packages/mp3-encoder/src/lame/PSY.ts b/packages/mp3-encoder/src/lame/PSY.ts new file mode 100644 index 0000000000..964ab3f7f5 --- /dev/null +++ b/packages/mp3-encoder/src/lame/PSY.ts @@ -0,0 +1,11 @@ +import { SBMAX_l, SBMAX_s } from './constants'; + +export class PSY { + mask_adjust = 0; + + mask_adjust_short = 0; + + readonly bo_l_weight = new Float32Array(SBMAX_l); + + readonly bo_s_weight = new Float32Array(SBMAX_s); +} diff --git a/packages/mp3-encoder/src/lame/Presets.ts b/packages/mp3-encoder/src/lame/Presets.ts new file mode 100644 index 0000000000..bc877eec88 --- /dev/null +++ b/packages/mp3-encoder/src/lame/Presets.ts @@ -0,0 +1,126 @@ +import { ABRPresets } from './ABRPresets'; +import type { LameGlobalFlags } from './LameGlobalFlags'; +import type { Quality } from './Quality'; +import { VBRPresets } from './VBRPresets'; +import { VbrMode } from './VbrMode'; + +const enum Preset { + V9 = 410, + V8 = 420, + V7 = 430, + V6 = 440, + V5 = 450, + V4 = 460, + V3 = 470, + V2 = 480, + V1 = 490, + V0 = 500, + R3MIX = 1000, + STANDARD = 1001, + EXTREME = 1002, + INSANE = 1003, + STANDARD_FAST = 1004, + EXTREME_FAST = 1005, + MEDIUM = 1006, + MEDIUM_FAST = 1007, +} + +export class Presets { + private readonly vbrPresets = new VBRPresets(); + + public readonly abrPresets = new ABRPresets(); + + apply(gfp: LameGlobalFlags, preset: Preset) { + switch (preset) { + case Preset.R3MIX: { + preset = Preset.V3; + gfp.VBR = VbrMode.vbr_mtrh; + break; + } + case Preset.MEDIUM: { + preset = Preset.V4; + gfp.VBR = VbrMode.vbr_rh; + break; + } + case Preset.MEDIUM_FAST: { + preset = Preset.V4; + gfp.VBR = VbrMode.vbr_mtrh; + break; + } + case Preset.STANDARD: { + preset = Preset.V2; + gfp.VBR = VbrMode.vbr_rh; + break; + } + case Preset.STANDARD_FAST: { + preset = Preset.V2; + gfp.VBR = VbrMode.vbr_mtrh; + break; + } + case Preset.EXTREME: { + preset = Preset.V0; + gfp.VBR = VbrMode.vbr_rh; + break; + } + case Preset.EXTREME_FAST: { + preset = Preset.V0; + gfp.VBR = VbrMode.vbr_mtrh; + break; + } + case Preset.INSANE: { + gfp.preset = 320; + gfp.VBR = VbrMode.vbr_off; + this.abrPresets.apply(gfp, 320); + return 320; + } + } + + gfp.preset = preset; + + switch (preset) { + case Preset.V9: + this.vbrPresets.apply(gfp, 9); + return preset; + case Preset.V8: + this.vbrPresets.apply(gfp, 8); + return preset; + case Preset.V7: + this.vbrPresets.apply(gfp, 7); + return preset; + case Preset.V6: + this.vbrPresets.apply(gfp, 6); + return preset; + case Preset.V5: + this.vbrPresets.apply(gfp, 5); + return preset; + case Preset.V4: + this.vbrPresets.apply(gfp, 4); + return preset; + case Preset.V3: + this.vbrPresets.apply(gfp, 3); + return preset; + case Preset.V2: + this.vbrPresets.apply(gfp, 2); + return preset; + case Preset.V1: + this.vbrPresets.apply(gfp, 1); + return preset; + case Preset.V0: + this.vbrPresets.apply(gfp, 0); + return preset; + default: + break; + } + + if (preset >= 8 && preset <= 320) { + return this.abrPresets.apply(gfp, preset); + } + + gfp.preset = 0; + return preset; + } + + applyPresetFromQuality(gfp: LameGlobalFlags, quality: Quality) { + return this.apply(gfp, 500 - 10 * quality); + } +} diff --git a/packages/mp3-encoder/src/lame/PsyModel.ts b/packages/mp3-encoder/src/lame/PsyModel.ts new file mode 100644 index 0000000000..520a914205 --- /dev/null +++ b/packages/mp3-encoder/src/lame/PsyModel.ts @@ -0,0 +1,2611 @@ +import { FFT } from './FFT'; +import type { III_psy_ratio } from './III_psy_ratio'; +import type { LameGlobalFlags } from './LameGlobalFlags'; +import type { LameInternalFlags } from './LameInternalFlags'; +import { MPEGMode } from './MPEGMode'; +import { ShortBlock } from './ShortBlock'; +import { VbrMode } from './VbrMode'; +import { fillArray } from './arrays'; +import { assert } from './assert'; +import { + BLKSIZE, + BLKSIZE_s, + CBANDS, + HBLKSIZE, + HBLKSIZE_s, + LOG10, + MAX_FLOAT32_VALUE, + NORM_TYPE, + SBMAX_l, + SBMAX_s, + SHORT_TYPE, + START_TYPE, + STOP_TYPE, +} from './constants'; + +export class PsyModel { + private readonly fft = new FFT(); + + private readonly rpelev = 2; + + private readonly rpelev2 = 16; + + private readonly rpelev_s = 2; + + private readonly rpelev2_s = 16; + + private static readonly DELBARK = 0.34; + + private static readonly VO_SCALE = 1 / (14752 * 14752) / (BLKSIZE / 2); + + private readonly temporalmask_sustain_sec = 0.01; + + private static readonly NS_PREECHO_ATT0 = 0.8; + + private static readonly NS_PREECHO_ATT1 = 0.6; + + private static readonly NS_PREECHO_ATT2 = 0.3; + + private static readonly NS_MSFIX = 3.5; + + static readonly NSATTACKTHRE = 4.4; + + static readonly NSATTACKTHRE_S = 25; + + private static readonly NSFIRLEN = 21; + + private static readonly LN_TO_LOG10 = 0.2302585093; + + private psycho_loudness_approx(energy: Float32Array, gfc: LameInternalFlags) { + let loudness_power = 0.0; + + for (let i = 0; i < BLKSIZE / 2; ++i) { + loudness_power += energy[i] * gfc.ATH.eql_w[i]; + } + loudness_power *= PsyModel.VO_SCALE; + + return loudness_power; + } + + private compute_ffts( + gfp: LameGlobalFlags, + fftenergy: Float32Array, + fftenergy_s: Float32Array[], + wsamp_l: Float32Array[], + wsamp_lPos: number, + wsamp_s: Float32Array[][], + wsamp_sPos: number, + gr_out: number, + chn: number, + buffer: Float32Array[], + bufPos: number + ) { + const gfc = gfp.internal_flags; + if (chn < 2) { + this.fft.fft_long(gfc, wsamp_l[wsamp_lPos], chn, buffer, bufPos); + this.fft.fft_short(gfc, wsamp_s[wsamp_sPos], chn, buffer, bufPos); + } else if (chn === 2) { + for (let j = BLKSIZE - 1; j >= 0; --j) { + const l = wsamp_l[wsamp_lPos + 0][j]; + const r = wsamp_l[wsamp_lPos + 1][j]; + wsamp_l[wsamp_lPos + 0][j] = (l + r) * Math.SQRT2 * 0.5; + wsamp_l[wsamp_lPos + 1][j] = (l - r) * Math.SQRT2 * 0.5; + } + for (let b = 2; b >= 0; --b) { + for (let j = BLKSIZE_s - 1; j >= 0; --j) { + const l = wsamp_s[wsamp_sPos + 0][b][j]; + const r = wsamp_s[wsamp_sPos + 1][b][j]; + wsamp_s[wsamp_sPos + 0][b][j] = (l + r) * Math.SQRT2 * 0.5; + wsamp_s[wsamp_sPos + 1][b][j] = (l - r) * Math.SQRT2 * 0.5; + } + } + } + + fftenergy[0] = wsamp_l[wsamp_lPos + 0][0]; + fftenergy[0] *= fftenergy[0]; + + for (let j = BLKSIZE / 2 - 1; j >= 0; --j) { + const re = wsamp_l[wsamp_lPos + 0][BLKSIZE / 2 - j]; + const im = wsamp_l[wsamp_lPos + 0][BLKSIZE / 2 + j]; + fftenergy[BLKSIZE / 2 - j] = (re * re + im * im) * 0.5; + } + for (let b = 2; b >= 0; --b) { + fftenergy_s[b][0] = wsamp_s[wsamp_sPos + 0][b][0]; + fftenergy_s[b][0] *= fftenergy_s[b][0]; + for (let j = BLKSIZE_s / 2 - 1; j >= 0; --j) { + const re = wsamp_s[wsamp_sPos + 0][b][BLKSIZE_s / 2 - j]; + const im = wsamp_s[wsamp_sPos + 0][b][BLKSIZE_s / 2 + j]; + fftenergy_s[b][BLKSIZE_s / 2 - j] = (re * re + im * im) * 0.5; + } + } + + let totalenergy = 0.0; + for (let j = 11; j < HBLKSIZE; j++) { + totalenergy += fftenergy[j]; + } + + gfc.tot_ener[chn] = totalenergy; + + if (gfp.athaa_loudapprox === 2 && chn < 2) { + gfc.loudness_sq[gr_out][chn] = gfc.loudness_sq_save[chn]; + gfc.loudness_sq_save[chn] = this.psycho_loudness_approx(fftenergy, gfc); + } + } + + private static readonly I1LIMIT = 8; + + private static readonly I2LIMIT = 23; + + private static readonly MLIMIT = 15; + + private ma_max_i1 = 0; + + private ma_max_i2 = 0; + + private ma_max_m = 0; + + private readonly tab = [ + 1.0, 0.79433, 0.63096, 0.63096, 0.63096, 0.63096, 0.63096, 0.25119, 0.11749, + ] as const; + + private init_mask_add_max_values() { + this.ma_max_i1 = Math.pow(10, (PsyModel.I1LIMIT + 1) / 16.0); + this.ma_max_i2 = Math.pow(10, (PsyModel.I2LIMIT + 1) / 16.0); + this.ma_max_m = Math.pow(10, PsyModel.MLIMIT / 10.0); + } + + private readonly table1 = [ + 3.3246 * 3.3246, + 3.23837 * 3.23837, + 3.15437 * 3.15437, + 3.00412 * 3.00412, + 2.86103 * 2.86103, + 2.65407 * 2.65407, + 2.46209 * 2.46209, + 2.284 * 2.284, + 2.11879 * 2.11879, + 1.96552 * 1.96552, + 1.82335 * 1.82335, + 1.69146 * 1.69146, + 1.56911 * 1.56911, + 1.46658 * 1.46658, + 1.37074 * 1.37074, + 1.31036 * 1.31036, + 1.25264 * 1.25264, + 1.20648 * 1.20648, + 1.16203 * 1.16203, + 1.12765 * 1.12765, + 1.09428 * 1.09428, + 1.0659 * 1.0659, + 1.03826 * 1.03826, + 1.01895 * 1.01895, + 1, + ] as const; + + private readonly table2 = [ + 1.33352 * 1.33352, + 1.35879 * 1.35879, + 1.38454 * 1.38454, + 1.39497 * 1.39497, + 1.40548 * 1.40548, + 1.3537 * 1.3537, + 1.30382 * 1.30382, + 1.22321 * 1.22321, + 1.14758 * 1.14758, + 1, + ] as const; + + private readonly table3 = [ + 2.35364 * 2.35364, + 2.29259 * 2.29259, + 2.23313 * 2.23313, + 2.12675 * 2.12675, + 2.02545 * 2.02545, + 1.87894 * 1.87894, + 1.74303 * 1.74303, + 1.61695 * 1.61695, + 1.49999 * 1.49999, + 1.39148 * 1.39148, + 1.29083 * 1.29083, + 1.19746 * 1.19746, + 1.11084 * 1.11084, + 1.03826 * 1.03826, + ] as const; + + private mask_add( + m1: number, + m2: number, + kk: number, + b: number, + gfc: LameInternalFlags, + shortblock: number + ) { + let ratio; + + if (m2 > m1) { + if (m2 < m1 * this.ma_max_i2) ratio = m2 / m1; + else return m1 + m2; + } else { + if (m1 >= m2 * this.ma_max_i2) return m1 + m2; + ratio = m1 / m2; + } + + assert(m1 >= 0); + assert(m2 >= 0); + + m1 += m2; + + if (b + 3 <= 3 + 3) { + if (ratio >= this.ma_max_i1) { + return m1; + } + + const i = Math.trunc(Math.log10(ratio) * 16.0); + return m1 * this.table2[i]; + } + + const i = Math.trunc(Math.log10(ratio) * 16.0); + if (shortblock !== 0) { + m2 = gfc.ATH.cb_s[kk] * gfc.ATH.adjust; + } else { + m2 = gfc.ATH.cb_l[kk] * gfc.ATH.adjust; + } + assert(m2 >= 0); + if (m1 < this.ma_max_m * m2) { + if (m1 > m2) { + let f; + f = 1.0; + if (i <= 13) f = this.table3[i]; + + const r = Math.log10(m1 / m2) * (10.0 / 15.0); + return m1 * ((this.table1[i] - f) * r + f); + } + + if (i > 13) { + return m1; + } + + return m1 * this.table3[i]; + } + + return m1 * this.table1[i]; + } + + private readonly table2_ = [ + 1.33352 * 1.33352, + 1.35879 * 1.35879, + 1.38454 * 1.38454, + 1.39497 * 1.39497, + 1.40548 * 1.40548, + 1.3537 * 1.3537, + 1.30382 * 1.30382, + 1.22321 * 1.22321, + 1.14758 * 1.14758, + 1, + ] as const; + + private vbrpsy_mask_add(m1: number, m2: number, b: number) { + let ratio; + + if (m1 < 0) { + m1 = 0; + } + if (m2 < 0) { + m2 = 0; + } + if (m1 <= 0) { + return m2; + } + if (m2 <= 0) { + return m1; + } + if (m2 > m1) { + ratio = m2 / m1; + } else { + ratio = m1 / m2; + } + if (b >= -2 && b <= 2) { + if (ratio >= this.ma_max_i1) { + return m1 + m2; + } + const i = Math.trunc(Math.log10(ratio) * 16.0); + return (m1 + m2) * this.table2_[i]; + } + if (ratio < this.ma_max_i2) { + return m1 + m2; + } + if (m1 < m2) { + m1 = m2; + } + return m1; + } + + private calc_interchannel_masking(gfp: LameGlobalFlags, ratio: number) { + const gfc = gfp.internal_flags; + if (gfc.channels_out > 1) { + for (let sb = 0; sb < SBMAX_l; sb++) { + const l = gfc.thm[0].l[sb]; + const r = gfc.thm[1].l[sb]; + gfc.thm[0].l[sb] += r * ratio; + gfc.thm[1].l[sb] += l * ratio; + } + for (let sb = 0; sb < SBMAX_s; sb++) { + for (let sblock = 0; sblock < 3; sblock++) { + const l = gfc.thm[0].s[sb][sblock]; + const r = gfc.thm[1].s[sb][sblock]; + gfc.thm[0].s[sb][sblock] += r * ratio; + gfc.thm[1].s[sb][sblock] += l * ratio; + } + } + } + } + + private msfix1(gfc: LameInternalFlags) { + for (let sb = 0; sb < SBMAX_l; sb++) { + if ( + gfc.thm[0].l[sb] > 1.58 * gfc.thm[1].l[sb] || + gfc.thm[1].l[sb] > 1.58 * gfc.thm[0].l[sb] + ) + continue; + let mld = gfc.mld_l[sb] * gfc.en[3].l[sb]; + const rmid = Math.max(gfc.thm[2].l[sb], Math.min(gfc.thm[3].l[sb], mld)); + + mld = gfc.mld_l[sb] * gfc.en[2].l[sb]; + const rside = Math.max(gfc.thm[3].l[sb], Math.min(gfc.thm[2].l[sb], mld)); + gfc.thm[2].l[sb] = rmid; + gfc.thm[3].l[sb] = rside; + } + + for (let sb = 0; sb < SBMAX_s; sb++) { + for (let sblock = 0; sblock < 3; sblock++) { + if ( + gfc.thm[0].s[sb][sblock] > 1.58 * gfc.thm[1].s[sb][sblock] || + gfc.thm[1].s[sb][sblock] > 1.58 * gfc.thm[0].s[sb][sblock] + ) + continue; + let mld = gfc.mld_s[sb] * gfc.en[3].s[sb][sblock]; + const rmid = Math.max( + gfc.thm[2].s[sb][sblock], + Math.min(gfc.thm[3].s[sb][sblock], mld) + ); + + mld = gfc.mld_s[sb] * gfc.en[2].s[sb][sblock]; + const rside = Math.max( + gfc.thm[3].s[sb][sblock], + Math.min(gfc.thm[2].s[sb][sblock], mld) + ); + + gfc.thm[2].s[sb][sblock] = rmid; + gfc.thm[3].s[sb][sblock] = rside; + } + } + } + + private ns_msfix(gfc: LameInternalFlags, msfix: number, athadjust: number) { + let msfix2 = msfix; + let athlower = Math.pow(10, athadjust); + + msfix *= 2.0; + msfix2 *= 2.0; + for (let sb = 0; sb < SBMAX_l; sb++) { + let thmM; + let thmS; + const ath = gfc.ATH.cb_l[gfc.bm_l[sb]] * athlower; + const thmLR = Math.min( + Math.max(gfc.thm[0].l[sb], ath), + Math.max(gfc.thm[1].l[sb], ath) + ); + thmM = Math.max(gfc.thm[2].l[sb], ath); + thmS = Math.max(gfc.thm[3].l[sb], ath); + if (thmLR * msfix < thmM + thmS) { + const f = (thmLR * msfix2) / (thmM + thmS); + thmM *= f; + thmS *= f; + assert(thmM + thmS > 0); + } + gfc.thm[2].l[sb] = Math.min(thmM, gfc.thm[2].l[sb]); + gfc.thm[3].l[sb] = Math.min(thmS, gfc.thm[3].l[sb]); + } + + athlower *= BLKSIZE_s / BLKSIZE; + for (let sb = 0; sb < SBMAX_s; sb++) { + for (let sblock = 0; sblock < 3; sblock++) { + let thmM; + let thmS; + const ath = gfc.ATH.cb_s[gfc.bm_s[sb]] * athlower; + const thmLR = Math.min( + Math.max(gfc.thm[0].s[sb][sblock], ath), + Math.max(gfc.thm[1].s[sb][sblock], ath) + ); + thmM = Math.max(gfc.thm[2].s[sb][sblock], ath); + thmS = Math.max(gfc.thm[3].s[sb][sblock], ath); + + if (thmLR * msfix < thmM + thmS) { + const f = (thmLR * msfix) / (thmM + thmS); + thmM *= f; + thmS *= f; + assert(thmM + thmS > 0); + } + gfc.thm[2].s[sb][sblock] = Math.min(gfc.thm[2].s[sb][sblock], thmM); + gfc.thm[3].s[sb][sblock] = Math.min(gfc.thm[3].s[sb][sblock], thmS); + } + } + } + + private convert_partition2scalefac_s( + gfc: LameInternalFlags, + eb: Float32Array, + thr: Float32Array, + chn: number, + sblock: number + ) { + let sb = 0; + let b = 0; + let enn = 0.0; + let thmm = 0.0; + for (; sb < SBMAX_s; ++b, ++sb) { + const bo_s_sb = gfc.bo_s[sb]; + const { npart_s } = gfc; + const b_lim = bo_s_sb < npart_s ? bo_s_sb : npart_s; + while (b < b_lim) { + assert(eb[b] >= 0); + + assert(thr[b] >= 0); + enn += eb[b]; + thmm += thr[b]; + b++; + } + gfc.en[chn].s[sb][sblock] = enn; + gfc.thm[chn].s[sb][sblock] = thmm; + + if (b >= npart_s) { + ++sb; + break; + } + assert(eb[b] >= 0); + + assert(thr[b] >= 0); + + const w_curr = gfc.PSY.bo_s_weight[sb]; + const w_next = 1.0 - w_curr; + enn = w_curr * eb[b]; + thmm = w_curr * thr[b]; + gfc.en[chn].s[sb][sblock] += enn; + gfc.thm[chn].s[sb][sblock] += thmm; + enn = w_next * eb[b]; + thmm = w_next * thr[b]; + } + + for (; sb < SBMAX_s; ++sb) { + gfc.en[chn].s[sb][sblock] = 0; + gfc.thm[chn].s[sb][sblock] = 0; + } + } + + private convert_partition2scalefac_l( + gfc: LameInternalFlags, + eb: Float32Array, + thr: Float32Array, + chn: number + ) { + let sb = 0; + let b = 0; + let enn = 0.0; + let thmm = 0.0; + for (; sb < SBMAX_l; ++b, ++sb) { + const bo_l_sb = gfc.bo_l[sb]; + const { npart_l } = gfc; + const b_lim = bo_l_sb < npart_l ? bo_l_sb : npart_l; + while (b < b_lim) { + assert(eb[b] >= 0); + + assert(thr[b] >= 0); + enn += eb[b]; + thmm += thr[b]; + b++; + } + gfc.en[chn].l[sb] = enn; + gfc.thm[chn].l[sb] = thmm; + + if (b >= npart_l) { + ++sb; + break; + } + assert(eb[b] >= 0); + assert(thr[b] >= 0); + + const w_curr = gfc.PSY.bo_l_weight[sb]; + const w_next = 1.0 - w_curr; + enn = w_curr * eb[b]; + thmm = w_curr * thr[b]; + gfc.en[chn].l[sb] += enn; + gfc.thm[chn].l[sb] += thmm; + enn = w_next * eb[b]; + thmm = w_next * thr[b]; + } + + for (; sb < SBMAX_l; ++sb) { + gfc.en[chn].l[sb] = 0; + gfc.thm[chn].l[sb] = 0; + } + } + + private compute_masking_s( + gfp: LameGlobalFlags, + fftenergy_s: Float32Array[], + eb: Float32Array, + thr: Float32Array, + chn: number, + sblock: number + ) { + const gfc = gfp.internal_flags; + let j = 0; + let b = 0; + + for (; b < gfc.npart_s; ++b) { + let ebb = 0; + let m = 0; + const n = gfc.numlines_s[b]; + for (let i = 0; i < n; ++i, ++j) { + const el = fftenergy_s[sblock][j]; + ebb += el; + if (m < el) m = el; + } + eb[b] = ebb; + } + assert(b === gfc.npart_s); + assert(j === 129); + j = 0; + b = 0; + assert(gfc.s3_ss !== null); + for (; b < gfc.npart_s; b++) { + let kk = gfc.s3ind_s[b][0]; + let ecb = gfc.s3_ss[j++] * eb[kk]; + ++kk; + while (kk <= gfc.s3ind_s[b][1]) { + ecb += gfc.s3_ss[j] * eb[kk]; + ++j; + ++kk; + } + + const x = this.rpelev_s * gfc.nb_s1[chn][b]; + thr[b] = Math.min(ecb, x); + + if (gfc.blocktype_old[chn & 1] === SHORT_TYPE) { + const x = this.rpelev2_s * gfc.nb_s2[chn][b]; + const y = thr[b]; + thr[b] = Math.min(x, y); + } + + gfc.nb_s2[chn][b] = gfc.nb_s1[chn][b]; + gfc.nb_s1[chn][b] = ecb; + assert(thr[b] >= 0); + } + for (; b <= CBANDS; ++b) { + eb[b] = 0; + thr[b] = 0; + } + } + + private block_type_set( + gfp: LameGlobalFlags, + uselongblock: Int32Array, + blocktype_d: Int32Array, + blocktype: Int32Array + ) { + const gfc = gfp.internal_flags; + + if ( + gfp.short_blocks === ShortBlock.short_block_coupled && + !(uselongblock[0] !== 0 && uselongblock[1] !== 0) + ) { + uselongblock[0] = 0; + uselongblock[1] = 0; + } + + for (let chn = 0; chn < gfc.channels_out; chn++) { + blocktype[chn] = NORM_TYPE; + + if (gfp.short_blocks === ShortBlock.short_block_dispensed) + uselongblock[chn] = 1; + if (gfp.short_blocks === ShortBlock.short_block_forced) + uselongblock[chn] = 0; + + if (uselongblock[chn] !== 0) { + assert(gfc.blocktype_old[chn] !== START_TYPE); + if (gfc.blocktype_old[chn] === SHORT_TYPE) blocktype[chn] = STOP_TYPE; + } else { + blocktype[chn] = SHORT_TYPE; + if (gfc.blocktype_old[chn] === NORM_TYPE) { + gfc.blocktype_old[chn] = START_TYPE; + } + if (gfc.blocktype_old[chn] === STOP_TYPE) + gfc.blocktype_old[chn] = SHORT_TYPE; + } + + blocktype_d[chn] = gfc.blocktype_old[chn]; + + gfc.blocktype_old[chn] = blocktype[chn]; + } + } + + private nsInterp(x: number, y: number, r: number) { + if (r >= 1.0) { + return x; + } + if (r <= 0.0) return y; + if (y > 0.0) { + return Math.pow(x / y, r) * y; + } + + return 0.0; + } + + private readonly regcoef_s = [ + 11.8, 13.6, 17.2, 32, 46.5, 51.3, 57.5, 67.1, 71.5, 84.6, 97.6, 130, + ] as const; + + private pecalc_s(mr: III_psy_ratio, masking_lower: number) { + let pe_s = 1236.28 / 4; + for (let sb = 0; sb < SBMAX_s - 1; sb++) { + for (let sblock = 0; sblock < 3; sblock++) { + const thm = mr.thm.s[sb][sblock]; + assert(sb < this.regcoef_s.length); + if (thm > 0.0) { + const x = thm * masking_lower; + const en = mr.en.s[sb][sblock]; + if (en > x) { + if (en > x * 1e10) { + pe_s += this.regcoef_s[sb] * (10.0 * LOG10); + } else { + assert(x > 0); + pe_s += this.regcoef_s[sb] * Math.log10(en / x); + } + } + } + } + } + + return pe_s; + } + + private readonly regcoef_l = [ + 6.8, 5.8, 5.8, 6.4, 6.5, 9.9, 12.1, 14.4, 15, 18.9, 21.6, 26.9, 34.2, 40.2, + 46.8, 56.5, 60.7, 73.9, 85.7, 93.4, 126.1, + ] as const; + + private pecalc_l(mr: III_psy_ratio, masking_lower: number) { + let pe_l = 1124.23 / 4; + for (let sb = 0; sb < SBMAX_l - 1; sb++) { + const thm = mr.thm.l[sb]; + assert(sb < this.regcoef_l.length); + if (thm > 0.0) { + const x = thm * masking_lower; + const en = mr.en.l[sb]; + if (en > x) { + if (en > x * 1e10) { + pe_l += this.regcoef_l[sb] * (10.0 * LOG10); + } else { + assert(x > 0); + pe_l += this.regcoef_l[sb] * Math.log10(en / x); + } + } + } + } + return pe_l; + } + + private calc_energy( + gfc: LameInternalFlags, + fftenergy: Float32Array, + eb: Float32Array, + max: Float32Array, + avg: Float32Array + ) { + let b = 0; + let j = 0; + + for (; b < gfc.npart_l; ++b) { + let ebb = 0; + let m = 0; + let i; + for (i = 0; i < gfc.numlines_l[b]; ++i, ++j) { + const el = fftenergy[j]; + assert(el >= 0); + ebb += el; + if (m < el) m = el; + } + eb[b] = ebb; + max[b] = m; + avg[b] = ebb * gfc.rnumlines_l[b]; + assert(gfc.rnumlines_l[b] >= 0); + assert(ebb >= 0); + assert(eb[b] >= 0); + assert(max[b] >= 0); + assert(avg[b] >= 0); + } + } + + private calc_mask_index_l( + gfc: LameInternalFlags, + max: Float32Array, + avg: Float32Array, + mask_idx: Int32Array + ) { + const last_tab_entry = this.tab.length - 1; + let b = 0; + let a = avg[b] + avg[b + 1]; + assert(a >= 0); + if (a > 0.0) { + let m = max[b]; + if (m < max[b + 1]) m = max[b + 1]; + assert(gfc.numlines_l[b] + gfc.numlines_l[b + 1] - 1 > 0); + a = + (20.0 * (m * 2.0 - a)) / + (a * (gfc.numlines_l[b] + gfc.numlines_l[b + 1] - 1)); + let k = Math.trunc(a); + if (k > last_tab_entry) k = last_tab_entry; + mask_idx[b] = k; + } else { + mask_idx[b] = 0; + } + + for (b = 1; b < gfc.npart_l - 1; b++) { + a = avg[b - 1] + avg[b] + avg[b + 1]; + assert(a >= 0); + if (a > 0.0) { + let m = max[b - 1]; + if (m < max[b]) m = max[b]; + if (m < max[b + 1]) m = max[b + 1]; + assert( + gfc.numlines_l[b - 1] + + gfc.numlines_l[b] + + gfc.numlines_l[b + 1] - + 1 > + 0 + ); + a = + (20.0 * (m * 3.0 - a)) / + (a * + (gfc.numlines_l[b - 1] + + gfc.numlines_l[b] + + gfc.numlines_l[b + 1] - + 1)); + let k = Math.trunc(a); + if (k > last_tab_entry) k = last_tab_entry; + mask_idx[b] = k; + } else { + mask_idx[b] = 0; + } + } + assert(b > 0); + assert(b === gfc.npart_l - 1); + + a = avg[b - 1] + avg[b]; + assert(a >= 0); + if (a > 0.0) { + let m = max[b - 1]; + if (m < max[b]) m = max[b]; + assert(gfc.numlines_l[b - 1] + gfc.numlines_l[b] - 1 > 0); + a = + (20.0 * (m * 2.0 - a)) / + (a * (gfc.numlines_l[b - 1] + gfc.numlines_l[b] - 1)); + let k = Math.trunc(a); + if (k > last_tab_entry) k = last_tab_entry; + mask_idx[b] = k; + } else { + mask_idx[b] = 0; + } + assert(b === gfc.npart_l - 1); + } + + private readonly fircoef = [ + -8.65163e-18 * 2, + -0.00851586 * 2, + -6.74764e-18 * 2, + 0.0209036 * 2, + -3.36639e-17 * 2, + -0.0438162 * 2, + -1.54175e-17 * 2, + 0.0931738 * 2, + -5.52212e-17 * 2, + -0.313819 * 2, + ] as const; + + // eslint-disable-next-line complexity + L3psycho_anal_ns( + gfp: LameGlobalFlags, + buffer: Float32Array[], + bufPos: number, + gr_out: number, + masking_ratio: III_psy_ratio[][], + masking_MS_ratio: III_psy_ratio[][], + percep_entropy: number[], + percep_MS_entropy: number[], + energy: Float32Array, + blocktype_d: Int32Array + ) { + const gfc = gfp.internal_flags; + + const wsamp_L = Array.from({ length: 2 }, () => new Float32Array(BLKSIZE)); + const wsamp_S = Array.from({ length: 2 }, () => + Array.from({ length: 3 }, () => new Float32Array(BLKSIZE_s)) + ); + + const eb_l = new Float32Array(CBANDS + 1); + const eb_s = new Float32Array(CBANDS + 1); + const thr = new Float32Array(CBANDS + 2); + + const blocktype = new Int32Array(2); + const uselongblock = new Int32Array(2); + + let numchn; + let chn; + let b; + let i; + let j; + let k; + let sb; + let sblock; + + const ns_hpfsmpl = Array.from({ length: 2 }, () => new Float32Array(576)); + let pcfact; + const mask_idx_l = new Int32Array(CBANDS + 2); + const mask_idx_s = new Int32Array(CBANDS + 2); + + fillArray(mask_idx_s, 0); + + numchn = gfc.channels_out; + + if (gfp.mode === MPEGMode.JOINT_STEREO) { + numchn = 4; + } + + if (gfp.VBR === VbrMode.vbr_off) { + pcfact = 0; + } else if ( + gfp.VBR === VbrMode.vbr_rh || + gfp.VBR === VbrMode.vbr_mtrh || + gfp.VBR === VbrMode.vbr_mt + ) { + pcfact = 0.6; + } else pcfact = 1.0; + + for (chn = 0; chn < gfc.channels_out; chn++) { + const firbuf = buffer[chn]; + const firbufPos = bufPos + 576 - 350 - PsyModel.NSFIRLEN + 192; + assert(this.fircoef.length === (PsyModel.NSFIRLEN - 1) / 2); + for (i = 0; i < 576; i++) { + let sum1; + let sum2; + sum1 = firbuf[firbufPos + i + 10]; + sum2 = 0.0; + for (j = 0; j < (PsyModel.NSFIRLEN - 1) / 2 - 1; j += 2) { + sum1 += + this.fircoef[j] * + (firbuf[firbufPos + i + j] + + firbuf[firbufPos + i + PsyModel.NSFIRLEN - j]); + sum2 += + this.fircoef[j + 1] * + (firbuf[firbufPos + i + j + 1] + + firbuf[firbufPos + i + PsyModel.NSFIRLEN - j - 1]); + } + ns_hpfsmpl[chn][i] = sum1 + sum2; + } + masking_ratio[gr_out][chn].en.assign(gfc.en[chn]); + masking_ratio[gr_out][chn].thm.assign(gfc.thm[chn]); + if (numchn > 2) { + masking_MS_ratio[gr_out][chn].en.assign(gfc.en[chn + 2]); + masking_MS_ratio[gr_out][chn].thm.assign(gfc.thm[chn + 2]); + } + } + + for (chn = 0; chn < numchn; chn++) { + const en_subshort = new Float32Array(12); + const en_short = [0, 0, 0, 0]; + const attack_intensity = new Float32Array(12); + let ns_uselongblock = 1; + const max = new Float32Array(CBANDS); + const avg = new Float32Array(CBANDS); + const ns_attacks = [0, 0, 0, 0]; + const fftenergy = new Float32Array(HBLKSIZE); + const fftenergy_s = Array.from( + { length: 3 }, + () => new Float32Array(HBLKSIZE_s) + ); + + assert(gfc.npart_s <= CBANDS); + assert(gfc.npart_l <= CBANDS); + + for (i = 0; i < 3; i++) { + en_subshort[i] = gfc.nsPsy.last_en_subshort[chn][i + 6]; + assert(gfc.nsPsy.last_en_subshort[chn][i + 4] > 0); + attack_intensity[i] = + en_subshort[i] / gfc.nsPsy.last_en_subshort[chn][i + 4]; + en_short[0] += en_subshort[i]; + } + + if (chn === 2) { + for (i = 0; i < 576; i++) { + const l = ns_hpfsmpl[0][i]; + const r = ns_hpfsmpl[1][i]; + ns_hpfsmpl[0][i] = l + r; + ns_hpfsmpl[1][i] = l - r; + } + } + + const pf = ns_hpfsmpl[chn & 1]; + let pfPos = 0; + for (i = 0; i < 9; i++) { + const pfe = pfPos + 576 / 9; + let p = 1; + for (; pfPos < pfe; pfPos++) + if (p < Math.abs(pf[pfPos])) p = Math.abs(pf[pfPos]); + + gfc.nsPsy.last_en_subshort[chn][i] = p; + en_subshort[i + 3] = p; + en_short[1 + i / 3] += p; + if (p > en_subshort[i + 3 - 2]) { + assert(en_subshort[i + 3 - 2] > 0); + p /= en_subshort[i + 3 - 2]; + } else if (en_subshort[i + 3 - 2] > p * 10.0) { + assert(p > 0); + p = en_subshort[i + 3 - 2] / (p * 10.0); + } else p = 0.0; + attack_intensity[i + 3] = p; + } + + const attackThreshold = + chn === 3 ? gfc.nsPsy.attackthre_s : gfc.nsPsy.attackthre; + for (i = 0; i < 12; i++) { + if (ns_attacks[i / 3] === 0 && attack_intensity[i] > attackThreshold) { + ns_attacks[i / 3] = (i % 3) + 1; + } + } + + for (i = 1; i < 4; i++) { + let ratio; + if (en_short[i - 1] > en_short[i]) { + assert(en_short[i] > 0); + ratio = en_short[i - 1] / en_short[i]; + } else { + assert(en_short[i - 1] > 0); + ratio = en_short[i] / en_short[i - 1]; + } + if (ratio < 1.7) { + ns_attacks[i] = 0; + if (i === 1) ns_attacks[0] = 0; + } + } + + if (ns_attacks[0] !== 0 && gfc.nsPsy.lastAttacks[chn] !== 0) + ns_attacks[0] = 0; + + if ( + gfc.nsPsy.lastAttacks[chn] === 3 || + ns_attacks[0] + ns_attacks[1] + ns_attacks[2] + ns_attacks[3] !== 0 + ) { + ns_uselongblock = 0; + + if (ns_attacks[1] !== 0 && ns_attacks[0] !== 0) ns_attacks[1] = 0; + if (ns_attacks[2] !== 0 && ns_attacks[1] !== 0) ns_attacks[2] = 0; + if (ns_attacks[3] !== 0 && ns_attacks[2] !== 0) ns_attacks[3] = 0; + } + + if (chn < 2) { + uselongblock[chn] = ns_uselongblock; + } else if (ns_uselongblock === 0) { + uselongblock[0] = 0; + uselongblock[1] = 0; + } + + energy[chn] = gfc.tot_ener[chn]; + + const wsamp_s = wsamp_S; + const wsamp_l = wsamp_L; + this.compute_ffts( + gfp, + fftenergy, + fftenergy_s, + wsamp_l, + chn & 1, + wsamp_s, + chn & 1, + gr_out, + chn, + buffer, + bufPos + ); + + this.calc_energy(gfc, fftenergy, eb_l, max, avg); + this.calc_mask_index_l(gfc, max, avg, mask_idx_l); + + for (sblock = 0; sblock < 3; sblock++) { + let enn; + let thmm; + this.compute_masking_s(gfp, fftenergy_s, eb_s, thr, chn, sblock); + this.convert_partition2scalefac_s(gfc, eb_s, thr, chn, sblock); + + for (sb = 0; sb < SBMAX_s; sb++) { + thmm = gfc.thm[chn].s[sb][sblock]; + + thmm *= PsyModel.NS_PREECHO_ATT0; + if (ns_attacks[sblock] >= 2 || ns_attacks[sblock + 1] === 1) { + const idx = sblock !== 0 ? sblock - 1 : 2; + const p = this.nsInterp( + gfc.thm[chn].s[sb][idx], + thmm, + PsyModel.NS_PREECHO_ATT1 * pcfact + ); + thmm = Math.min(thmm, p); + } + + if (ns_attacks[sblock] === 1) { + const idx = sblock !== 0 ? sblock - 1 : 2; + const p = this.nsInterp( + gfc.thm[chn].s[sb][idx], + thmm, + PsyModel.NS_PREECHO_ATT2 * pcfact + ); + thmm = Math.min(thmm, p); + } else if ( + (sblock !== 0 && ns_attacks[sblock - 1] === 3) || + (sblock === 0 && gfc.nsPsy.lastAttacks[chn] === 3) + ) { + const idx = sblock !== 2 ? sblock + 1 : 0; + const p = this.nsInterp( + gfc.thm[chn].s[sb][idx], + thmm, + PsyModel.NS_PREECHO_ATT2 * pcfact + ); + thmm = Math.min(thmm, p); + } + + enn = + en_subshort[sblock * 3 + 3] + + en_subshort[sblock * 3 + 4] + + en_subshort[sblock * 3 + 5]; + if (en_subshort[sblock * 3 + 5] * 6 < enn) { + thmm *= 0.5; + if (en_subshort[sblock * 3 + 4] * 6 < enn) thmm *= 0.5; + } + + gfc.thm[chn].s[sb][sblock] = thmm; + } + } + gfc.nsPsy.lastAttacks[chn] = ns_attacks[2]; + + k = 0; + + assert(gfc.s3_ll !== null); + for (b = 0; b < gfc.npart_l; b++) { + let kk = gfc.s3ind[b][0]; + let eb2 = eb_l[kk] * this.tab[mask_idx_l[kk]]; + let ecb = gfc.s3_ll[k++] * eb2; + while (++kk <= gfc.s3ind[b][1]) { + eb2 = eb_l[kk] * this.tab[mask_idx_l[kk]]; + ecb = this.mask_add(ecb, gfc.s3_ll[k++] * eb2, kk, kk - b, gfc, 0); + } + ecb *= 0.158489319246111; + + if (gfc.blocktype_old[chn & 1] === SHORT_TYPE) thr[b] = ecb; + else + thr[b] = this.nsInterp( + Math.min( + ecb, + Math.min( + this.rpelev * gfc.nb_1[chn][b], + this.rpelev2 * gfc.nb_2[chn][b] + ) + ), + ecb, + pcfact + ); + + gfc.nb_2[chn][b] = gfc.nb_1[chn][b]; + gfc.nb_1[chn][b] = ecb; + } + + for (; b <= CBANDS; ++b) { + eb_l[b] = 0; + thr[b] = 0; + } + + this.convert_partition2scalefac_l(gfc, eb_l, thr, chn); + } + + if (gfp.mode === MPEGMode.STEREO || gfp.mode === MPEGMode.JOINT_STEREO) { + if (gfp.interChRatio > 0.0) { + this.calc_interchannel_masking(gfp, gfp.interChRatio); + } + } + + if (gfp.mode === MPEGMode.JOINT_STEREO) { + this.msfix1(gfc); + const { msfix } = gfp; + if (Math.abs(msfix) > 0.0) + this.ns_msfix(gfc, msfix, gfp.ATHlower * gfc.ATH.adjust); + } + + this.block_type_set(gfp, uselongblock, blocktype_d, blocktype); + + for (chn = 0; chn < numchn; chn++) { + let ppe; + let ppePos = 0; + let type; + let mr; + + if (chn > 1) { + ppe = percep_MS_entropy; + ppePos = -2; + type = NORM_TYPE; + if (blocktype_d[0] === SHORT_TYPE || blocktype_d[1] === SHORT_TYPE) + type = SHORT_TYPE; + mr = masking_MS_ratio[gr_out][chn - 2]; + } else { + ppe = percep_entropy; + ppePos = 0; + type = blocktype_d[chn]; + mr = masking_ratio[gr_out][chn]; + } + + if (type === SHORT_TYPE) + ppe[ppePos + chn] = this.pecalc_s(mr, gfc.masking_lower); + else ppe[ppePos + chn] = this.pecalc_l(mr, gfc.masking_lower); + } + return 0; + } + + private vbrpsy_compute_fft_l( + gfp: LameGlobalFlags, + buffer: Float32Array[], + bufPos: number, + chn: number, + fftenergy: Float32Array, + wsamp_l: Float32Array[], + wsamp_lPos: number + ) { + const gfc = gfp.internal_flags; + if (chn < 2) { + this.fft.fft_long(gfc, wsamp_l[wsamp_lPos], chn, buffer, bufPos); + } else if (chn === 2) { + for (let j = BLKSIZE - 1; j >= 0; --j) { + const l = wsamp_l[wsamp_lPos + 0][j]; + const r = wsamp_l[wsamp_lPos + 1][j]; + wsamp_l[wsamp_lPos + 0][j] = (l + r) * Math.SQRT2 * 0.5; + wsamp_l[wsamp_lPos + 1][j] = (l - r) * Math.SQRT2 * 0.5; + } + } + + fftenergy[0] = wsamp_l[wsamp_lPos + 0][0]; + fftenergy[0] *= fftenergy[0]; + + for (let j = BLKSIZE / 2 - 1; j >= 0; --j) { + const re = wsamp_l[wsamp_lPos + 0][BLKSIZE / 2 - j]; + const im = wsamp_l[wsamp_lPos + 0][BLKSIZE / 2 + j]; + fftenergy[BLKSIZE / 2 - j] = (re * re + im * im) * 0.5; + } + + let totalenergy = 0.0; + for (let j = 11; j < HBLKSIZE; j++) totalenergy += fftenergy[j]; + + gfc.tot_ener[chn] = totalenergy; + } + + private vbrpsy_compute_fft_s( + gfp: LameGlobalFlags, + buffer: Float32Array[], + bufPos: number, + chn: number, + sblock: number, + fftenergy_s: Float32Array[], + wsamp_s: Float32Array[][], + wsamp_sPos: number + ) { + const gfc = gfp.internal_flags; + + if (sblock === 0 && chn < 2) { + this.fft.fft_short(gfc, wsamp_s[wsamp_sPos], chn, buffer, bufPos); + } + if (chn === 2) { + for (let j = BLKSIZE_s - 1; j >= 0; --j) { + const l = wsamp_s[wsamp_sPos + 0][sblock][j]; + const r = wsamp_s[wsamp_sPos + 1][sblock][j]; + wsamp_s[wsamp_sPos + 0][sblock][j] = (l + r) * Math.SQRT2 * 0.5; + wsamp_s[wsamp_sPos + 1][sblock][j] = (l - r) * Math.SQRT2 * 0.5; + } + } + + fftenergy_s[sblock][0] = wsamp_s[wsamp_sPos + 0][sblock][0]; + fftenergy_s[sblock][0] *= fftenergy_s[sblock][0]; + for (let j = BLKSIZE_s / 2 - 1; j >= 0; --j) { + const re = wsamp_s[wsamp_sPos + 0][sblock][BLKSIZE_s / 2 - j]; + const im = wsamp_s[wsamp_sPos + 0][sblock][BLKSIZE_s / 2 + j]; + fftenergy_s[sblock][BLKSIZE_s / 2 - j] = (re * re + im * im) * 0.5; + } + } + + private vbrpsy_compute_loudness_approximation_l( + gfp: LameGlobalFlags, + gr_out: number, + chn: number, + fftenergy: Float32Array + ) { + const gfc = gfp.internal_flags; + if (gfp.athaa_loudapprox === 2 && chn < 2) { + gfc.loudness_sq[gr_out][chn] = gfc.loudness_sq_save[chn]; + gfc.loudness_sq_save[chn] = this.psycho_loudness_approx(fftenergy, gfc); + } + } + + private readonly fircoef_ = [ + -8.65163e-18 * 2, + -0.00851586 * 2, + -6.74764e-18 * 2, + 0.0209036 * 2, + -3.36639e-17 * 2, + -0.0438162 * 2, + -1.54175e-17 * 2, + 0.0931738 * 2, + -5.52212e-17 * 2, + -0.313819 * 2, + ] as const; + + // eslint-disable-next-line complexity + private vbrpsy_attack_detection( + gfp: LameGlobalFlags, + buffer: Float32Array[], + bufPos: number, + gr_out: number, + masking_ratio: III_psy_ratio[][], + masking_MS_ratio: III_psy_ratio[][], + energy: Float32Array, + sub_short_factor: Float32Array[], + ns_attacks: number[][], + uselongblock: Int32Array + ) { + const ns_hpfsmpl = Array.from({ length: 2 }, () => new Float32Array(576)); + const gfc = gfp.internal_flags; + const n_chn_out = gfc.channels_out; + + const n_chn_psy = gfp.mode === MPEGMode.JOINT_STEREO ? 4 : n_chn_out; + + for (let chn = 0; chn < n_chn_out; chn++) { + const firbuf = buffer[chn]; + const firbufPos = bufPos + 576 - 350 - PsyModel.NSFIRLEN + 192; + assert(this.fircoef_.length === (PsyModel.NSFIRLEN - 1) / 2); + for (let i = 0; i < 576; i++) { + let sum1; + let sum2; + sum1 = firbuf[firbufPos + i + 10]; + sum2 = 0.0; + for (let j = 0; j < (PsyModel.NSFIRLEN - 1) / 2 - 1; j += 2) { + sum1 += + this.fircoef_[j] * + (firbuf[firbufPos + i + j] + + firbuf[firbufPos + i + PsyModel.NSFIRLEN - j]); + sum2 += + this.fircoef_[j + 1] * + (firbuf[firbufPos + i + j + 1] + + firbuf[firbufPos + i + PsyModel.NSFIRLEN - j - 1]); + } + ns_hpfsmpl[chn][i] = sum1 + sum2; + } + masking_ratio[gr_out][chn].en.assign(gfc.en[chn]); + masking_ratio[gr_out][chn].thm.assign(gfc.thm[chn]); + if (n_chn_psy > 2) { + masking_MS_ratio[gr_out][chn].en.assign(gfc.en[chn + 2]); + masking_MS_ratio[gr_out][chn].thm.assign(gfc.thm[chn + 2]); + } + } + for (let chn = 0; chn < n_chn_psy; chn++) { + const attack_intensity = new Float32Array(12); + const en_subshort = new Float32Array(12); + const en_short = [0, 0, 0, 0]; + const pf = ns_hpfsmpl[chn & 1]; + let pfPos = 0; + const attackThreshold = + chn === 3 ? gfc.nsPsy.attackthre_s : gfc.nsPsy.attackthre; + let ns_uselongblock = 1; + + if (chn === 2) { + for (let i = 0, j = 576; j > 0; ++i, --j) { + const l = ns_hpfsmpl[0][i]; + const r = ns_hpfsmpl[1][i]; + ns_hpfsmpl[0][i] = l + r; + ns_hpfsmpl[1][i] = l - r; + } + } + + for (let i = 0; i < 3; i++) { + en_subshort[i] = gfc.nsPsy.last_en_subshort[chn][i + 6]; + assert(gfc.nsPsy.last_en_subshort[chn][i + 4] > 0); + attack_intensity[i] = + en_subshort[i] / gfc.nsPsy.last_en_subshort[chn][i + 4]; + en_short[0] += en_subshort[i]; + } + + for (let i = 0; i < 9; i++) { + const pfe = pfPos + 576 / 9; + let p = 1; + for (; pfPos < pfe; pfPos++) + if (p < Math.abs(pf[pfPos])) p = Math.abs(pf[pfPos]); + + gfc.nsPsy.last_en_subshort[chn][i] = p; + en_subshort[i + 3] = p; + en_short[1 + i / 3] += p; + if (p > en_subshort[i + 3 - 2]) { + assert(en_subshort[i + 3 - 2] > 0); + p /= en_subshort[i + 3 - 2]; + } else if (en_subshort[i + 3 - 2] > p * 10.0) { + assert(p > 0); + p = en_subshort[i + 3 - 2] / (p * 10.0); + } else { + p = 0.0; + } + attack_intensity[i + 3] = p; + } + + for (let i = 0; i < 3; ++i) { + const enn = + en_subshort[i * 3 + 3] + + en_subshort[i * 3 + 4] + + en_subshort[i * 3 + 5]; + let factor = 1; + if (en_subshort[i * 3 + 5] * 6 < enn) { + factor *= 0.5; + if (en_subshort[i * 3 + 4] * 6 < enn) { + factor *= 0.5; + } + } + sub_short_factor[chn][i] = factor; + } + + for (let i = 0; i < 12; i++) { + if ( + ns_attacks[chn][i / 3] === 0 && + attack_intensity[i] > attackThreshold + ) { + ns_attacks[chn][i / 3] = (i % 3) + 1; + } + } + + for (let i = 1; i < 4; i++) { + const u = en_short[i - 1]; + const v = en_short[i]; + const m = Math.max(u, v); + if (m < 40000) { + if (u < 1.7 * v && v < 1.7 * u) { + if (i === 1 && ns_attacks[chn][0] <= ns_attacks[chn][i]) { + ns_attacks[chn][0] = 0; + } + ns_attacks[chn][i] = 0; + } + } + } + + if (ns_attacks[chn][0] <= gfc.nsPsy.lastAttacks[chn]) { + ns_attacks[chn][0] = 0; + } + + if ( + gfc.nsPsy.lastAttacks[chn] === 3 || + ns_attacks[chn][0] + + ns_attacks[chn][1] + + ns_attacks[chn][2] + + ns_attacks[chn][3] !== + 0 + ) { + ns_uselongblock = 0; + + if (ns_attacks[chn][1] !== 0 && ns_attacks[chn][0] !== 0) { + ns_attacks[chn][1] = 0; + } + if (ns_attacks[chn][2] !== 0 && ns_attacks[chn][1] !== 0) { + ns_attacks[chn][2] = 0; + } + if (ns_attacks[chn][3] !== 0 && ns_attacks[chn][2] !== 0) { + ns_attacks[chn][3] = 0; + } + } + if (chn < 2) { + uselongblock[chn] = ns_uselongblock; + } else if (ns_uselongblock === 0) { + uselongblock[0] = 0; + uselongblock[1] = 0; + } + + energy[chn] = gfc.tot_ener[chn]; + } + } + + private vbrpsy_skip_masking_s( + gfc: LameInternalFlags, + chn: number, + sblock: number + ) { + if (sblock === 0) { + for (let b = 0; b < gfc.npart_s; b++) { + gfc.nb_s2[chn][b] = gfc.nb_s1[chn][b]; + gfc.nb_s1[chn][b] = 0; + } + } + } + + private vbrpsy_skip_masking_l(gfc: LameInternalFlags, chn: number) { + for (let b = 0; b < gfc.npart_l; b++) { + gfc.nb_2[chn][b] = gfc.nb_1[chn][b]; + gfc.nb_1[chn][b] = 0; + } + } + + private psyvbr_calc_mask_index_s( + gfc: LameInternalFlags, + max: number[], + avg: Float32Array, + mask_idx: number[] + ) { + const last_tab_entry = this.tab.length - 1; + let b = 0; + let a = avg[b] + avg[b + 1]; + assert(a >= 0); + if (a > 0.0) { + let m = max[b]; + if (m < max[b + 1]) m = max[b + 1]; + assert(gfc.numlines_s[b] + gfc.numlines_s[b + 1] - 1 > 0); + a = + (20.0 * (m * 2.0 - a)) / + (a * (gfc.numlines_s[b] + gfc.numlines_s[b + 1] - 1)); + let k = Math.trunc(a); + if (k > last_tab_entry) k = last_tab_entry; + mask_idx[b] = k; + } else { + mask_idx[b] = 0; + } + + for (b = 1; b < gfc.npart_s - 1; b++) { + a = avg[b - 1] + avg[b] + avg[b + 1]; + assert(b + 1 < gfc.npart_s); + assert(a >= 0); + if (a > 0.0) { + let m = max[b - 1]; + if (m < max[b]) m = max[b]; + if (m < max[b + 1]) m = max[b + 1]; + assert( + gfc.numlines_s[b - 1] + + gfc.numlines_s[b] + + gfc.numlines_s[b + 1] - + 1 > + 0 + ); + a = + (20.0 * (m * 3.0 - a)) / + (a * + (gfc.numlines_s[b - 1] + + gfc.numlines_s[b] + + gfc.numlines_s[b + 1] - + 1)); + let k = Math.trunc(a); + if (k > last_tab_entry) k = last_tab_entry; + mask_idx[b] = k; + } else { + mask_idx[b] = 0; + } + } + assert(b > 0); + assert(b === gfc.npart_s - 1); + + a = avg[b - 1] + avg[b]; + assert(a >= 0); + if (a > 0.0) { + let m = max[b - 1]; + if (m < max[b]) m = max[b]; + assert(gfc.numlines_s[b - 1] + gfc.numlines_s[b] - 1 > 0); + a = + (20.0 * (m * 2.0 - a)) / + (a * (gfc.numlines_s[b - 1] + gfc.numlines_s[b] - 1)); + let k = Math.trunc(a); + if (k > last_tab_entry) k = last_tab_entry; + mask_idx[b] = k; + } else { + mask_idx[b] = 0; + } + assert(b === gfc.npart_s - 1); + } + + private vbrpsy_compute_masking_s( + gfp: LameGlobalFlags, + fftenergy_s: Float32Array[], + eb: Float32Array, + thr: Float32Array, + chn: number, + sblock: number + ) { + const gfc = gfp.internal_flags; + const max = new Array<number>(CBANDS); + const avg = new Float32Array(CBANDS); + let i; + let j = 0; + let b = 0; + const mask_idx_s = new Array<number>(CBANDS); + + for (; b < gfc.npart_s; ++b) { + let ebb = 0; + let m = 0; + const n = gfc.numlines_s[b]; + for (i = 0; i < n; ++i, ++j) { + const el = fftenergy_s[sblock][j]; + ebb += el; + if (m < el) m = el; + } + eb[b] = ebb; + assert(ebb >= 0); + max[b] = m; + assert(n > 0); + avg[b] = ebb / n; + assert(avg[b] >= 0); + } + assert(b === gfc.npart_s); + assert(j === 129); + for (; b < CBANDS; ++b) { + max[b] = 0; + avg[b] = 0; + } + j = 0; + b = 0; + this.psyvbr_calc_mask_index_s(gfc, max, avg, mask_idx_s); + assert(gfc.s3_ss !== null); + for (; b < gfc.npart_s; b++) { + let kk = gfc.s3ind_s[b][0]; + const last = gfc.s3ind_s[b][1]; + let dd; + let dd_n; + let x; + let ecb; + dd = mask_idx_s[kk]; + dd_n = 1; + ecb = gfc.s3_ss[j] * eb[kk] * this.tab[mask_idx_s[kk]]; + ++j; + ++kk; + while (kk <= last) { + dd += mask_idx_s[kk]; + dd_n += 1; + x = gfc.s3_ss[j] * eb[kk] * this.tab[mask_idx_s[kk]]; + ecb = this.vbrpsy_mask_add(ecb, x, kk - b); + ++j; + ++kk; + } + dd = (1 + 2 * dd) / (2 * dd_n); + const avg_mask = this.tab[dd] * 0.5; + ecb *= avg_mask; + thr[b] = ecb; + gfc.nb_s2[chn][b] = gfc.nb_s1[chn][b]; + gfc.nb_s1[chn][b] = ecb; + + x = max[b]; + x *= gfc.minval_s[b]; + x *= avg_mask; + if (thr[b] > x) { + thr[b] = x; + } + + if (gfc.masking_lower > 1) { + thr[b] *= gfc.masking_lower; + } + if (thr[b] > eb[b]) { + thr[b] = eb[b]; + } + if (gfc.masking_lower < 1) { + thr[b] *= gfc.masking_lower; + } + + assert(thr[b] >= 0); + } + for (; b < CBANDS; ++b) { + eb[b] = 0; + thr[b] = 0; + } + } + + private vbrpsy_compute_masking_l( + gfc: LameInternalFlags, + fftenergy: Float32Array, + eb_l: Float32Array, + thr: Float32Array, + chn: number + ) { + const max = new Float32Array(CBANDS); + const avg = new Float32Array(CBANDS); + const mask_idx_l = new Int32Array(CBANDS + 2); + let b; + + this.calc_energy(gfc, fftenergy, eb_l, max, avg); + this.calc_mask_index_l(gfc, max, avg, mask_idx_l); + + let k = 0; + assert(gfc.s3_ll !== null); + for (b = 0; b < gfc.npart_l; b++) { + let x; + let ecb; + let t; + + let kk = gfc.s3ind[b][0]; + const last = gfc.s3ind[b][1]; + let dd = 0; + let dd_n = 0; + dd = mask_idx_l[kk]; + dd_n += 1; + ecb = gfc.s3_ll[k] * eb_l[kk] * this.tab[mask_idx_l[kk]]; + ++k; + ++kk; + while (kk <= last) { + dd += mask_idx_l[kk]; + dd_n += 1; + x = gfc.s3_ll[k] * eb_l[kk] * this.tab[mask_idx_l[kk]]; + t = this.vbrpsy_mask_add(ecb, x, kk - b); + ecb = t; + ++k; + ++kk; + } + dd = (1 + 2 * dd) / (2 * dd_n); + const avg_mask = this.tab[dd] * 0.5; + ecb *= avg_mask; + + if (gfc.blocktype_old[chn & 0x01] === SHORT_TYPE) { + const ecb_limit = this.rpelev * gfc.nb_1[chn][b]; + if (ecb_limit > 0) { + thr[b] = Math.min(ecb, ecb_limit); + } else { + thr[b] = Math.min(ecb, eb_l[b] * PsyModel.NS_PREECHO_ATT2); + } + } else { + let ecb_limit_2 = this.rpelev2 * gfc.nb_2[chn][b]; + let ecb_limit_1 = this.rpelev * gfc.nb_1[chn][b]; + let ecb_limit; + if (ecb_limit_2 <= 0) { + ecb_limit_2 = ecb; + } + if (ecb_limit_1 <= 0) { + ecb_limit_1 = ecb; + } + if (gfc.blocktype_old[chn & 0x01] === NORM_TYPE) { + ecb_limit = Math.min(ecb_limit_1, ecb_limit_2); + } else { + ecb_limit = ecb_limit_1; + } + thr[b] = Math.min(ecb, ecb_limit); + } + gfc.nb_2[chn][b] = gfc.nb_1[chn][b]; + gfc.nb_1[chn][b] = ecb; + + x = max[b]; + x *= gfc.minval_l[b]; + x *= avg_mask; + if (thr[b] > x) { + thr[b] = x; + } + + if (gfc.masking_lower > 1) { + thr[b] *= gfc.masking_lower; + } + if (thr[b] > eb_l[b]) { + thr[b] = eb_l[b]; + } + if (gfc.masking_lower < 1) { + thr[b] *= gfc.masking_lower; + } + assert(thr[b] >= 0); + } + for (; b < CBANDS; ++b) { + eb_l[b] = 0; + thr[b] = 0; + } + } + + private vbrpsy_compute_block_type( + gfp: LameGlobalFlags, + uselongblock: Int32Array + ) { + const gfc = gfp.internal_flags; + + if ( + gfp.short_blocks === ShortBlock.short_block_coupled && + !(uselongblock[0] !== 0 && uselongblock[1] !== 0) + ) { + uselongblock[0] = 0; + uselongblock[1] = 0; + } + + for (let chn = 0; chn < gfc.channels_out; chn++) { + if (gfp.short_blocks === ShortBlock.short_block_dispensed) { + uselongblock[chn] = 1; + } + if (gfp.short_blocks === ShortBlock.short_block_forced) { + uselongblock[chn] = 0; + } + } + } + + private vbrpsy_apply_block_type( + gfp: LameGlobalFlags, + uselongblock: Int32Array, + blocktype_d: Int32Array + ) { + const gfc = gfp.internal_flags; + + for (let chn = 0; chn < gfc.channels_out; chn++) { + let blocktype = NORM_TYPE; + + if (uselongblock[chn] !== 0) { + assert(gfc.blocktype_old[chn] !== START_TYPE); + if (gfc.blocktype_old[chn] === SHORT_TYPE) blocktype = STOP_TYPE; + } else { + blocktype = SHORT_TYPE; + if (gfc.blocktype_old[chn] === NORM_TYPE) { + gfc.blocktype_old[chn] = START_TYPE; + } + if (gfc.blocktype_old[chn] === STOP_TYPE) + gfc.blocktype_old[chn] = SHORT_TYPE; + } + + blocktype_d[chn] = gfc.blocktype_old[chn]; + + gfc.blocktype_old[chn] = blocktype; + } + } + + private vbrpsy_compute_MS_thresholds( + eb: Float32Array[], + thr: Float32Array[], + cb_mld: Float32Array, + ath_cb: Float32Array, + athadjust: number, + msfix: number, + n: number + ) { + const msfix2 = msfix * 2; + const athlower = msfix > 0 ? Math.pow(10, athadjust) : 1; + let rside; + let rmid; + for (let b = 0; b < n; ++b) { + const ebM = eb[2][b]; + const ebS = eb[3][b]; + const thmL = thr[0][b]; + const thmR = thr[1][b]; + let thmM = thr[2][b]; + let thmS = thr[3][b]; + + if (thmL <= 1.58 * thmR && thmR <= 1.58 * thmL) { + const mld_m = cb_mld[b] * ebS; + const mld_s = cb_mld[b] * ebM; + rmid = Math.max(thmM, Math.min(thmS, mld_m)); + rside = Math.max(thmS, Math.min(thmM, mld_s)); + } else { + rmid = thmM; + rside = thmS; + } + if (msfix > 0) { + const ath = ath_cb[b] * athlower; + const thmLR = Math.min(Math.max(thmL, ath), Math.max(thmR, ath)); + thmM = Math.max(rmid, ath); + thmS = Math.max(rside, ath); + const thmMS = thmM + thmS; + if (thmMS > 0 && thmLR * msfix2 < thmMS) { + const f = (thmLR * msfix2) / thmMS; + thmM *= f; + thmS *= f; + assert(thmMS > 0); + } + rmid = Math.min(thmM, rmid); + rside = Math.min(thmS, rside); + } + if (rmid > ebM) { + rmid = ebM; + } + if (rside > ebS) { + rside = ebS; + } + thr[2][b] = rmid; + thr[3][b] = rside; + } + } + + // eslint-disable-next-line complexity + L3psycho_anal_vbr( + gfp: LameGlobalFlags, + buffer: Float32Array[], + bufPos: number, + gr_out: number, + masking_ratio: III_psy_ratio[][], + masking_MS_ratio: III_psy_ratio[][], + percep_entropy: number[], + percep_MS_entropy: number[], + energy: Float32Array, + blocktype_d: Int32Array + ) { + const gfc = gfp.internal_flags; + + let wsamp_l; + let wsamp_s; + const fftenergy = new Float32Array(HBLKSIZE); + const fftenergy_s = Array.from( + { length: 3 }, + () => new Float32Array(HBLKSIZE_s) + ); + const wsamp_L = Array.from({ length: 2 }, () => new Float32Array(BLKSIZE)); + const wsamp_S = Array.from({ length: 2 }, () => + Array.from({ length: 3 }, () => new Float32Array(BLKSIZE_s)) + ); + const eb = Array.from({ length: 4 }, () => new Float32Array(CBANDS)); + const thr = Array.from({ length: 4 }, () => new Float32Array(CBANDS)); + const sub_short_factor = Array.from( + { length: 4 }, + () => new Float32Array(3) + ); + const pcfact = 0.6; + + const ns_attacks = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ]; + const uselongblock = new Int32Array(2); + + const n_chn_psy = gfp.mode === MPEGMode.JOINT_STEREO ? 4 : gfc.channels_out; + + this.vbrpsy_attack_detection( + gfp, + buffer, + bufPos, + gr_out, + masking_ratio, + masking_MS_ratio, + energy, + sub_short_factor, + ns_attacks, + uselongblock + ); + + this.vbrpsy_compute_block_type(gfp, uselongblock); + + for (let chn = 0; chn < n_chn_psy; chn++) { + const ch01 = chn & 0x01; + wsamp_l = wsamp_L; + this.vbrpsy_compute_fft_l( + gfp, + buffer, + bufPos, + chn, + fftenergy, + wsamp_l, + ch01 + ); + + this.vbrpsy_compute_loudness_approximation_l(gfp, gr_out, chn, fftenergy); + + if (uselongblock[ch01] !== 0) { + this.vbrpsy_compute_masking_l(gfc, fftenergy, eb[chn], thr[chn], chn); + } else { + this.vbrpsy_skip_masking_l(gfc, chn); + } + } + if (uselongblock[0] + uselongblock[1] === 2) { + if (gfp.mode === MPEGMode.JOINT_STEREO) { + this.vbrpsy_compute_MS_thresholds( + eb, + thr, + gfc.mld_cb_l, + gfc.ATH.cb_l, + gfp.ATHlower * gfc.ATH.adjust, + gfp.msfix, + gfc.npart_l + ); + } + } + + for (let chn = 0; chn < n_chn_psy; chn++) { + const ch01 = chn & 0x01; + if (uselongblock[ch01] !== 0) { + this.convert_partition2scalefac_l(gfc, eb[chn], thr[chn], chn); + } + } + + for (let sblock = 0; sblock < 3; sblock++) { + for (let chn = 0; chn < n_chn_psy; ++chn) { + const ch01 = chn & 0x01; + + if (uselongblock[ch01] !== 0) { + this.vbrpsy_skip_masking_s(gfc, chn, sblock); + } else { + wsamp_s = wsamp_S; + this.vbrpsy_compute_fft_s( + gfp, + buffer, + bufPos, + chn, + sblock, + fftenergy_s, + wsamp_s, + ch01 + ); + this.vbrpsy_compute_masking_s( + gfp, + fftenergy_s, + eb[chn], + thr[chn], + chn, + sblock + ); + } + } + if (uselongblock[0] + uselongblock[1] === 0) { + if (gfp.mode === MPEGMode.JOINT_STEREO) { + this.vbrpsy_compute_MS_thresholds( + eb, + thr, + gfc.mld_cb_s, + gfc.ATH.cb_s, + gfp.ATHlower * gfc.ATH.adjust, + gfp.msfix, + gfc.npart_s + ); + } + } + + for (let chn = 0; chn < n_chn_psy; ++chn) { + const ch01 = chn & 0x01; + if (uselongblock[ch01] === 0) { + this.convert_partition2scalefac_s( + gfc, + eb[chn], + thr[chn], + chn, + sblock + ); + } + } + } + + for (let chn = 0; chn < n_chn_psy; chn++) { + const ch01 = chn & 0x01; + + if (uselongblock[ch01] !== 0) { + continue; + } + for (let sb = 0; sb < SBMAX_s; sb++) { + const new_thmm = new Float32Array(3); + for (let sblock = 0; sblock < 3; sblock++) { + let thmm = gfc.thm[chn].s[sb][sblock]; + thmm *= PsyModel.NS_PREECHO_ATT0; + + if ( + ns_attacks[chn][sblock] >= 2 || + ns_attacks[chn][sblock + 1] === 1 + ) { + const idx = sblock !== 0 ? sblock - 1 : 2; + const p = this.nsInterp( + gfc.thm[chn].s[sb][idx], + thmm, + PsyModel.NS_PREECHO_ATT1 * pcfact + ); + thmm = Math.min(thmm, p); + } else if (ns_attacks[chn][sblock] === 1) { + const idx = sblock !== 0 ? sblock - 1 : 2; + const p = this.nsInterp( + gfc.thm[chn].s[sb][idx], + thmm, + PsyModel.NS_PREECHO_ATT2 * pcfact + ); + thmm = Math.min(thmm, p); + } else if ( + (sblock !== 0 && ns_attacks[chn][sblock - 1] === 3) || + (sblock === 0 && gfc.nsPsy.lastAttacks[chn] === 3) + ) { + const idx = sblock !== 2 ? sblock + 1 : 0; + const p = this.nsInterp( + gfc.thm[chn].s[sb][idx], + thmm, + PsyModel.NS_PREECHO_ATT2 * pcfact + ); + thmm = Math.min(thmm, p); + } + + thmm *= sub_short_factor[chn][sblock]; + + new_thmm[sblock] = thmm; + } + for (let sblock = 0; sblock < 3; sblock++) { + gfc.thm[chn].s[sb][sblock] = new_thmm[sblock]; + } + } + } + + for (let chn = 0; chn < n_chn_psy; chn++) { + gfc.nsPsy.lastAttacks[chn] = ns_attacks[chn][2]; + } + + this.vbrpsy_apply_block_type(gfp, uselongblock, blocktype_d); + + for (let chn = 0; chn < n_chn_psy; chn++) { + let ppe; + let ppePos; + let type; + let mr; + + if (chn > 1) { + ppe = percep_MS_entropy; + ppePos = -2; + type = NORM_TYPE; + if (blocktype_d[0] === SHORT_TYPE || blocktype_d[1] === SHORT_TYPE) + type = SHORT_TYPE; + mr = masking_MS_ratio[gr_out][chn - 2]; + } else { + ppe = percep_entropy; + ppePos = 0; + type = blocktype_d[chn]; + mr = masking_ratio[gr_out][chn]; + } + + if (type === SHORT_TYPE) { + ppe[ppePos + chn] = this.pecalc_s(mr, gfc.masking_lower); + } else { + ppe[ppePos + chn] = this.pecalc_l(mr, gfc.masking_lower); + } + } + return 0; + } + + private s3_func_x(bark: number, hf_slope: number) { + const tempx = bark; + let tempy; + + if (tempx >= 0) { + tempy = -tempx * 27; + } else { + tempy = tempx * hf_slope; + } + if (tempy <= -72.0) { + return 0; + } + return Math.exp(tempy * PsyModel.LN_TO_LOG10); + } + + private norm_s3_func_x(hf_slope: number) { + let lim_a = 0; + let lim_b = 0; + + let x = 0; + let l; + let h; + for (x = 0; this.s3_func_x(x, hf_slope) > 1e-20; x -= 1); + l = x; + h = 0; + while (Math.abs(h - l) > 1e-12) { + x = (h + l) / 2; + if (this.s3_func_x(x, hf_slope) > 0) { + h = x; + } else { + l = x; + } + } + lim_a = l; + + for (x = 0; this.s3_func_x(x, hf_slope) > 1e-20; x += 1); + l = 0; + h = x; + while (Math.abs(h - l) > 1e-12) { + x = (h + l) / 2; + if (this.s3_func_x(x, hf_slope) > 0) { + l = x; + } else { + h = x; + } + } + lim_b = h; + + let sum = 0; + const m = 1000; + let i; + for (i = 0; i <= m; ++i) { + const x = lim_a + (i * (lim_b - lim_a)) / m; + const y = this.s3_func_x(x, hf_slope); + sum += y; + } + + const norm = (m + 1) / (sum * (lim_b - lim_a)); + + return norm; + } + + private s3_func(bark: number) { + let tempx; + let x; + let temp; + tempx = bark; + if (tempx >= 0) tempx *= 3; + else tempx *= 1.5; + + if (tempx >= 0.5 && tempx <= 2.5) { + temp = tempx - 0.5; + x = 8.0 * (temp * temp - 2.0 * temp); + } else x = 0.0; + tempx += 0.474; + const tempy = + 15.811389 + 7.5 * tempx - 17.5 * Math.sqrt(1.0 + tempx * tempx); + + if (tempy <= -60.0) return 0.0; + + tempx = Math.exp((x + tempy) * PsyModel.LN_TO_LOG10); + + tempx /= 0.6609193; + return tempx; + } + + private freq2bark(freq: number) { + if (freq < 0) freq = 0; + freq *= 0.001; + return ( + 13.0 * Math.atan(0.76 * freq) + + 3.5 * Math.atan((freq * freq) / (7.5 * 7.5)) + ); + } + + private init_numline( + numlines: Int32Array, + bo: Int32Array, + bm: Int32Array, + bval: Float32Array, + bval_width: Float32Array, + mld: Float32Array, + bo_w: Float32Array, + sfreq: number, + blksize: number, + scalepos: Int32Array, + deltafreq: number, + sbmax: number + ) { + const b_frq = new Float32Array(CBANDS + 1); + const sample_freq_frac = sfreq / (sbmax > 15 ? 2 * 576 : 2 * 192); + const partition = new Int32Array(HBLKSIZE); + let i; + sfreq /= blksize; + let j = 0; + let ni = 0; + + for (i = 0; i < CBANDS; i++) { + let j2; + const bark1 = this.freq2bark(sfreq * j); + + b_frq[i] = sfreq * j; + + for ( + j2 = j; + this.freq2bark(sfreq * j2) - bark1 < PsyModel.DELBARK && + j2 <= blksize / 2; + j2++ + ); + + numlines[i] = j2 - j; + ni = i + 1; + + while (j < j2) { + assert(j < HBLKSIZE); + partition[j++] = i; + } + if (j > blksize / 2) { + j = blksize / 2; + ++i; + break; + } + } + assert(i < CBANDS); + b_frq[i] = sfreq * j; + + for (let sfb = 0; sfb < sbmax; sfb++) { + let i1; + let i2; + let arg; + const start = scalepos[sfb]; + const end = scalepos[sfb + 1]; + + i1 = Math.floor(0.5 + deltafreq * (start - 0.5)); + if (i1 < 0) i1 = 0; + i2 = Math.floor(0.5 + deltafreq * (end - 0.5)); + + if (i2 > blksize / 2) i2 = blksize / 2; + + bm[sfb] = (partition[i1] + partition[i2]) / 2; + bo[sfb] = partition[i2]; + const f_tmp = sample_freq_frac * end; + + bo_w[sfb] = + (f_tmp - b_frq[bo[sfb]]) / (b_frq[bo[sfb] + 1] - b_frq[bo[sfb]]); + if (bo_w[sfb] < 0) { + bo_w[sfb] = 0; + } else if (bo_w[sfb] > 1) { + bo_w[sfb] = 1; + } + + arg = this.freq2bark(sfreq * scalepos[sfb] * deltafreq); + arg = Math.min(arg, 15.5) / 15.5; + + mld[sfb] = Math.pow(10.0, 1.25 * (1 - Math.cos(Math.PI * arg)) - 2.5); + } + + j = 0; + for (let k = 0; k < ni; k++) { + const w = numlines[k]; + let bark1; + let bark2; + + bark1 = this.freq2bark(sfreq * j); + bark2 = this.freq2bark(sfreq * (j + w - 1)); + bval[k] = 0.5 * (bark1 + bark2); + + bark1 = this.freq2bark(sfreq * (j - 0.5)); + bark2 = this.freq2bark(sfreq * (j + w - 0.5)); + bval_width[k] = bark2 - bark1; + j += w; + } + + return ni; + } + + private init_s3_values( + s3ind: Int32Array[], + npart: number, + bval: Float32Array, + bval_width: Float32Array, + norm: Float32Array, + use_old_s3: boolean + ) { + const s3 = Array.from({ length: CBANDS }, () => new Float32Array(CBANDS)); + + let j; + let numberOfNoneZero = 0; + + if (use_old_s3) { + for (let i = 0; i < npart; i++) { + for (j = 0; j < npart; j++) { + const v = this.s3_func(bval[i] - bval[j]) * bval_width[j]; + s3[i][j] = v * norm[i]; + } + } + } else { + for (j = 0; j < npart; j++) { + const hf_slope = 15 + Math.min(21 / bval[j], 12); + const s3_x_norm = this.norm_s3_func_x(hf_slope); + for (let i = 0; i < npart; i++) { + const v = + s3_x_norm * + this.s3_func_x(bval[i] - bval[j], hf_slope) * + bval_width[j]; + s3[i][j] = v * norm[i]; + } + } + } + for (let i = 0; i < npart; i++) { + for (j = 0; j < npart; j++) { + if (s3[i][j] > 0.0) break; + } + s3ind[i][0] = j; + + for (j = npart - 1; j > 0; j--) { + if (s3[i][j] > 0.0) break; + } + s3ind[i][1] = j; + numberOfNoneZero += s3ind[i][1] - s3ind[i][0] + 1; + } + + const p = new Float32Array(numberOfNoneZero); + let k = 0; + for (let i = 0; i < npart; i++) + for (j = s3ind[i][0]; j <= s3ind[i][1]; j++) p[k++] = s3[i][j]; + + return p; + } + + private stereo_demask(f: number) { + let arg = this.freq2bark(f); + arg = Math.min(arg, 15.5) / 15.5; + + return Math.pow(10.0, 1.25 * (1 - Math.cos(Math.PI * arg)) - 2.5); + } + + // eslint-disable-next-line complexity + psymodel_init(gfp: LameGlobalFlags) { + const gfc = gfp.internal_flags; + let i; + const useOldS3 = true; + const bvl_a = 13; + const bvl_b = 24; + const snr_l_a = 0; + const snr_l_b = 0; + const snr_s_a = -8.25; + const snr_s_b = -4.5; + const bval = new Float32Array(CBANDS); + const bval_width = new Float32Array(CBANDS); + const norm = new Float32Array(CBANDS); + const sfreq = gfp.out_samplerate; + + gfc.ms_ener_ratio_old = 0.25; + gfc.blocktype_old[0] = NORM_TYPE; + gfc.blocktype_old[1] = NORM_TYPE; + + for (i = 0; i < 4; ++i) { + for (let j = 0; j < CBANDS; ++j) { + gfc.nb_1[i][j] = 1e20; + gfc.nb_2[i][j] = 1e20; + gfc.nb_s1[i][j] = 1.0; + gfc.nb_s2[i][j] = 1.0; + } + for (let sb = 0; sb < SBMAX_l; sb++) { + gfc.en[i].l[sb] = 1e20; + gfc.thm[i].l[sb] = 1e20; + } + for (let j = 0; j < 3; ++j) { + for (let sb = 0; sb < SBMAX_s; sb++) { + gfc.en[i].s[sb][j] = 1e20; + gfc.thm[i].s[sb][j] = 1e20; + } + gfc.nsPsy.lastAttacks[i] = 0; + } + for (let j = 0; j < 9; j++) gfc.nsPsy.last_en_subshort[i][j] = 10; + } + + gfc.loudness_sq_save[0] = 0.0; + gfc.loudness_sq_save[1] = 0.0; + + gfc.npart_l = this.init_numline( + gfc.numlines_l, + gfc.bo_l, + gfc.bm_l, + bval, + bval_width, + gfc.mld_l, + gfc.PSY.bo_l_weight, + sfreq, + BLKSIZE, + gfc.scalefac_band.l, + BLKSIZE / (2.0 * 576), + SBMAX_l + ); + assert(gfc.npart_l < CBANDS); + + for (i = 0; i < gfc.npart_l; i++) { + let snr = snr_l_a; + if (bval[i] >= bvl_a) { + snr = + (snr_l_b * (bval[i] - bvl_a)) / (bvl_b - bvl_a) + + (snr_l_a * (bvl_b - bval[i])) / (bvl_b - bvl_a); + } + norm[i] = Math.pow(10.0, snr / 10.0); + if (gfc.numlines_l[i] > 0) { + gfc.rnumlines_l[i] = 1.0 / gfc.numlines_l[i]; + } else { + gfc.rnumlines_l[i] = 0; + } + } + gfc.s3_ll = this.init_s3_values( + gfc.s3ind, + gfc.npart_l, + bval, + bval_width, + norm, + useOldS3 + ); + + let j = 0; + for (i = 0; i < gfc.npart_l; i++) { + let x; + + x = MAX_FLOAT32_VALUE; + for (let k = 0; k < gfc.numlines_l[i]; k++, j++) { + const freq = (sfreq * j) / (1000.0 * BLKSIZE); + let level; + + level = this.ATHformula(freq * 1000, gfp) - 20; + + level = Math.pow(10, 0.1 * level); + + level *= gfc.numlines_l[i]; + if (x > level) x = level; + } + gfc.ATH.cb_l[i] = x; + + x = -20 + (bval[i] * 20) / 10; + if (x > 6) { + x = 100; + } + if (x < -15) { + x = -15; + } + x -= 8; + gfc.minval_l[i] = Math.pow(10.0, x / 10) * gfc.numlines_l[i]; + } + + gfc.npart_s = this.init_numline( + gfc.numlines_s, + gfc.bo_s, + gfc.bm_s, + bval, + bval_width, + gfc.mld_s, + gfc.PSY.bo_s_weight, + sfreq, + BLKSIZE_s, + gfc.scalefac_band.s, + BLKSIZE_s / (2.0 * 192), + SBMAX_s + ); + assert(gfc.npart_s < CBANDS); + + j = 0; + for (i = 0; i < gfc.npart_s; i++) { + let x; + let snr = snr_s_a; + if (bval[i] >= bvl_a) { + snr = + (snr_s_b * (bval[i] - bvl_a)) / (bvl_b - bvl_a) + + (snr_s_a * (bvl_b - bval[i])) / (bvl_b - bvl_a); + } + norm[i] = Math.pow(10.0, snr / 10.0); + + x = MAX_FLOAT32_VALUE; + for (let k = 0; k < gfc.numlines_s[i]; k++, j++) { + const freq = (sfreq * j) / (1000.0 * BLKSIZE_s); + let level; + + level = this.ATHformula(freq * 1000, gfp) - 20; + + level = Math.pow(10, 0.1 * level); + + level *= gfc.numlines_s[i]; + if (x > level) x = level; + } + gfc.ATH.cb_s[i] = x; + + x = -7.0 + (bval[i] * 7.0) / 12.0; + if (bval[i] > 12) { + x *= 1 + Math.log(1 + x) * 3.1; + } + if (bval[i] < 12) { + x *= 1 + Math.log(1 - x) * 2.3; + } + if (x < -15) { + x = -15; + } + x -= 8; + gfc.minval_s[i] = Math.pow(10.0, x / 10) * gfc.numlines_s[i]; + } + + gfc.s3_ss = this.init_s3_values( + gfc.s3ind_s, + gfc.npart_s, + bval, + bval_width, + norm, + useOldS3 + ); + + this.init_mask_add_max_values(); + this.fft.init(); + + gfc.decay = Math.exp( + (-1.0 * LOG10) / ((this.temporalmask_sustain_sec * sfreq) / 192.0) + ); + + { + let msfix; + msfix = PsyModel.NS_MSFIX; + if ((gfp.exp_nspsytune & 2) !== 0) msfix = 1.0; + if (Math.abs(gfp.msfix) > 0.0) msfix = gfp.msfix; + gfp.msfix = msfix; + + for (let b = 0; b < gfc.npart_l; b++) + if (gfc.s3ind[b][1] > gfc.npart_l - 1) + gfc.s3ind[b][1] = gfc.npart_l - 1; + } + + const frame_duration = (576 * gfc.mode_gr) / sfreq; + gfc.ATH.decay = Math.pow(10, (-12 / 10) * frame_duration); + gfc.ATH.adjust = 0.01; + + gfc.ATH.adjustLimit = 1.0; + + assert(gfc.bo_l[SBMAX_l - 1] <= gfc.npart_l); + assert(gfc.bo_s[SBMAX_s - 1] <= gfc.npart_s); + + if (gfp.ATHtype !== -1) { + let freq; + const freq_inc = gfp.out_samplerate / BLKSIZE; + let eql_balance = 0.0; + freq = 0.0; + for (i = 0; i < BLKSIZE / 2; ++i) { + freq += freq_inc; + gfc.ATH.eql_w[i] = 1 / Math.pow(10, this.ATHformula(freq, gfp) / 10); + eql_balance += gfc.ATH.eql_w[i]; + } + eql_balance = 1.0 / eql_balance; + for (i = BLKSIZE / 2; --i >= 0; ) { + gfc.ATH.eql_w[i] *= eql_balance; + } + } + { + let b = 0; + let j = 0; + for (; b < gfc.npart_s; ++b) { + for (i = 0; i < gfc.numlines_s[b]; ++i) { + ++j; + } + } + assert(j === 129); + b = 0; + j = 0; + for (; b < gfc.npart_l; ++b) { + for (i = 0; i < gfc.numlines_l[b]; ++i) { + ++j; + } + } + assert(j === 513); + } + j = 0; + for (i = 0; i < gfc.npart_l; i++) { + const freq = (sfreq * (j + gfc.numlines_l[i] / 2)) / (1.0 * BLKSIZE); + gfc.mld_cb_l[i] = this.stereo_demask(freq); + j += gfc.numlines_l[i]; + } + for (; i < CBANDS; ++i) { + gfc.mld_cb_l[i] = 1; + } + j = 0; + for (i = 0; i < gfc.npart_s; i++) { + const freq = (sfreq * (j + gfc.numlines_s[i] / 2)) / (1.0 * BLKSIZE_s); + gfc.mld_cb_s[i] = this.stereo_demask(freq); + j += gfc.numlines_s[i]; + } + for (; i < CBANDS; ++i) { + gfc.mld_cb_s[i] = 1; + } + return 0; + } + + private ATHformula_GB(f: number, value: number) { + if (f < -0.3) f = 3410; + + f /= 1000; + f = Math.max(0.1, f); + const ath = + 3.64 * Math.pow(f, -0.8) - + 6.8 * Math.exp(-0.6 * Math.pow(f - 3.4, 2.0)) + + 6.0 * Math.exp(-0.15 * Math.pow(f - 8.7, 2.0)) + + (0.6 + 0.04 * value) * 0.001 * Math.pow(f, 4.0); + return ath; + } + + ATHformula(f: number, gfp: LameGlobalFlags) { + let ath; + switch (gfp.ATHtype) { + case 0: + ath = this.ATHformula_GB(f, 9); + break; + case 1: + ath = this.ATHformula_GB(f, -1); + break; + case 2: + ath = this.ATHformula_GB(f, 0); + break; + case 3: + ath = this.ATHformula_GB(f, 1) + 6; + break; + case 4: + ath = this.ATHformula_GB(f, gfp.ATHcurve); + break; + default: + ath = this.ATHformula_GB(f, 0); + break; + } + return ath; + } +} diff --git a/packages/mp3-encoder/src/lame/Quality.ts b/packages/mp3-encoder/src/lame/Quality.ts new file mode 100644 index 0000000000..84133a7193 --- /dev/null +++ b/packages/mp3-encoder/src/lame/Quality.ts @@ -0,0 +1 @@ +export type Quality = 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0; diff --git a/packages/mp3-encoder/src/lame/Quantize.ts b/packages/mp3-encoder/src/lame/Quantize.ts new file mode 100644 index 0000000000..5e3dd86a57 --- /dev/null +++ b/packages/mp3-encoder/src/lame/Quantize.ts @@ -0,0 +1,1013 @@ +import type { BitStream } from './BitStream'; +import { CalcNoiseData } from './CalcNoiseData'; +import { CalcNoiseResult } from './CalcNoiseResult'; +import { GrInfo } from './GrInfo'; +import type { IIISideInfo } from './IIISideInfo'; +import type { LameGlobalFlags } from './LameGlobalFlags'; +import type { LameInternalFlags } from './LameInternalFlags'; +import { PsyModel } from './PsyModel'; +import { QuantizePVT } from './QuantizePVT'; +import { Reservoir } from './Reservoir'; +import { StartLine } from './StartLine'; +import { Takehiro } from './Takehiro'; +import { VbrMode } from './VbrMode'; +import { copyArray, fillArray, sortArray } from './arrays'; +import { assert } from './assert'; +import { + PSFB12, + PSFB21, + SBMAX_l, + SBMAX_s, + SBPSY_l, + SBPSY_s, + SFBMAX, + SHORT_TYPE, +} from './constants'; +import { isCloseToEachOther } from './math'; + +export class Quantize { + readonly rv: Reservoir; + + readonly qupvt: QuantizePVT; + + readonly tak: Takehiro; + + constructor(bs: BitStream) { + this.rv = new Reservoir(bs); + this.qupvt = new QuantizePVT(new PsyModel()); + this.tak = new Takehiro(this.qupvt); + } + + ms_convert(l3_side: IIISideInfo, gr: number) { + for (let i = 0; i < 576; ++i) { + const l = l3_side.tt[gr][0].xr[i]; + const r = l3_side.tt[gr][1].xr[i]; + l3_side.tt[gr][0].xr[i] = (l + r) * (Math.SQRT2 * 0.5); + l3_side.tt[gr][1].xr[i] = (l - r) * (Math.SQRT2 * 0.5); + } + } + + private init_xrpow_core( + cod_info: GrInfo, + xrpow: Float32Array, + upper: number, + sum: number + ) { + sum = 0; + for (let i = 0; i <= upper; ++i) { + const tmp = Math.abs(cod_info.xr[i]); + sum += tmp; + xrpow[i] = Math.sqrt(tmp * Math.sqrt(tmp)); + + if (xrpow[i] > cod_info.xrpow_max) cod_info.xrpow_max = xrpow[i]; + } + return sum; + } + + init_xrpow(gfc: LameInternalFlags, cod_info: GrInfo, xrpow: Float32Array) { + let sum = 0; + const upper = Math.trunc(cod_info.max_nonzero_coeff); + + assert(xrpow !== null); + cod_info.xrpow_max = 0; + + assert(upper >= 0 && upper <= 575); + + fillArray(xrpow, upper, 576, 0); + + sum = this.init_xrpow_core(cod_info, xrpow, upper, sum); + + if (sum > 1e-20) { + let j = 0; + if ((gfc.substep_shaping & 2) !== 0) j = 1; + + for (let i = 0; i < cod_info.psymax; i++) gfc.pseudohalf[i] = j; + + return true; + } + + fillArray(cod_info.l3_enc, 0, 576, 0); + return false; + } + + private psfb21_analogsilence(gfc: LameInternalFlags, cod_info: GrInfo) { + const ath = gfc.ATH; + const { xr } = cod_info; + + if (cod_info.block_type !== SHORT_TYPE) { + let stop = false; + for (let gsfb = PSFB21 - 1; gsfb >= 0 && !stop; gsfb--) { + const start = gfc.scalefac_band.psfb21[gsfb]; + const end = gfc.scalefac_band.psfb21[gsfb + 1]; + let ath21 = this.qupvt.athAdjust( + ath.adjust, + ath.psfb21[gsfb], + ath.floor + ); + + if (gfc.nsPsy.longfact[21] > 1e-12) ath21 *= gfc.nsPsy.longfact[21]; + + for (let j = end - 1; j >= start; j--) { + if (Math.abs(xr[j]) < ath21) xr[j] = 0; + else { + stop = true; + break; + } + } + } + } else { + for (let block = 0; block < 3; block++) { + let stop = false; + for (let gsfb = PSFB12 - 1; gsfb >= 0 && !stop; gsfb--) { + const start = + gfc.scalefac_band.s[12] * 3 + + (gfc.scalefac_band.s[13] - gfc.scalefac_band.s[12]) * block + + (gfc.scalefac_band.psfb12[gsfb] - gfc.scalefac_band.psfb12[0]); + const end = + start + + (gfc.scalefac_band.psfb12[gsfb + 1] - + gfc.scalefac_band.psfb12[gsfb]); + let ath12 = this.qupvt.athAdjust( + ath.adjust, + ath.psfb12[gsfb], + ath.floor + ); + + if (gfc.nsPsy.shortfact[12] > 1e-12) ath12 *= gfc.nsPsy.shortfact[12]; + + for (let j = end - 1; j >= start; j--) { + if (Math.abs(xr[j]) < ath12) xr[j] = 0; + else { + stop = true; + break; + } + } + } + } + } + } + + init_outer_loop(gfc: LameInternalFlags, cod_info: GrInfo) { + cod_info.part2_3_length = 0; + cod_info.big_values = 0; + cod_info.count1 = 0; + cod_info.global_gain = 210; + cod_info.scalefac_compress = 0; + + cod_info.table_select[0] = 0; + cod_info.table_select[1] = 0; + cod_info.table_select[2] = 0; + cod_info.subblock_gain[0] = 0; + cod_info.subblock_gain[1] = 0; + cod_info.subblock_gain[2] = 0; + cod_info.subblock_gain[3] = 0; + + cod_info.region0_count = 0; + cod_info.region1_count = 0; + cod_info.preflag = 0; + cod_info.scalefac_scale = 0; + cod_info.count1table_select = 0; + cod_info.part2_length = 0; + cod_info.sfb_lmax = SBPSY_l; + cod_info.sfb_smin = SBPSY_s; + cod_info.psy_lmax = gfc.sfb21_extra ? SBMAX_l : SBPSY_l; + cod_info.psymax = cod_info.psy_lmax; + cod_info.sfbmax = cod_info.sfb_lmax; + cod_info.sfbdivide = 11; + for (let sfb = 0; sfb < SBMAX_l; sfb++) { + cod_info.width[sfb] = + gfc.scalefac_band.l[sfb + 1] - gfc.scalefac_band.l[sfb]; + + cod_info.window[sfb] = 3; + } + if (cod_info.block_type === SHORT_TYPE) { + const ixwork = new Float32Array(576); + + cod_info.sfb_smin = 0; + cod_info.sfb_lmax = 0; + if (cod_info.mixed_block_flag !== 0) { + cod_info.sfb_smin = 3; + cod_info.sfb_lmax = gfc.mode_gr * 2 + 4; + } + cod_info.psymax = + cod_info.sfb_lmax + + 3 * ((gfc.sfb21_extra ? SBMAX_s : SBPSY_s) - cod_info.sfb_smin); + cod_info.sfbmax = cod_info.sfb_lmax + 3 * (SBPSY_s - cod_info.sfb_smin); + cod_info.sfbdivide = cod_info.sfbmax - 18; + cod_info.psy_lmax = cod_info.sfb_lmax; + + let ix = gfc.scalefac_band.l[cod_info.sfb_lmax]; + copyArray(cod_info.xr, 0, ixwork, 0, 576); + for (let sfb = cod_info.sfb_smin; sfb < SBMAX_s; sfb++) { + const start = gfc.scalefac_band.s[sfb]; + const end = gfc.scalefac_band.s[sfb + 1]; + for (let window = 0; window < 3; window++) { + for (let l = start; l < end; l++) { + cod_info.xr[ix++] = ixwork[3 * l + window]; + } + } + } + + let j = cod_info.sfb_lmax; + for (let sfb = cod_info.sfb_smin; sfb < SBMAX_s; sfb++) { + const y = gfc.scalefac_band.s[sfb + 1] - gfc.scalefac_band.s[sfb]; + cod_info.width[j] = y; + cod_info.width[j + 1] = y; + cod_info.width[j + 2] = y; + cod_info.window[j] = 0; + cod_info.window[j + 1] = 1; + cod_info.window[j + 2] = 2; + j += 3; + } + } + + cod_info.count1bits = 0; + cod_info.sfb_partition_table = this.qupvt.nr_of_sfb_block[0][0]; + cod_info.slen[0] = 0; + cod_info.slen[1] = 0; + cod_info.slen[2] = 0; + cod_info.slen[3] = 0; + + cod_info.max_nonzero_coeff = 575; + + fillArray(cod_info.scalefac, 0); + + this.psfb21_analogsilence(gfc, cod_info); + } + + private bin_search_StepSize( + gfc: LameInternalFlags, + cod_info: GrInfo, + desired_rate: number, + ch: number, + xrpow: Float32Array + ) { + const enum BinSearchDirection { + BINSEARCH_NONE, + BINSEARCH_UP, + BINSEARCH_DOWN, + } + + let nBits; + let CurrentStep = gfc.currentStep[ch]; + let flagGoneOver = false; + const start = gfc.oldValue[ch]; + let direction = BinSearchDirection.BINSEARCH_NONE; + cod_info.global_gain = start; + desired_rate -= cod_info.part2_length; + + assert(CurrentStep !== 0); + for (;;) { + let step; + nBits = this.tak.count_bits(gfc, xrpow, cod_info, null); + + if (CurrentStep === 1 || nBits === desired_rate) break; + + if (nBits > desired_rate) { + if (direction === BinSearchDirection.BINSEARCH_DOWN) + flagGoneOver = true; + + if (flagGoneOver) CurrentStep /= 2; + direction = BinSearchDirection.BINSEARCH_UP; + step = CurrentStep; + } else { + if (direction === BinSearchDirection.BINSEARCH_UP) flagGoneOver = true; + + if (flagGoneOver) CurrentStep /= 2; + direction = BinSearchDirection.BINSEARCH_DOWN; + step = -CurrentStep; + } + cod_info.global_gain += step; + if (cod_info.global_gain < 0) { + cod_info.global_gain = 0; + flagGoneOver = true; + } + if (cod_info.global_gain > 255) { + cod_info.global_gain = 255; + flagGoneOver = true; + } + } + + assert(cod_info.global_gain >= 0); + assert(cod_info.global_gain < 256); + + while (nBits > desired_rate && cod_info.global_gain < 255) { + cod_info.global_gain++; + nBits = this.tak.count_bits(gfc, xrpow, cod_info, null); + } + gfc.currentStep[ch] = start - cod_info.global_gain >= 4 ? 4 : 2; + gfc.oldValue[ch] = cod_info.global_gain; + cod_info.part2_3_length = nBits; + return nBits; + } + + trancate_smallspectrums( + gfc: LameInternalFlags, + gi: GrInfo, + l3_xmin: Float32Array, + work: Float32Array + ) { + const distort = new Float32Array(SFBMAX); + + if ( + ((gfc.substep_shaping & 4) === 0 && gi.block_type === SHORT_TYPE) || + (gfc.substep_shaping & 0x80) !== 0 + ) + return; + this.calc_noise(gi, l3_xmin, distort, new CalcNoiseResult(), null); + for (let j = 0; j < 576; j++) { + let xr = 0.0; + if (gi.l3_enc[j] !== 0) xr = Math.abs(gi.xr[j]); + work[j] = xr; + } + + let j = 0; + let sfb = 8; + if (gi.block_type === SHORT_TYPE) sfb = 6; + do { + let allowedNoise; + let trancateThreshold; + let nsame; + let start; + + let width = gi.width[sfb]; + j += width; + if (distort[sfb] >= 1.0) continue; + + sortArray(work, j - width, width); + if (isCloseToEachOther(work[j - 1], 0.0)) continue; + + allowedNoise = (1.0 - distort[sfb]) * l3_xmin[sfb]; + trancateThreshold = 0.0; + start = 0; + do { + for (nsame = 1; start + nsame < width; nsame++) + if ( + !isCloseToEachOther( + work[start + j - width], + work[start + j + nsame - width] + ) + ) + break; + + const noise = work[start + j - width] * work[start + j - width] * nsame; + if (allowedNoise < noise) { + if (start !== 0) trancateThreshold = work[start + j - width - 1]; + break; + } + allowedNoise -= noise; + start += nsame; + } while (start < width); + if (isCloseToEachOther(trancateThreshold, 0.0)) continue; + + do { + if (Math.abs(gi.xr[j - width]) <= trancateThreshold) + gi.l3_enc[j - width] = 0; + } while (--width > 0); + } while (++sfb < gi.psymax); + + gi.part2_3_length = this.tak.noquant_count_bits(gfc, gi, null); + } + + private loop_break(cod_info: GrInfo) { + for (let sfb = 0; sfb < cod_info.sfbmax; sfb++) + if ( + cod_info.scalefac[sfb] + + cod_info.subblock_gain[cod_info.window[sfb]] === + 0 + ) + return false; + + return true; + } + + private penalties(noise: number) { + return Math.log10(0.368 + 0.632 * noise * noise * noise); + } + + private get_klemm_noise(distort: Float32Array, gi: GrInfo) { + let klemm_noise = 1e-37; + for (let sfb = 0; sfb < gi.psymax; sfb++) + klemm_noise += this.penalties(distort[sfb]); + + return Math.max(1e-20, klemm_noise); + } + + // eslint-disable-next-line complexity + private quant_compare( + quant_comp: number, + best: CalcNoiseResult, + calc: CalcNoiseResult, + gi: GrInfo, + distort: Float32Array + ) { + let better; + + switch (quant_comp) { + default: + case 9: { + if (best.over_count > 0) { + better = calc.over_SSD <= best.over_SSD; + if (calc.over_SSD === best.over_SSD) better = calc.bits < best.bits; + } else { + better = + calc.max_noise < 0 && + calc.max_noise * 10 + calc.bits <= best.max_noise * 10 + best.bits; + } + break; + } + + case 0: + better = + calc.over_count < best.over_count || + (calc.over_count === best.over_count && + calc.over_noise < best.over_noise) || + (calc.over_count === best.over_count && + isCloseToEachOther(calc.over_noise, best.over_noise) && + calc.tot_noise < best.tot_noise); + break; + + case 8: + calc.max_noise = this.get_klemm_noise(distort, gi); + + // eslint-disable-next-line no-fallthrough + case 1: + better = calc.max_noise < best.max_noise; + break; + case 2: + better = calc.tot_noise < best.tot_noise; + break; + case 3: + better = + calc.tot_noise < best.tot_noise && calc.max_noise < best.max_noise; + break; + case 4: + better = + (calc.max_noise <= 0.0 && best.max_noise > 0.2) || + (calc.max_noise <= 0.0 && + best.max_noise < 0.0 && + best.max_noise > calc.max_noise - 0.2 && + calc.tot_noise < best.tot_noise) || + (calc.max_noise <= 0.0 && + best.max_noise > 0.0 && + best.max_noise > calc.max_noise - 0.2 && + calc.tot_noise < best.tot_noise + best.over_noise) || + (calc.max_noise > 0.0 && + best.max_noise > -0.05 && + best.max_noise > calc.max_noise - 0.1 && + calc.tot_noise + calc.over_noise < + best.tot_noise + best.over_noise) || + (calc.max_noise > 0.0 && + best.max_noise > -0.1 && + best.max_noise > calc.max_noise - 0.15 && + calc.tot_noise + calc.over_noise + calc.over_noise < + best.tot_noise + best.over_noise + best.over_noise); + break; + case 5: + better = + calc.over_noise < best.over_noise || + (isCloseToEachOther(calc.over_noise, best.over_noise) && + calc.tot_noise < best.tot_noise); + break; + case 6: + better = + calc.over_noise < best.over_noise || + (isCloseToEachOther(calc.over_noise, best.over_noise) && + (calc.max_noise < best.max_noise || + (isCloseToEachOther(calc.max_noise, best.max_noise) && + calc.tot_noise <= best.tot_noise))); + break; + case 7: + better = + calc.over_count < best.over_count || + calc.over_noise < best.over_noise; + break; + } + + if (best.over_count === 0) { + better = better && calc.bits < best.bits; + } + + return better; + } + + private amp_scalefac_bands( + gfp: LameGlobalFlags, + cod_info: GrInfo, + distort: Float32Array, + xrpow: Float32Array, + bRefine: boolean + ) { + const gfc = gfp.internal_flags; + let ifqstep34; + + if (cod_info.scalefac_scale === 0) { + ifqstep34 = 1.29683955465100964055; + } else { + ifqstep34 = 1.68179283050742922612; + } + + let trigger = 0; + for (let sfb = 0; sfb < cod_info.sfbmax; sfb++) { + if (trigger < distort[sfb]) trigger = distort[sfb]; + } + + let { noise_shaping_amp } = gfc; + if (noise_shaping_amp === 3) { + if (bRefine) noise_shaping_amp = 2; + else noise_shaping_amp = 1; + } + switch (noise_shaping_amp) { + case 2: + break; + + case 1: + if (trigger > 1.0) trigger = Math.pow(trigger, 0.5); + else trigger *= 0.95; + break; + + case 0: + default: + if (trigger > 1.0) trigger = 1.0; + else trigger *= 0.95; + break; + } + + let j = 0; + for (let sfb = 0; sfb < cod_info.sfbmax; sfb++) { + const width = cod_info.width[sfb]; + let l; + j += width; + if (distort[sfb] < trigger) continue; + + if ((gfc.substep_shaping & 2) !== 0) { + gfc.pseudohalf[sfb] = gfc.pseudohalf[sfb] === 0 ? 1 : 0; + if (gfc.pseudohalf[sfb] === 0 && gfc.noise_shaping_amp === 2) return; + } + cod_info.scalefac[sfb]++; + for (l = -width; l < 0; l++) { + xrpow[j + l] *= ifqstep34; + if (xrpow[j + l] > cod_info.xrpow_max) + cod_info.xrpow_max = xrpow[j + l]; + } + + if (gfc.noise_shaping_amp === 2) return; + } + } + + private inc_scalefac_scale(cod_info: GrInfo, xrpow: Float32Array) { + const ifqstep34 = 1.29683955465100964055; + + let j = 0; + for (let sfb = 0; sfb < cod_info.sfbmax; sfb++) { + const width = cod_info.width[sfb]; + let s = cod_info.scalefac[sfb]; + if (cod_info.preflag !== 0) s += this.qupvt.pretab[sfb]; + j += width; + if ((s & 1) !== 0) { + s++; + for (let l = -width; l < 0; l++) { + xrpow[j + l] *= ifqstep34; + if (xrpow[j + l] > cod_info.xrpow_max) + cod_info.xrpow_max = xrpow[j + l]; + } + } + cod_info.scalefac[sfb] = s >> 1; + } + cod_info.preflag = 0; + cod_info.scalefac_scale = 1; + } + + private inc_subblock_gain( + gfc: LameInternalFlags, + cod_info: GrInfo, + xrpow: Float32Array + ) { + let sfb; + const { scalefac } = cod_info; + + for (sfb = 0; sfb < cod_info.sfb_lmax; sfb++) { + if (scalefac[sfb] >= 16) return true; + } + + for (let window = 0; window < 3; window++) { + let s1 = 0; + let s2 = 0; + + for ( + sfb = cod_info.sfb_lmax + window; + sfb < cod_info.sfbdivide; + sfb += 3 + ) { + if (s1 < scalefac[sfb]) s1 = scalefac[sfb]; + } + for (; sfb < cod_info.sfbmax; sfb += 3) { + if (s2 < scalefac[sfb]) s2 = scalefac[sfb]; + } + + if (s1 < 16 && s2 < 8) continue; + + if (cod_info.subblock_gain[window] >= 7) return true; + + cod_info.subblock_gain[window]++; + let j = gfc.scalefac_band.l[cod_info.sfb_lmax]; + for (sfb = cod_info.sfb_lmax + window; sfb < cod_info.sfbmax; sfb += 3) { + let amp; + const width = cod_info.width[sfb]; + let s = scalefac[sfb]; + assert(s >= 0); + s -= 4 >> cod_info.scalefac_scale; + if (s >= 0) { + scalefac[sfb] = s; + j += width * 3; + continue; + } + + scalefac[sfb] = 0; + { + const gain = 210 + (s << (cod_info.scalefac_scale + 1)); + amp = this.qupvt.ipow20(gain); + } + j += width * (window + 1); + for (let l = -width; l < 0; l++) { + xrpow[j + l] *= amp; + if (xrpow[j + l] > cod_info.xrpow_max) + cod_info.xrpow_max = xrpow[j + l]; + } + j += width * (3 - window - 1); + } + + const amp = this.qupvt.ipow20(202); + j += cod_info.width[sfb] * (window + 1); + for (let l = -cod_info.width[sfb]; l < 0; l++) { + xrpow[j + l] *= amp; + if (xrpow[j + l] > cod_info.xrpow_max) + cod_info.xrpow_max = xrpow[j + l]; + } + } + return false; + } + + private balance_noise( + gfp: LameGlobalFlags, + cod_info: GrInfo, + distort: Float32Array, + xrpow: Float32Array, + bRefine: boolean + ) { + const gfc = gfp.internal_flags; + + this.amp_scalefac_bands(gfp, cod_info, distort, xrpow, bRefine); + + let status = this.loop_break(cod_info); + + if (status) return false; + + if (gfc.mode_gr === 2) status = this.tak.scale_bitcount(cod_info); + else status = this.tak.scale_bitcount_lsf(gfc, cod_info); + + if (!status) return true; + + if (gfc.noise_shaping > 1) { + fillArray(gfc.pseudohalf, 0); + if (cod_info.scalefac_scale === 0) { + this.inc_scalefac_scale(cod_info, xrpow); + status = false; + } else if (cod_info.block_type === SHORT_TYPE && gfc.subblock_gain > 0) { + status = + this.inc_subblock_gain(gfc, cod_info, xrpow) || + this.loop_break(cod_info); + } + } + + if (!status) { + if (gfc.mode_gr === 2) status = this.tak.scale_bitcount(cod_info); + else status = this.tak.scale_bitcount_lsf(gfc, cod_info); + } + return !status; + } + + // eslint-disable-next-line complexity + outer_loop( + gfp: LameGlobalFlags, + cod_info: GrInfo, + l3_xmin: Float32Array, + xrpow: Float32Array, + ch: number, + targ_bits: number + ) { + const gfc = gfp.internal_flags; + const cod_info_w = new GrInfo(); + const save_xrpow = new Float32Array(576); + const distort = new Float32Array(SFBMAX); + let best_noise_info = new CalcNoiseResult(); + let better; + const prev_noise = new CalcNoiseData(); + let best_part2_3_length = 9999999; + let bEndOfSearch = false; + let bRefine = false; + let best_ggain_pass1 = 0; + + this.bin_search_StepSize(gfc, cod_info, targ_bits, ch, xrpow); + + if (gfc.noise_shaping === 0) { + return 100; + } + + this.calc_noise(cod_info, l3_xmin, distort, best_noise_info, prev_noise); + best_noise_info.bits = cod_info.part2_3_length; + + cod_info_w.assign(cod_info); + let age = 0; + copyArray(xrpow, 0, save_xrpow, 0, 576); + + while (!bEndOfSearch) { + do { + const noise_info = new CalcNoiseResult(); + let search_limit; + let maxggain = 255; + + if ((gfc.substep_shaping & 2) !== 0) { + search_limit = 20; + } else { + search_limit = 3; + } + + if (gfc.sfb21_extra) { + if (distort[cod_info_w.sfbmax] > 1.0) break; + if ( + cod_info_w.block_type === SHORT_TYPE && + (distort[cod_info_w.sfbmax + 1] > 1.0 || + distort[cod_info_w.sfbmax + 2] > 1.0) + ) + break; + } + + if (!this.balance_noise(gfp, cod_info_w, distort, xrpow, bRefine)) + break; + if (cod_info_w.scalefac_scale !== 0) maxggain = 254; + + const huff_bits = targ_bits - cod_info_w.part2_length; + if (huff_bits <= 0) break; + + while ( + (cod_info_w.part2_3_length = this.tak.count_bits( + gfc, + xrpow, + cod_info_w, + prev_noise + )) > huff_bits && + cod_info_w.global_gain <= maxggain + ) + cod_info_w.global_gain++; + + if (cod_info_w.global_gain > maxggain) break; + + if (best_noise_info.over_count === 0) { + while ( + (cod_info_w.part2_3_length = this.tak.count_bits( + gfc, + xrpow, + cod_info_w, + prev_noise + )) > best_part2_3_length && + cod_info_w.global_gain <= maxggain + ) + cod_info_w.global_gain++; + + if (cod_info_w.global_gain > maxggain) break; + } + + this.calc_noise(cod_info_w, l3_xmin, distort, noise_info, prev_noise); + noise_info.bits = cod_info_w.part2_3_length; + + if (cod_info.block_type !== SHORT_TYPE) { + better = gfp.quant_comp; + } else better = gfp.quant_comp_short; + + better = this.quant_compare( + better, + best_noise_info, + noise_info, + cod_info_w, + distort + ) + ? 1 + : 0; + + if (better !== 0) { + best_part2_3_length = cod_info.part2_3_length; + best_noise_info = noise_info; + cod_info.assign(cod_info_w); + age = 0; + + copyArray(xrpow, 0, save_xrpow, 0, 576); + } else if (gfc.full_outer_loop === 0) { + if (++age > search_limit && best_noise_info.over_count === 0) break; + if (gfc.noise_shaping_amp === 3 && bRefine && age > 30) break; + if ( + gfc.noise_shaping_amp === 3 && + bRefine && + cod_info_w.global_gain - best_ggain_pass1 > 15 + ) + break; + } + } while (cod_info_w.global_gain + cod_info_w.scalefac_scale < 255); + + if (gfc.noise_shaping_amp === 3) { + if (!bRefine) { + cod_info_w.assign(cod_info); + copyArray(save_xrpow, 0, xrpow, 0, 576); + age = 0; + best_ggain_pass1 = cod_info_w.global_gain; + + bRefine = true; + } else { + bEndOfSearch = true; + } + } else { + bEndOfSearch = true; + } + } + + assert(cod_info.global_gain + cod_info.scalefac_scale <= 255); + + if (gfp.VBR === VbrMode.vbr_rh || gfp.VBR === VbrMode.vbr_mtrh) { + copyArray(save_xrpow, 0, xrpow, 0, 576); + } else if ((gfc.substep_shaping & 1) !== 0) { + this.trancate_smallspectrums(gfc, cod_info, l3_xmin, xrpow); + } + + return best_noise_info.over_count; + } + + iteration_finish_one(gfc: LameInternalFlags, gr: number, ch: number) { + const { l3_side } = gfc; + const cod_info = l3_side.tt[gr][ch]; + + this.tak.best_scalefac_store(gfc, gr, ch, l3_side); + + if (gfc.use_best_huffman === 1) this.tak.best_huffman_divide(gfc, cod_info); + + this.rv.ResvAdjust(gfc, cod_info); + } + + private _pow20: Float32Array | undefined; + + private pow20(x: number) { + assert(x + QuantizePVT.Q_MAX2 >= 0 && x < QuantizePVT.Q_MAX); + + if (this._pow20 === undefined) { + this._pow20 = new Float32Array( + QuantizePVT.Q_MAX + QuantizePVT.Q_MAX2 + 1 + ); + for (let i = 0; i <= QuantizePVT.Q_MAX + QuantizePVT.Q_MAX2; i++) { + this._pow20[i] = Math.pow(2.0, (i - 210 - QuantizePVT.Q_MAX2) * 0.25); + } + } + + return this._pow20[x + QuantizePVT.Q_MAX2]; + } + + private calc_noise_core( + cod_info: GrInfo, + startline: StartLine, + l: number, + step: number + ) { + let noise = 0; + let j = startline.s; + const ix = cod_info.l3_enc; + + if (j > cod_info.count1) { + while (l-- !== 0) { + let temp; + temp = cod_info.xr[j]; + j++; + noise += temp * temp; + temp = cod_info.xr[j]; + j++; + noise += temp * temp; + } + } else if (j > cod_info.big_values) { + const ix01 = new Float32Array(2); + ix01[0] = 0; + ix01[1] = step; + while (l-- !== 0) { + let temp; + temp = Math.abs(cod_info.xr[j]) - ix01[ix[j]]; + j++; + noise += temp * temp; + temp = Math.abs(cod_info.xr[j]) - ix01[ix[j]]; + j++; + noise += temp * temp; + } + } else { + while (l-- !== 0) { + let temp; + temp = Math.abs(cod_info.xr[j]) - this.qupvt.pow43(ix[j]) * step; + j++; + noise += temp * temp; + temp = Math.abs(cod_info.xr[j]) - this.qupvt.pow43(ix[j]) * step; + j++; + noise += temp * temp; + } + } + + startline.s = j; + return noise; + } + + private calc_noise( + cod_info: GrInfo, + l3_xmin: Float32Array, + distort: Float32Array, + res: CalcNoiseResult, + prev_noise: CalcNoiseData | null + ) { + let distortPos = 0; + let l3_xminPos = 0; + let sfb; + let l; + let over = 0; + let over_noise_db = 0; + + let tot_noise_db = 0; + + let max_noise = -20.0; + let j = 0; + const { scalefac } = cod_info; + let scalefacPos = 0; + + res.over_SSD = 0; + + for (sfb = 0; sfb < cod_info.psymax; sfb++) { + const s = + cod_info.global_gain - + ((scalefac[scalefacPos++] + + (cod_info.preflag !== 0 ? this.qupvt.pretab[sfb] : 0)) << + (cod_info.scalefac_scale + 1)) - + cod_info.subblock_gain[cod_info.window[sfb]] * 8; + let noise = 0.0; + + if (prev_noise !== null && prev_noise.step[sfb] === s) { + noise = prev_noise.noise[sfb]; + j += cod_info.width[sfb]; + distort[distortPos++] = noise / l3_xmin[l3_xminPos++]; + + noise = prev_noise.noise_log[sfb]; + } else { + const step = this.pow20(s); + l = cod_info.width[sfb] >> 1; + + if (j + cod_info.width[sfb] > cod_info.max_nonzero_coeff) { + const usefullsize = cod_info.max_nonzero_coeff - j + 1; + + if (usefullsize > 0) l = usefullsize >> 1; + else l = 0; + } + + const sl = new StartLine(j); + noise = this.calc_noise_core(cod_info, sl, l, step); + j = sl.s; + + if (prev_noise !== null) { + prev_noise.step[sfb] = s; + prev_noise.noise[sfb] = noise; + } + + noise /= l3_xmin[l3_xminPos++]; + distort[distortPos++] = noise; + + noise = Math.log10(Math.max(noise, 1e-20)); + + if (prev_noise !== null) { + prev_noise.noise_log[sfb] = noise; + } + } + + if (prev_noise !== null) { + prev_noise.global_gain = cod_info.global_gain; + } + + tot_noise_db += noise; + + if (noise > 0.0) { + const tmp = Math.max(Math.trunc(noise * 10 + 0.5), 1); + res.over_SSD += tmp * tmp; + + over++; + + over_noise_db += noise; + } + max_noise = Math.max(max_noise, noise); + } + + res.over_count = over; + res.tot_noise = tot_noise_db; + res.over_noise = over_noise_db; + res.max_noise = max_noise; + + return over; + } +} diff --git a/packages/mp3-encoder/src/lame/QuantizePVT.ts b/packages/mp3-encoder/src/lame/QuantizePVT.ts new file mode 100644 index 0000000000..2723b0a3bf --- /dev/null +++ b/packages/mp3-encoder/src/lame/QuantizePVT.ts @@ -0,0 +1,433 @@ +import type { GrInfo } from './GrInfo'; +import type { III_psy_ratio } from './III_psy_ratio'; +import type { LameGlobalFlags } from './LameGlobalFlags'; +import type { PsyModel } from './PsyModel'; +import type { Takehiro } from './Takehiro'; +import { VbrMode } from './VbrMode'; +import { assert } from './assert'; +import { + MAX_BITS_PER_CHANNEL, + MAX_BITS_PER_GRANULE, + MAX_FLOAT32_VALUE, + PSFB12, + PSFB21, + SBMAX_l, + SBMAX_s, + SBPSY_l, + SBPSY_s, + SHORT_TYPE, +} from './constants'; +import { isCloseToEachOther } from './math'; + +export class QuantizePVT { + static Q_MAX = 256 + 1; + + static Q_MAX2 = 116; + + static IXMAX_VAL = 8206; + + readonly psy: PsyModel; + + constructor(psy: PsyModel) { + this.psy = psy; + } + + private static readonly DBL_EPSILON = 2.2204460492503131e-16; + + static readonly PRECALC_SIZE = QuantizePVT.IXMAX_VAL + 2; + + private static readonly NSATHSCALE = 100; + + readonly nr_of_sfb_block = [ + [ + [6, 5, 5, 5], + [9, 9, 9, 9], + [6, 9, 9, 9], + ], + [ + [6, 5, 7, 3], + [9, 9, 12, 6], + [6, 9, 12, 6], + ], + [ + [11, 10, 0, 0], + [18, 18, 0, 0], + [15, 18, 0, 0], + ], + [ + [7, 7, 7, 0], + [12, 12, 12, 0], + [6, 15, 12, 0], + ], + [ + [6, 6, 6, 3], + [12, 9, 9, 6], + [6, 12, 9, 6], + ], + [ + [8, 8, 5, 0], + [15, 12, 9, 0], + [6, 18, 9, 0], + ], + ] as const; + + private _ipow20: Float32Array | undefined; + + ipow20(x: number) { + assert(x >= 0 && x < QuantizePVT.Q_MAX); + + if (this._ipow20 === undefined) { + this._ipow20 = new Float32Array(QuantizePVT.Q_MAX); + for (let i = 0; i < QuantizePVT.Q_MAX; i++) { + this._ipow20[i] = Math.pow(2.0, (i - 210) * -0.1875); + } + } + + return this._ipow20[x]; + } + + private _pow43: Float32Array | undefined; + + pow43(k: number) { + if (this._pow43 === undefined) { + this._pow43 = new Float32Array(QuantizePVT.PRECALC_SIZE); + this._pow43[0] = 0.0; + for (let i = 1; i < QuantizePVT.PRECALC_SIZE; i++) { + this._pow43[i] = Math.pow(i, 4.0 / 3.0); + } + } + + return this._pow43[k]; + } + + _adj43: Float32Array | undefined; + + adj43(k: number) { + if (this._adj43 === undefined) { + this._adj43 = new Float32Array(QuantizePVT.PRECALC_SIZE); + + let i; + for (i = 0; i < QuantizePVT.PRECALC_SIZE - 1; i++) { + this._adj43[i] = + i + 1 - Math.pow(0.5 * (this.pow43(i) + this.pow43(i + 1)), 0.75); + } + this._adj43[i] = 0.5; + } + return this._adj43[k]; + } + + private ATHmdct(gfp: LameGlobalFlags, f: number) { + let ath = this.psy.ATHformula(f, gfp); + + ath -= QuantizePVT.NSATHSCALE; + + ath = Math.pow(10.0, ath / 10.0 + gfp.ATHlower); + return ath; + } + + private compute_ath(gfp: LameGlobalFlags) { + const ATH_l = gfp.internal_flags.ATH.l; + const ATH_psfb21 = gfp.internal_flags.ATH.psfb21; + const ATH_s = gfp.internal_flags.ATH.s; + const ATH_psfb12 = gfp.internal_flags.ATH.psfb12; + const gfc = gfp.internal_flags; + const samp_freq = gfp.out_samplerate; + + for (let sfb = 0; sfb < SBMAX_l; sfb++) { + const start = gfc.scalefac_band.l[sfb]; + const end = gfc.scalefac_band.l[sfb + 1]; + ATH_l[sfb] = MAX_FLOAT32_VALUE; + for (let i = start; i < end; i++) { + const freq = (i * samp_freq) / (2 * 576); + const ATH_f = this.ATHmdct(gfp, freq); + + ATH_l[sfb] = Math.min(ATH_l[sfb], ATH_f); + } + } + + for (let sfb = 0; sfb < PSFB21; sfb++) { + const start = gfc.scalefac_band.psfb21[sfb]; + const end = gfc.scalefac_band.psfb21[sfb + 1]; + ATH_psfb21[sfb] = MAX_FLOAT32_VALUE; + for (let i = start; i < end; i++) { + const freq = (i * samp_freq) / (2 * 576); + const ATH_f = this.ATHmdct(gfp, freq); + + ATH_psfb21[sfb] = Math.min(ATH_psfb21[sfb], ATH_f); + } + } + + for (let sfb = 0; sfb < SBMAX_s; sfb++) { + const start = gfc.scalefac_band.s[sfb]; + const end = gfc.scalefac_band.s[sfb + 1]; + ATH_s[sfb] = MAX_FLOAT32_VALUE; + for (let i = start; i < end; i++) { + const freq = (i * samp_freq) / (2 * 192); + const ATH_f = this.ATHmdct(gfp, freq); + + ATH_s[sfb] = Math.min(ATH_s[sfb], ATH_f); + } + ATH_s[sfb] *= gfc.scalefac_band.s[sfb + 1] - gfc.scalefac_band.s[sfb]; + } + + for (let sfb = 0; sfb < PSFB12; sfb++) { + const start = gfc.scalefac_band.psfb12[sfb]; + const end = gfc.scalefac_band.psfb12[sfb + 1]; + ATH_psfb12[sfb] = MAX_FLOAT32_VALUE; + for (let i = start; i < end; i++) { + const freq = (i * samp_freq) / (2 * 192); + const ATH_f = this.ATHmdct(gfp, freq); + + ATH_psfb12[sfb] = Math.min(ATH_psfb12[sfb], ATH_f); + } + + ATH_psfb12[sfb] *= gfc.scalefac_band.s[13] - gfc.scalefac_band.s[12]; + } + + gfc.ATH.floor = 10 * Math.log10(this.ATHmdct(gfp, -1)); + } + + iteration_init(gfp: LameGlobalFlags, tak: Takehiro) { + const gfc = gfp.internal_flags; + const { l3_side } = gfc; + let i; + + if (!gfc.iteration_init_init) { + gfc.iteration_init_init = true; + + l3_side.main_data_begin = 0; + this.compute_ath(gfp); + + tak.huffman_init(gfc); + + i = (gfp.exp_nspsytune >> 2) & 63; + if (i >= 32) i -= 64; + const bass = Math.pow(10, i / 4.0 / 10.0); + + i = (gfp.exp_nspsytune >> 8) & 63; + if (i >= 32) i -= 64; + const alto = Math.pow(10, i / 4.0 / 10.0); + + i = (gfp.exp_nspsytune >> 14) & 63; + if (i >= 32) i -= 64; + const treble = Math.pow(10, i / 4.0 / 10.0); + + i = (gfp.exp_nspsytune >> 20) & 63; + if (i >= 32) i -= 64; + const sfb21 = treble * Math.pow(10, i / 4.0 / 10.0); + for (i = 0; i < SBMAX_l; i++) { + let f; + if (i <= 6) f = bass; + else if (i <= 13) f = alto; + else if (i <= 20) f = treble; + else f = sfb21; + + gfc.nsPsy.longfact[i] = f; + } + for (i = 0; i < SBMAX_s; i++) { + let f; + if (i <= 5) f = bass; + else if (i <= 10) f = alto; + else if (i <= 11) f = treble; + else f = sfb21; + + gfc.nsPsy.shortfact[i] = f; + } + } + } + + reduce_side( + targ_bits: Int32Array, + ms_ener_ratio: number, + mean_bits: number, + max_bits: number + ) { + assert(max_bits <= MAX_BITS_PER_GRANULE); + assert(targ_bits[0] + targ_bits[1] <= MAX_BITS_PER_GRANULE); + + let fac = (0.33 * (0.5 - ms_ener_ratio)) / 0.5; + if (fac < 0) fac = 0; + if (fac > 0.5) fac = 0.5; + + let move_bits = Math.trunc(fac * 0.5 * (targ_bits[0] + targ_bits[1])); + + if (move_bits > MAX_BITS_PER_CHANNEL - targ_bits[0]) { + move_bits = MAX_BITS_PER_CHANNEL - targ_bits[0]; + } + if (move_bits < 0) move_bits = 0; + + if (targ_bits[1] >= 125) { + if (targ_bits[1] - move_bits > 125) { + if (targ_bits[0] < mean_bits) targ_bits[0] += move_bits; + targ_bits[1] -= move_bits; + } else { + targ_bits[0] += targ_bits[1] - 125; + targ_bits[1] = 125; + } + } + + move_bits = targ_bits[0] + targ_bits[1]; + if (move_bits > max_bits) { + targ_bits[0] = (max_bits * targ_bits[0]) / move_bits; + targ_bits[1] = (max_bits * targ_bits[1]) / move_bits; + } + assert(targ_bits[0] <= MAX_BITS_PER_CHANNEL); + assert(targ_bits[1] <= MAX_BITS_PER_CHANNEL); + assert(targ_bits[0] + targ_bits[1] <= MAX_BITS_PER_GRANULE); + } + + athAdjust(a: number, x: number, athFloor: number) { + const o = 90.30873362; + const p = 94.82444863; + let u = Math.log10(x) * 10.0; + const v = a * a; + let w = 0.0; + u -= athFloor; + + if (v > 1e-20) w = 1 + Math.log10(v) * (10.0 / o); + if (w < 0) w = 0; + u *= w; + u += athFloor + o - p; + + return Math.pow(10, 0.1 * u); + } + + // eslint-disable-next-line complexity + calc_xmin( + gfp: LameGlobalFlags, + ratio: III_psy_ratio, + cod_info: GrInfo, + pxmin: Float32Array + ) { + let pxminPos = 0; + const gfc = gfp.internal_flags; + let gsfb; + let j = 0; + let ath_over = 0; + const { ATH } = gfc; + const { xr } = cod_info; + const enable_athaa_fix = gfp.VBR === VbrMode.vbr_mtrh ? 1 : 0; + let { masking_lower } = gfc; + + if (gfp.VBR === VbrMode.vbr_mtrh || gfp.VBR === VbrMode.vbr_mt) { + masking_lower = 1.0; + } + + for (gsfb = 0; gsfb < cod_info.psy_lmax; gsfb++) { + let en0; + let xmin; + let rh2; + let l; + + if (gfp.VBR === VbrMode.vbr_rh || gfp.VBR === VbrMode.vbr_mtrh) + xmin = this.athAdjust(ATH.adjust, ATH.l[gsfb], ATH.floor); + else xmin = ATH.adjust * ATH.l[gsfb]; + + const width = cod_info.width[gsfb]; + const rh1 = xmin / width; + rh2 = QuantizePVT.DBL_EPSILON; + l = width >> 1; + en0 = 0.0; + do { + const xa = xr[j] * xr[j]; + en0 += xa; + rh2 += xa < rh1 ? xa : rh1; + j++; + const xb = xr[j] * xr[j]; + en0 += xb; + rh2 += xb < rh1 ? xb : rh1; + j++; + } while (--l > 0); + if (en0 > xmin) ath_over++; + + if (gsfb === SBPSY_l) { + const x = xmin * gfc.nsPsy.longfact[gsfb]; + if (rh2 < x) { + rh2 = x; + } + } + if (enable_athaa_fix !== 0) { + xmin = rh2; + } + + const e = ratio.en.l[gsfb]; + if (e > 0.0) { + let x; + x = (en0 * ratio.thm.l[gsfb] * masking_lower) / e; + if (enable_athaa_fix !== 0) x *= gfc.nsPsy.longfact[gsfb]; + if (xmin < x) xmin = x; + } + + if (enable_athaa_fix !== 0) pxmin[pxminPos++] = xmin; + else pxmin[pxminPos++] = xmin * gfc.nsPsy.longfact[gsfb]; + } + + let max_nonzero = 575; + if (cod_info.block_type !== SHORT_TYPE) { + let k = 576; + while (k-- !== 0 && isCloseToEachOther(xr[k], 0)) { + max_nonzero = k; + } + } + cod_info.max_nonzero_coeff = max_nonzero; + + for ( + let sfb = cod_info.sfb_smin; + gsfb < cod_info.psymax; + sfb++, gsfb += 3 + ) { + let b; + let tmpATH; + if (gfp.VBR === VbrMode.vbr_rh || gfp.VBR === VbrMode.vbr_mtrh) + tmpATH = this.athAdjust(ATH.adjust, ATH.s[sfb], ATH.floor); + else tmpATH = ATH.adjust * ATH.s[sfb]; + + const width = cod_info.width[gsfb]; + for (b = 0; b < 3; b++) { + let en0 = 0.0; + let xmin; + let rh2; + let l = width >> 1; + + const rh1 = tmpATH / width; + rh2 = QuantizePVT.DBL_EPSILON; + do { + const xa = xr[j] * xr[j]; + en0 += xa; + rh2 += xa < rh1 ? xa : rh1; + j++; + const xb = xr[j] * xr[j]; + en0 += xb; + rh2 += xb < rh1 ? xb : rh1; + j++; + } while (--l > 0); + if (en0 > tmpATH) ath_over++; + if (sfb === SBPSY_s) { + const x = tmpATH * gfc.nsPsy.shortfact[sfb]; + if (rh2 < x) { + rh2 = x; + } + } + if (enable_athaa_fix !== 0) xmin = rh2; + else xmin = tmpATH; + + const e = ratio.en.s[sfb][b]; + if (e > 0.0) { + let x; + x = (en0 * ratio.thm.s[sfb][b] * masking_lower) / e; + if (enable_athaa_fix !== 0) x *= gfc.nsPsy.shortfact[sfb]; + if (xmin < x) xmin = x; + } + + if (enable_athaa_fix !== 0) pxmin[pxminPos++] = xmin; + else pxmin[pxminPos++] = xmin * gfc.nsPsy.shortfact[sfb]; + } + } + + return ath_over; + } + + readonly pretab = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 2, 0, + ] as const; +} diff --git a/packages/mp3-encoder/src/lame/ReplayGain.ts b/packages/mp3-encoder/src/lame/ReplayGain.ts new file mode 100644 index 0000000000..5ee1bb3762 --- /dev/null +++ b/packages/mp3-encoder/src/lame/ReplayGain.ts @@ -0,0 +1,53 @@ +import { GainAnalysis } from './GainAnalysis'; + +export class ReplayGain { + linprebuf = new Float32Array(GainAnalysis.MAX_ORDER * 2); + + linpre = 0; + + lstepbuf = new Float32Array( + GainAnalysis.MAX_SAMPLES_PER_WINDOW + GainAnalysis.MAX_ORDER + ); + + lstep = 0; + + loutbuf = new Float32Array( + GainAnalysis.MAX_SAMPLES_PER_WINDOW + GainAnalysis.MAX_ORDER + ); + + lout = 0; + + rinprebuf = new Float32Array(GainAnalysis.MAX_ORDER * 2); + + rinpre = 0; + + rstepbuf = new Float32Array( + GainAnalysis.MAX_SAMPLES_PER_WINDOW + GainAnalysis.MAX_ORDER + ); + + rstep = 0; + + routbuf = new Float32Array( + GainAnalysis.MAX_SAMPLES_PER_WINDOW + GainAnalysis.MAX_ORDER + ); + + rout = 0; + + sampleWindow = 0; + + totsamp = 0; + + lsum = 0; + + rsum = 0; + + freqindex = 0; + + first = 0; + + A = new Int32Array(GainAnalysis.STEPS_per_dB * GainAnalysis.MAX_dB); + + B = new Int32Array(GainAnalysis.STEPS_per_dB * GainAnalysis.MAX_dB); + + reqindex = 0; +} diff --git a/packages/mp3-encoder/src/lame/Reservoir.ts b/packages/mp3-encoder/src/lame/Reservoir.ts new file mode 100644 index 0000000000..3187b2d266 --- /dev/null +++ b/packages/mp3-encoder/src/lame/Reservoir.ts @@ -0,0 +1,102 @@ +import type { BitStream } from './BitStream'; +import type { GrInfo } from './GrInfo'; +import type { LameGlobalFlags } from './LameGlobalFlags'; +import type { LameInternalFlags } from './LameInternalFlags'; +import type { MeanBits } from './MeanBits'; +import { assert } from './assert'; + +export class Reservoir { + private bs: BitStream | undefined; + + constructor(bs: BitStream) { + this.bs = bs; + } + + ResvFrameBegin(gfp: LameGlobalFlags, mean_bits: MeanBits) { + const gfc = gfp.internal_flags; + let maxmp3buf; + const { l3_side } = gfc; + + const frameLength = this.bs?.getframebits(gfp) ?? 0; + mean_bits.bits = (frameLength - gfc.sideinfo_len * 8) / gfc.mode_gr; + + if (gfp.brate > 320) { + maxmp3buf = + 8 * + Math.trunc((gfp.brate * 1000) / (gfp.out_samplerate / 1152) / 8 + 0.5); + } else { + maxmp3buf = 8 * 1440; + } + + let fullFrameBits = + mean_bits.bits * gfc.mode_gr + Math.min(gfc.ResvSize, 0); + + if (fullFrameBits > maxmp3buf) fullFrameBits = maxmp3buf; + + l3_side.resvDrain_pre = 0; + + return fullFrameBits; + } + + ResvMaxBits( + gfp: LameGlobalFlags, + mean_bits: number, + targ_bits: MeanBits, + cbr: number + ) { + const gfc = gfp.internal_flags; + let add_bits; + let { ResvSize } = gfc; + + if (cbr !== 0) ResvSize += mean_bits; + + targ_bits.bits = mean_bits; + + if (ResvSize * 10 > 0) { + add_bits = ResvSize; + targ_bits.bits += add_bits; + gfc.substep_shaping |= 0x80; + } else { + add_bits = 0; + gfc.substep_shaping &= 0x7f; + } + + let extra_bits = ResvSize < 0 ? ResvSize : 0; + extra_bits -= add_bits; + + if (extra_bits < 0) extra_bits = 0; + return extra_bits; + } + + ResvAdjust(gfc: LameInternalFlags, gi: GrInfo) { + gfc.ResvSize -= gi.part2_3_length + gi.part2_length; + } + + ResvFrameEnd(gfc: LameInternalFlags, mean_bits: number) { + let over_bits; + const { l3_side } = gfc; + + gfc.ResvSize += mean_bits * gfc.mode_gr; + let stuffingBits = 0; + l3_side.resvDrain_post = 0; + l3_side.resvDrain_pre = 0; + + if ((over_bits = gfc.ResvSize % 8) !== 0) stuffingBits += over_bits; + + over_bits = gfc.ResvSize - stuffingBits; + if (over_bits > 0) { + assert(over_bits % 8 === 0); + assert(over_bits >= 0); + stuffingBits += over_bits; + } + + const mdb_bytes = Math.min(l3_side.main_data_begin * 8, stuffingBits) / 8; + l3_side.resvDrain_pre += 8 * mdb_bytes; + stuffingBits -= 8 * mdb_bytes; + gfc.ResvSize -= 8 * mdb_bytes; + l3_side.main_data_begin -= mdb_bytes; + + l3_side.resvDrain_post += stuffingBits; + gfc.ResvSize -= stuffingBits; + } +} diff --git a/packages/mp3-encoder/src/lame/ScaleFac.ts b/packages/mp3-encoder/src/lame/ScaleFac.ts new file mode 100644 index 0000000000..a9c5cf197a --- /dev/null +++ b/packages/mp3-encoder/src/lame/ScaleFac.ts @@ -0,0 +1,58 @@ +import { copyArray } from './arrays'; +import { PSFB12, PSFB21, SBMAX_l, SBMAX_s } from './constants'; + +export class ScaleFac { + private readonly arrL: number[] | undefined; + + private readonly arrS: number[] | undefined; + + private readonly arr21: number[] | undefined; + + private readonly arr12: number[] | undefined; + + readonly l: Int32Array; + + readonly s: Int32Array; + + readonly psfb21: Int32Array; + + readonly psfb12: Int32Array; + + constructor(); + + constructor(arrL: number[], arrS: number[], arr21: number[], arr12: number[]); + + constructor( + ...args: + | [] + | [arrL: number[], arrS: number[], arr21: number[], arr12: number[]] + ) { + this.l = new Int32Array(1 + SBMAX_l); + this.s = new Int32Array(1 + SBMAX_s); + this.psfb21 = new Int32Array(1 + PSFB21); + this.psfb12 = new Int32Array(1 + PSFB12); + const { l } = this; + const { s } = this; + + if (args.length === 4) { + [this.arrL, this.arrS, this.arr21, this.arr12] = args; + + copyArray(this.arrL, 0, l, 0, Math.min(this.arrL.length, this.l.length)); + copyArray(this.arrS, 0, s, 0, Math.min(this.arrS.length, this.s.length)); + copyArray( + this.arr21, + 0, + this.psfb21, + 0, + Math.min(this.arr21.length, this.psfb21.length) + ); + copyArray( + this.arr12, + 0, + this.psfb12, + 0, + Math.min(this.arr12.length, this.psfb12.length) + ); + } + } +} diff --git a/packages/mp3-encoder/src/lame/ShortBlock.ts b/packages/mp3-encoder/src/lame/ShortBlock.ts new file mode 100644 index 0000000000..199201e063 --- /dev/null +++ b/packages/mp3-encoder/src/lame/ShortBlock.ts @@ -0,0 +1,6 @@ +export const enum ShortBlock { + short_block_allowed, + short_block_coupled, + short_block_dispensed, + short_block_forced, +} diff --git a/packages/mp3-encoder/src/lame/StartLine.ts b/packages/mp3-encoder/src/lame/StartLine.ts new file mode 100644 index 0000000000..9eff9dc867 --- /dev/null +++ b/packages/mp3-encoder/src/lame/StartLine.ts @@ -0,0 +1,3 @@ +export class StartLine { + constructor(public s: number) {} +} diff --git a/packages/mp3-encoder/src/lame/Tables.ts b/packages/mp3-encoder/src/lame/Tables.ts new file mode 100644 index 0000000000..ddf78a60ef --- /dev/null +++ b/packages/mp3-encoder/src/lame/Tables.ts @@ -0,0 +1,360 @@ +import type { HuffCodeTab } from './HuffCodeTab'; + +const t1HB = [1, 1, 1, 0] as const; + +const t2HB = [1, 2, 1, 3, 1, 1, 3, 2, 0] as const; + +const t3HB = [3, 2, 1, 1, 1, 1, 3, 2, 0] as const; + +const t5HB = [1, 2, 6, 5, 3, 1, 4, 4, 7, 5, 7, 1, 6, 1, 1, 0] as const; + +const t6HB = [7, 3, 5, 1, 6, 2, 3, 2, 5, 4, 4, 1, 3, 3, 2, 0] as const; + +const t7HB = [ + 1, 2, 10, 19, 16, 10, 3, 3, 7, 10, 5, 3, 11, 4, 13, 17, 8, 4, 12, 11, 18, 15, + 11, 2, 7, 6, 9, 14, 3, 1, 6, 4, 5, 3, 2, 0, +] as const; + +const t8HB = [ + 3, 4, 6, 18, 12, 5, 5, 1, 2, 16, 9, 3, 7, 3, 5, 14, 7, 3, 19, 17, 15, 13, 10, + 4, 13, 5, 8, 11, 5, 1, 12, 4, 4, 1, 1, 0, +] as const; + +const t9HB = [ + 7, 5, 9, 14, 15, 7, 6, 4, 5, 5, 6, 7, 7, 6, 8, 8, 8, 5, 15, 6, 9, 10, 5, 1, + 11, 7, 9, 6, 4, 1, 14, 4, 6, 2, 6, 0, +] as const; + +const t10HB = [ + 1, 2, 10, 23, 35, 30, 12, 17, 3, 3, 8, 12, 18, 21, 12, 7, 11, 9, 15, 21, 32, + 40, 19, 6, 14, 13, 22, 34, 46, 23, 18, 7, 20, 19, 33, 47, 27, 22, 9, 3, 31, + 22, 41, 26, 21, 20, 5, 3, 14, 13, 10, 11, 16, 6, 5, 1, 9, 8, 7, 8, 4, 4, 2, 0, +] as const; + +const t11HB = [ + 3, 4, 10, 24, 34, 33, 21, 15, 5, 3, 4, 10, 32, 17, 11, 10, 11, 7, 13, 18, 30, + 31, 20, 5, 25, 11, 19, 59, 27, 18, 12, 5, 35, 33, 31, 58, 30, 16, 7, 5, 28, + 26, 32, 19, 17, 15, 8, 14, 14, 12, 9, 13, 14, 9, 4, 1, 11, 4, 6, 6, 6, 3, 2, + 0, +] as const; + +const t12HB = [ + 9, 6, 16, 33, 41, 39, 38, 26, 7, 5, 6, 9, 23, 16, 26, 11, 17, 7, 11, 14, 21, + 30, 10, 7, 17, 10, 15, 12, 18, 28, 14, 5, 32, 13, 22, 19, 18, 16, 9, 5, 40, + 17, 31, 29, 17, 13, 4, 2, 27, 12, 11, 15, 10, 7, 4, 1, 27, 12, 8, 12, 6, 3, 1, + 0, +] as const; + +const t13HB = [ + 1, 5, 14, 21, 34, 51, 46, 71, 42, 52, 68, 52, 67, 44, 43, 19, 3, 4, 12, 19, + 31, 26, 44, 33, 31, 24, 32, 24, 31, 35, 22, 14, 15, 13, 23, 36, 59, 49, 77, + 65, 29, 40, 30, 40, 27, 33, 42, 16, 22, 20, 37, 61, 56, 79, 73, 64, 43, 76, + 56, 37, 26, 31, 25, 14, 35, 16, 60, 57, 97, 75, 114, 91, 54, 73, 55, 41, 48, + 53, 23, 24, 58, 27, 50, 96, 76, 70, 93, 84, 77, 58, 79, 29, 74, 49, 41, 17, + 47, 45, 78, 74, 115, 94, 90, 79, 69, 83, 71, 50, 59, 38, 36, 15, 72, 34, 56, + 95, 92, 85, 91, 90, 86, 73, 77, 65, 51, 44, 43, 42, 43, 20, 30, 44, 55, 78, + 72, 87, 78, 61, 46, 54, 37, 30, 20, 16, 53, 25, 41, 37, 44, 59, 54, 81, 66, + 76, 57, 54, 37, 18, 39, 11, 35, 33, 31, 57, 42, 82, 72, 80, 47, 58, 55, 21, + 22, 26, 38, 22, 53, 25, 23, 38, 70, 60, 51, 36, 55, 26, 34, 23, 27, 14, 9, 7, + 34, 32, 28, 39, 49, 75, 30, 52, 48, 40, 52, 28, 18, 17, 9, 5, 45, 21, 34, 64, + 56, 50, 49, 45, 31, 19, 12, 15, 10, 7, 6, 3, 48, 23, 20, 39, 36, 35, 53, 21, + 16, 23, 13, 10, 6, 1, 4, 2, 16, 15, 17, 27, 25, 20, 29, 11, 17, 12, 16, 8, 1, + 1, 0, 1, +] as const; + +const t15HB = [ + 7, 12, 18, 53, 47, 76, 124, 108, 89, 123, 108, 119, 107, 81, 122, 63, 13, 5, + 16, 27, 46, 36, 61, 51, 42, 70, 52, 83, 65, 41, 59, 36, 19, 17, 15, 24, 41, + 34, 59, 48, 40, 64, 50, 78, 62, 80, 56, 33, 29, 28, 25, 43, 39, 63, 55, 93, + 76, 59, 93, 72, 54, 75, 50, 29, 52, 22, 42, 40, 67, 57, 95, 79, 72, 57, 89, + 69, 49, 66, 46, 27, 77, 37, 35, 66, 58, 52, 91, 74, 62, 48, 79, 63, 90, 62, + 40, 38, 125, 32, 60, 56, 50, 92, 78, 65, 55, 87, 71, 51, 73, 51, 70, 30, 109, + 53, 49, 94, 88, 75, 66, 122, 91, 73, 56, 42, 64, 44, 21, 25, 90, 43, 41, 77, + 73, 63, 56, 92, 77, 66, 47, 67, 48, 53, 36, 20, 71, 34, 67, 60, 58, 49, 88, + 76, 67, 106, 71, 54, 38, 39, 23, 15, 109, 53, 51, 47, 90, 82, 58, 57, 48, 72, + 57, 41, 23, 27, 62, 9, 86, 42, 40, 37, 70, 64, 52, 43, 70, 55, 42, 25, 29, 18, + 11, 11, 118, 68, 30, 55, 50, 46, 74, 65, 49, 39, 24, 16, 22, 13, 14, 7, 91, + 44, 39, 38, 34, 63, 52, 45, 31, 52, 28, 19, 14, 8, 9, 3, 123, 60, 58, 53, 47, + 43, 32, 22, 37, 24, 17, 12, 15, 10, 2, 1, 71, 37, 34, 30, 28, 20, 17, 26, 21, + 16, 10, 6, 8, 6, 2, 0, +] as const; + +const t16HB = [ + 1, 5, 14, 44, 74, 63, 110, 93, 172, 149, 138, 242, 225, 195, 376, 17, 3, 4, + 12, 20, 35, 62, 53, 47, 83, 75, 68, 119, 201, 107, 207, 9, 15, 13, 23, 38, 67, + 58, 103, 90, 161, 72, 127, 117, 110, 209, 206, 16, 45, 21, 39, 69, 64, 114, + 99, 87, 158, 140, 252, 212, 199, 387, 365, 26, 75, 36, 68, 65, 115, 101, 179, + 164, 155, 264, 246, 226, 395, 382, 362, 9, 66, 30, 59, 56, 102, 185, 173, 265, + 142, 253, 232, 400, 388, 378, 445, 16, 111, 54, 52, 100, 184, 178, 160, 133, + 257, 244, 228, 217, 385, 366, 715, 10, 98, 48, 91, 88, 165, 157, 148, 261, + 248, 407, 397, 372, 380, 889, 884, 8, 85, 84, 81, 159, 156, 143, 260, 249, + 427, 401, 392, 383, 727, 713, 708, 7, 154, 76, 73, 141, 131, 256, 245, 426, + 406, 394, 384, 735, 359, 710, 352, 11, 139, 129, 67, 125, 247, 233, 229, 219, + 393, 743, 737, 720, 885, 882, 439, 4, 243, 120, 118, 115, 227, 223, 396, 746, + 742, 736, 721, 712, 706, 223, 436, 6, 202, 224, 222, 218, 216, 389, 386, 381, + 364, 888, 443, 707, 440, 437, 1728, 4, 747, 211, 210, 208, 370, 379, 734, 723, + 714, 1735, 883, 877, 876, 3459, 865, 2, 377, 369, 102, 187, 726, 722, 358, + 711, 709, 866, 1734, 871, 3458, 870, 434, 0, 12, 10, 7, 11, 10, 17, 11, 9, 13, + 12, 10, 7, 5, 3, 1, 3, +] as const; + +const t24HB = [ + 15, 13, 46, 80, 146, 262, 248, 434, 426, 669, 653, 649, 621, 517, 1032, 88, + 14, 12, 21, 38, 71, 130, 122, 216, 209, 198, 327, 345, 319, 297, 279, 42, 47, + 22, 41, 74, 68, 128, 120, 221, 207, 194, 182, 340, 315, 295, 541, 18, 81, 39, + 75, 70, 134, 125, 116, 220, 204, 190, 178, 325, 311, 293, 271, 16, 147, 72, + 69, 135, 127, 118, 112, 210, 200, 188, 352, 323, 306, 285, 540, 14, 263, 66, + 129, 126, 119, 114, 214, 202, 192, 180, 341, 317, 301, 281, 262, 12, 249, 123, + 121, 117, 113, 215, 206, 195, 185, 347, 330, 308, 291, 272, 520, 10, 435, 115, + 111, 109, 211, 203, 196, 187, 353, 332, 313, 298, 283, 531, 381, 17, 427, 212, + 208, 205, 201, 193, 186, 177, 169, 320, 303, 286, 268, 514, 377, 16, 335, 199, + 197, 191, 189, 181, 174, 333, 321, 305, 289, 275, 521, 379, 371, 11, 668, 184, + 183, 179, 175, 344, 331, 314, 304, 290, 277, 530, 383, 373, 366, 10, 652, 346, + 171, 168, 164, 318, 309, 299, 287, 276, 263, 513, 375, 368, 362, 6, 648, 322, + 316, 312, 307, 302, 292, 284, 269, 261, 512, 376, 370, 364, 359, 4, 620, 300, + 296, 294, 288, 282, 273, 266, 515, 380, 374, 369, 365, 361, 357, 2, 1033, 280, + 278, 274, 267, 264, 259, 382, 378, 372, 367, 363, 360, 358, 356, 0, 43, 20, + 19, 17, 15, 13, 11, 9, 7, 6, 4, 7, 5, 3, 1, 3, +] as const; + +const t32HB = [ + 1 << 0, + 5 << 1, + 4 << 1, + 5 << 2, + 6 << 1, + 5 << 2, + 4 << 2, + 4 << 3, + 7 << 1, + 3 << 2, + 6 << 2, + 0 << 3, + 7 << 2, + 2 << 3, + 3 << 3, + 1 << 4, +] as const; + +const t33HB = [ + 15 << 0, + 14 << 1, + 13 << 1, + 12 << 2, + 11 << 1, + 10 << 2, + 9 << 2, + 8 << 3, + 7 << 1, + 6 << 2, + 5 << 2, + 4 << 3, + 3 << 2, + 2 << 3, + 1 << 3, + 0 << 4, +] as const; + +const t1l = [1, 4, 3, 5] as const; + +const t2l = [1, 4, 7, 4, 5, 7, 6, 7, 8] as const; + +const t3l = [2, 3, 7, 4, 4, 7, 6, 7, 8] as const; + +const t5l = [1, 4, 7, 8, 4, 5, 8, 9, 7, 8, 9, 10, 8, 8, 9, 10] as const; + +const t6l = [3, 4, 6, 8, 4, 4, 6, 7, 5, 6, 7, 8, 7, 7, 8, 9] as const; + +const t7l = [ + 1, 4, 7, 9, 9, 10, 4, 6, 8, 9, 9, 10, 7, 7, 9, 10, 10, 11, 8, 9, 10, 11, 11, + 11, 8, 9, 10, 11, 11, 12, 9, 10, 11, 12, 12, 12, +] as const; + +const t8l = [ + 2, 4, 7, 9, 9, 10, 4, 4, 6, 10, 10, 10, 7, 6, 8, 10, 10, 11, 9, 10, 10, 11, + 11, 12, 9, 9, 10, 11, 12, 12, 10, 10, 11, 11, 13, 13, +] as const; + +const t9l = [ + 3, 4, 6, 7, 9, 10, 4, 5, 6, 7, 8, 10, 5, 6, 7, 8, 9, 10, 7, 7, 8, 9, 9, 10, 8, + 8, 9, 9, 10, 11, 9, 9, 10, 10, 11, 11, +] as const; + +const t10l = [ + 1, 4, 7, 9, 10, 10, 10, 11, 4, 6, 8, 9, 10, 11, 10, 10, 7, 8, 9, 10, 11, 12, + 11, 11, 8, 9, 10, 11, 12, 12, 11, 12, 9, 10, 11, 12, 12, 12, 12, 12, 10, 11, + 12, 12, 13, 13, 12, 13, 9, 10, 11, 12, 12, 12, 13, 13, 10, 10, 11, 12, 12, 13, + 13, 13, +] as const; + +const t11l = [ + 2, 4, 6, 8, 9, 10, 9, 10, 4, 5, 6, 8, 10, 10, 9, 10, 6, 7, 8, 9, 10, 11, 10, + 10, 8, 8, 9, 11, 10, 12, 10, 11, 9, 10, 10, 11, 11, 12, 11, 12, 9, 10, 11, 12, + 12, 13, 12, 13, 9, 9, 9, 10, 11, 12, 12, 12, 9, 9, 10, 11, 12, 12, 12, 12, +] as const; + +const t12l = [ + 4, 4, 6, 8, 9, 10, 10, 10, 4, 5, 6, 7, 9, 9, 10, 10, 6, 6, 7, 8, 9, 10, 9, 10, + 7, 7, 8, 8, 9, 10, 10, 10, 8, 8, 9, 9, 10, 10, 10, 11, 9, 9, 10, 10, 10, 11, + 10, 11, 9, 9, 9, 10, 10, 11, 11, 12, 10, 10, 10, 11, 11, 11, 11, 12, +] as const; + +const t13l = [ + 1, 5, 7, 8, 9, 10, 10, 11, 10, 11, 12, 12, 13, 13, 14, 14, 4, 6, 8, 9, 10, 10, + 11, 11, 11, 11, 12, 12, 13, 14, 14, 14, 7, 8, 9, 10, 11, 11, 12, 12, 11, 12, + 12, 13, 13, 14, 15, 15, 8, 9, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, + 15, 15, 9, 9, 11, 11, 12, 12, 13, 13, 12, 13, 13, 14, 14, 15, 15, 16, 10, 10, + 11, 12, 12, 12, 13, 13, 13, 13, 14, 13, 15, 15, 16, 16, 10, 11, 12, 12, 13, + 13, 13, 13, 13, 14, 14, 14, 15, 15, 16, 16, 11, 11, 12, 13, 13, 13, 14, 14, + 14, 14, 15, 15, 15, 16, 18, 18, 10, 10, 11, 12, 12, 13, 13, 14, 14, 14, 14, + 15, 15, 16, 17, 17, 11, 11, 12, 12, 13, 13, 13, 15, 14, 15, 15, 16, 16, 16, + 18, 17, 11, 12, 12, 13, 13, 14, 14, 15, 14, 15, 16, 15, 16, 17, 18, 19, 12, + 12, 12, 13, 14, 14, 14, 14, 15, 15, 15, 16, 17, 17, 17, 18, 12, 13, 13, 14, + 14, 15, 14, 15, 16, 16, 17, 17, 17, 18, 18, 18, 13, 13, 14, 15, 15, 15, 16, + 16, 16, 16, 16, 17, 18, 17, 18, 18, 14, 14, 14, 15, 15, 15, 17, 16, 16, 19, + 17, 17, 17, 19, 18, 18, 13, 14, 15, 16, 16, 16, 17, 16, 17, 17, 18, 18, 21, + 20, 21, 18, +] as const; + +const t15l = [ + 3, 5, 6, 8, 8, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 14, 5, 5, 7, 8, 9, 9, + 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 6, 7, 7, 8, 9, 9, 10, 10, 10, 11, 11, + 12, 12, 13, 13, 13, 7, 8, 8, 9, 9, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, + 8, 8, 9, 9, 10, 10, 11, 11, 11, 11, 12, 12, 12, 13, 13, 13, 9, 9, 9, 10, 10, + 10, 11, 11, 11, 11, 12, 12, 13, 13, 13, 14, 10, 9, 10, 10, 10, 11, 11, 11, 11, + 12, 12, 12, 13, 13, 14, 14, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 12, + 13, 13, 13, 14, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 14, 14, + 14, 10, 10, 11, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 11, 11, + 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 13, 14, 15, 14, 11, 11, 11, 11, 12, + 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 12, 12, 11, 12, 12, 12, 13, 13, + 13, 13, 13, 13, 14, 14, 15, 15, 12, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, + 14, 14, 14, 15, 15, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, + 14, 15, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15, 15, 15, +] as const; + +const t16_5l = [ + 1, 5, 7, 9, 10, 10, 11, 11, 12, 12, 12, 13, 13, 13, 14, 11, 4, 6, 8, 9, 10, + 11, 11, 11, 12, 12, 12, 13, 14, 13, 14, 11, 7, 8, 9, 10, 11, 11, 12, 12, 13, + 12, 13, 13, 13, 14, 14, 12, 9, 9, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 14, + 15, 15, 13, 10, 10, 11, 11, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 12, + 10, 10, 11, 11, 12, 13, 13, 14, 13, 14, 14, 15, 15, 15, 16, 13, 11, 11, 11, + 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 16, 13, 11, 11, 12, 12, 13, 13, + 13, 14, 14, 15, 15, 15, 15, 17, 17, 13, 11, 12, 12, 13, 13, 13, 14, 14, 15, + 15, 15, 15, 16, 16, 16, 13, 12, 12, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, + 15, 16, 15, 14, 12, 13, 12, 13, 14, 14, 14, 14, 15, 16, 16, 16, 17, 17, 16, + 13, 13, 13, 13, 13, 14, 14, 15, 16, 16, 16, 16, 16, 16, 15, 16, 14, 13, 14, + 14, 14, 14, 15, 15, 15, 15, 17, 16, 16, 16, 16, 18, 14, 15, 14, 14, 14, 15, + 15, 16, 16, 16, 18, 17, 17, 17, 19, 17, 14, 14, 15, 13, 14, 16, 16, 15, 16, + 16, 17, 18, 17, 19, 17, 16, 14, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 14, + 14, 14, 14, 14, 12, +] as const; + +const t16l = [ + 1, 5, 7, 9, 10, 10, 11, 11, 12, 12, 12, 13, 13, 13, 14, 10, 4, 6, 8, 9, 10, + 11, 11, 11, 12, 12, 12, 13, 14, 13, 14, 10, 7, 8, 9, 10, 11, 11, 12, 12, 13, + 12, 13, 13, 13, 14, 14, 11, 9, 9, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 14, + 15, 15, 12, 10, 10, 11, 11, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 11, + 10, 10, 11, 11, 12, 13, 13, 14, 13, 14, 14, 15, 15, 15, 16, 12, 11, 11, 11, + 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 16, 12, 11, 11, 12, 12, 13, 13, + 13, 14, 14, 15, 15, 15, 15, 17, 17, 12, 11, 12, 12, 13, 13, 13, 14, 14, 15, + 15, 15, 15, 16, 16, 16, 12, 12, 12, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, + 15, 16, 15, 13, 12, 13, 12, 13, 14, 14, 14, 14, 15, 16, 16, 16, 17, 17, 16, + 12, 13, 13, 13, 13, 14, 14, 15, 16, 16, 16, 16, 16, 16, 15, 16, 13, 13, 14, + 14, 14, 14, 15, 15, 15, 15, 17, 16, 16, 16, 16, 18, 13, 15, 14, 14, 14, 15, + 15, 16, 16, 16, 18, 17, 17, 17, 19, 17, 13, 14, 15, 13, 14, 16, 16, 15, 16, + 16, 17, 18, 17, 19, 17, 16, 13, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 13, + 13, 13, 13, 13, 10, +] as const; + +const t24l = [ + 4, 5, 7, 8, 9, 10, 10, 11, 11, 12, 12, 12, 12, 12, 13, 10, 5, 6, 7, 8, 9, 10, + 10, 11, 11, 11, 12, 12, 12, 12, 12, 10, 7, 7, 8, 9, 9, 10, 10, 11, 11, 11, 11, + 12, 12, 12, 13, 9, 8, 8, 9, 9, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 9, + 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12, 12, 12, 13, 9, 10, 9, 10, 10, 10, + 10, 11, 11, 11, 11, 12, 12, 12, 12, 12, 9, 10, 10, 10, 10, 10, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 13, 9, 11, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 12, + 13, 13, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 10, + 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 10, 12, 11, 11, + 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 10, 12, 12, 11, 11, 11, 12, + 12, 12, 12, 12, 12, 13, 13, 13, 13, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 13, 13, 13, 13, 13, 10, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, + 13, 13, 13, 10, 13, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, + 10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 6, +] as const; + +const t32l = [ + 1 + 0, + 4 + 1, + 4 + 1, + 5 + 2, + 4 + 1, + 6 + 2, + 5 + 2, + 6 + 3, + 4 + 1, + 5 + 2, + 5 + 2, + 6 + 3, + 5 + 2, + 6 + 3, + 6 + 3, + 6 + 4, +] as const; + +const t33l = [ + 4 + 0, + 4 + 1, + 4 + 1, + 4 + 2, + 4 + 1, + 4 + 2, + 4 + 2, + 4 + 3, + 4 + 1, + 4 + 2, + 4 + 2, + 4 + 3, + 4 + 2, + 4 + 3, + 4 + 3, + 4 + 4, +] as const; + +const ht: readonly HuffCodeTab[] = [ + { xlen: 0, linmax: 0 }, + { xlen: 2, linmax: 0, table: t1HB, hlen: t1l }, + { xlen: 3, linmax: 0, table: t2HB, hlen: t2l }, + { xlen: 3, linmax: 0, table: t3HB, hlen: t3l }, + { xlen: 0, linmax: 0 }, + { xlen: 4, linmax: 0, table: t5HB, hlen: t5l }, + { xlen: 4, linmax: 0, table: t6HB, hlen: t6l }, + { xlen: 6, linmax: 0, table: t7HB, hlen: t7l }, + { xlen: 6, linmax: 0, table: t8HB, hlen: t8l }, + { xlen: 6, linmax: 0, table: t9HB, hlen: t9l }, + { xlen: 8, linmax: 0, table: t10HB, hlen: t10l }, + { xlen: 8, linmax: 0, table: t11HB, hlen: t11l }, + { xlen: 8, linmax: 0, table: t12HB, hlen: t12l }, + { xlen: 16, linmax: 0, table: t13HB, hlen: t13l }, + { xlen: 0, linmax: 0, hlen: t16_5l }, + { xlen: 16, linmax: 0, table: t15HB, hlen: t15l }, + { xlen: 1, linmax: 1, table: t16HB, hlen: t16l }, + { xlen: 2, linmax: 3, table: t16HB, hlen: t16l }, + { xlen: 3, linmax: 7, table: t16HB, hlen: t16l }, + { xlen: 4, linmax: 15, table: t16HB, hlen: t16l }, + { xlen: 6, linmax: 63, table: t16HB, hlen: t16l }, + { xlen: 8, linmax: 255, table: t16HB, hlen: t16l }, + { xlen: 10, linmax: 1023, table: t16HB, hlen: t16l }, + { xlen: 13, linmax: 8191, table: t16HB, hlen: t16l }, + { xlen: 4, linmax: 15, table: t24HB, hlen: t24l }, + { xlen: 5, linmax: 31, table: t24HB, hlen: t24l }, + { xlen: 6, linmax: 63, table: t24HB, hlen: t24l }, + { xlen: 7, linmax: 127, table: t24HB, hlen: t24l }, + { xlen: 8, linmax: 255, table: t24HB, hlen: t24l }, + { xlen: 9, linmax: 511, table: t24HB, hlen: t24l }, + { xlen: 11, linmax: 2047, table: t24HB, hlen: t24l }, + { xlen: 13, linmax: 8191, table: t24HB, hlen: t24l }, + { xlen: 0, linmax: 0, table: t32HB, hlen: t32l }, + { xlen: 0, linmax: 0, table: t33HB, hlen: t33l }, +]; + +export { t32l, t33l, ht }; diff --git a/packages/mp3-encoder/src/lame/Takehiro.ts b/packages/mp3-encoder/src/lame/Takehiro.ts new file mode 100644 index 0000000000..5ccfaa2066 --- /dev/null +++ b/packages/mp3-encoder/src/lame/Takehiro.ts @@ -0,0 +1,1208 @@ +import { Bits } from './Bits'; +import type { CalcNoiseData } from './CalcNoiseData'; +import { GrInfo } from './GrInfo'; +import type { IIISideInfo } from './IIISideInfo'; +import type { LameInternalFlags } from './LameInternalFlags'; +import { QuantizePVT } from './QuantizePVT'; +import * as tables from './Tables'; +import { fillArray } from './arrays'; +import { assert } from './assert'; +import { NORM_TYPE, SBMAX_l, SBPSY_l, SHORT_TYPE } from './constants'; + +export class Takehiro { + private static readonly LARGE_BITS = 100000; + + private readonly qupvt: QuantizePVT; + + constructor(qupvt: QuantizePVT) { + this.qupvt = qupvt; + } + + private readonly largetbl = [ + 0x010004, 0x050005, 0x070007, 0x090008, 0x0a0009, 0x0a000a, 0x0b000a, + 0x0b000b, 0x0c000b, 0x0c000c, 0x0c000c, 0x0d000c, 0x0d000c, 0x0d000c, + 0x0e000d, 0x0a000a, 0x040005, 0x060006, 0x080007, 0x090008, 0x0a0009, + 0x0b000a, 0x0b000a, 0x0b000b, 0x0c000b, 0x0c000b, 0x0c000c, 0x0d000c, + 0x0e000c, 0x0d000c, 0x0e000c, 0x0a000a, 0x070007, 0x080007, 0x090008, + 0x0a0009, 0x0b0009, 0x0b000a, 0x0c000a, 0x0c000b, 0x0d000b, 0x0c000b, + 0x0d000b, 0x0d000c, 0x0d000c, 0x0e000c, 0x0e000d, 0x0b0009, 0x090008, + 0x090008, 0x0a0009, 0x0b0009, 0x0b000a, 0x0c000a, 0x0c000a, 0x0c000b, + 0x0d000b, 0x0d000b, 0x0e000b, 0x0e000c, 0x0e000c, 0x0f000c, 0x0f000c, + 0x0c0009, 0x0a0009, 0x0a0009, 0x0b0009, 0x0b000a, 0x0c000a, 0x0c000a, + 0x0d000a, 0x0d000b, 0x0d000b, 0x0e000b, 0x0e000c, 0x0e000c, 0x0f000c, + 0x0f000c, 0x0f000d, 0x0b0009, 0x0a000a, 0x0a0009, 0x0b000a, 0x0b000a, + 0x0c000a, 0x0d000a, 0x0d000b, 0x0e000b, 0x0d000b, 0x0e000b, 0x0e000c, + 0x0f000c, 0x0f000c, 0x0f000c, 0x10000c, 0x0c0009, 0x0b000a, 0x0b000a, + 0x0b000a, 0x0c000a, 0x0d000a, 0x0d000b, 0x0d000b, 0x0d000b, 0x0e000b, + 0x0e000c, 0x0e000c, 0x0e000c, 0x0f000c, 0x0f000c, 0x10000d, 0x0c0009, + 0x0b000b, 0x0b000a, 0x0c000a, 0x0c000a, 0x0d000b, 0x0d000b, 0x0d000b, + 0x0e000b, 0x0e000c, 0x0f000c, 0x0f000c, 0x0f000c, 0x0f000c, 0x11000d, + 0x11000d, 0x0c000a, 0x0b000b, 0x0c000b, 0x0c000b, 0x0d000b, 0x0d000b, + 0x0d000b, 0x0e000b, 0x0e000b, 0x0f000b, 0x0f000c, 0x0f000c, 0x0f000c, + 0x10000c, 0x10000d, 0x10000d, 0x0c000a, 0x0c000b, 0x0c000b, 0x0c000b, + 0x0d000b, 0x0d000b, 0x0e000b, 0x0e000b, 0x0f000c, 0x0f000c, 0x0f000c, + 0x0f000c, 0x10000c, 0x0f000d, 0x10000d, 0x0f000d, 0x0d000a, 0x0c000c, + 0x0d000b, 0x0c000b, 0x0d000b, 0x0e000b, 0x0e000c, 0x0e000c, 0x0e000c, + 0x0f000c, 0x10000c, 0x10000c, 0x10000d, 0x11000d, 0x11000d, 0x10000d, + 0x0c000a, 0x0d000c, 0x0d000c, 0x0d000b, 0x0d000b, 0x0e000b, 0x0e000c, + 0x0f000c, 0x10000c, 0x10000c, 0x10000c, 0x10000c, 0x10000d, 0x10000d, + 0x0f000d, 0x10000d, 0x0d000a, 0x0d000c, 0x0e000c, 0x0e000c, 0x0e000c, + 0x0e000c, 0x0f000c, 0x0f000c, 0x0f000c, 0x0f000c, 0x11000c, 0x10000d, + 0x10000d, 0x10000d, 0x10000d, 0x12000d, 0x0d000a, 0x0f000c, 0x0e000c, + 0x0e000c, 0x0e000c, 0x0f000c, 0x0f000c, 0x10000c, 0x10000c, 0x10000d, + 0x12000d, 0x11000d, 0x11000d, 0x11000d, 0x13000d, 0x11000d, 0x0d000a, + 0x0e000d, 0x0f000c, 0x0d000c, 0x0e000c, 0x10000c, 0x10000c, 0x0f000c, + 0x10000d, 0x10000d, 0x11000d, 0x12000d, 0x11000d, 0x13000d, 0x11000d, + 0x10000d, 0x0d000a, 0x0a0009, 0x0a0009, 0x0a0009, 0x0b0009, 0x0b0009, + 0x0c0009, 0x0c0009, 0x0c0009, 0x0d0009, 0x0d0009, 0x0d0009, 0x0d000a, + 0x0d000a, 0x0d000a, 0x0d000a, 0x0a0006, + ] as const; + + private readonly table23 = [ + 0x010002, 0x040003, 0x070007, 0x040004, 0x050004, 0x070007, 0x060006, + 0x070007, 0x080008, + ] as const; + + private readonly table56 = [ + 0x010003, 0x040004, 0x070006, 0x080008, 0x040004, 0x050004, 0x080006, + 0x090007, 0x070005, 0x080006, 0x090007, 0x0a0008, 0x080007, 0x080007, + 0x090008, 0x0a0009, + ] as const; + + private readonly scfsi_band = [0, 6, 11, 16, 21] as const; + + private readonly subdv_table = [ + [0, 0], + [0, 0], + [0, 0], + [0, 0], + [0, 0], + [0, 1], + [1, 1], + [1, 1], + [1, 2], + [2, 2], + [2, 3], + [2, 3], + [3, 4], + [3, 4], + [3, 4], + [4, 5], + [4, 5], + [4, 6], + [5, 6], + [5, 6], + [5, 7], + [6, 7], + [6, 7], + ] as const; + + private quantize_lines_xrpow_01( + l: number, + istep: number, + xr: Float32Array, + xrPos: number, + ix: Int32Array, + ixPos: number + ) { + const compareval0 = (1.0 - 0.4054) / istep; + + assert(l > 0); + l >>= 1; + while (l-- !== 0) { + ix[ixPos++] = compareval0 > xr[xrPos++] ? 0 : 1; + ix[ixPos++] = compareval0 > xr[xrPos++] ? 0 : 1; + } + } + + private quantize_lines_xrpow( + l: number, + istep: number, + xr: Float32Array, + xrPos: number, + ix: Int32Array, + ixPos: number + ) { + assert(l > 0); + + l >>= 1; + const remaining = l % 2; + l >>= 1; + while (l-- !== 0) { + let x0 = xr[xrPos++] * istep; + let x1 = xr[xrPos++] * istep; + const rx0 = Math.trunc(x0); + let x2 = xr[xrPos++] * istep; + const rx1 = Math.trunc(x1); + let x3 = xr[xrPos++] * istep; + const rx2 = Math.trunc(x2); + x0 += this.qupvt.adj43(rx0); + const rx3 = Math.trunc(x3); + x1 += this.qupvt.adj43(rx1); + ix[ixPos++] = Math.trunc(x0); + x2 += this.qupvt.adj43(rx2); + ix[ixPos++] = Math.trunc(x1); + x3 += this.qupvt.adj43(rx3); + ix[ixPos++] = Math.trunc(x2); + ix[ixPos++] = Math.trunc(x3); + } + if (remaining !== 0) { + let x0 = xr[xrPos++] * istep; + let x1 = xr[xrPos++] * istep; + const rx0 = Math.trunc(x0); + const rx1 = Math.trunc(x1); + x0 += this.qupvt.adj43(rx0); + x1 += this.qupvt.adj43(rx1); + ix[ixPos++] = Math.trunc(x0); + ix[ixPos++] = Math.trunc(x1); + } + } + + private quantize_xrpow( + xp: Float32Array, + pi: Int32Array, + istep: number, + codInfo: GrInfo, + prevNoise: CalcNoiseData | null + ) { + let sfb; + let sfbmax; + let j = 0; + let accumulate = 0; + let accumulate01 = 0; + let xpPos = 0; + const iData = pi; + let iDataPos = 0; + let acc_iData = iData; + let acc_iDataPos = 0; + let acc_xp = xp; + let acc_xpPos = 0; + + const prev_data_use = + prevNoise !== null && codInfo.global_gain === prevNoise.global_gain; + + if (codInfo.block_type === SHORT_TYPE) sfbmax = 38; + else sfbmax = 21; + + for (sfb = 0; sfb <= sfbmax; sfb++) { + let step = -1; + + if (prev_data_use || codInfo.block_type === NORM_TYPE) { + step = + codInfo.global_gain - + ((codInfo.scalefac[sfb] + + (codInfo.preflag !== 0 ? this.qupvt.pretab[sfb] : 0)) << + (codInfo.scalefac_scale + 1)) - + codInfo.subblock_gain[codInfo.window[sfb]] * 8; + } + assert(codInfo.width[sfb] >= 0); + if (prev_data_use && prevNoise.step[sfb] === step) { + if (accumulate !== 0) { + this.quantize_lines_xrpow( + accumulate, + istep, + acc_xp, + acc_xpPos, + acc_iData, + acc_iDataPos + ); + accumulate = 0; + } + if (accumulate01 !== 0) { + this.quantize_lines_xrpow_01( + accumulate01, + istep, + acc_xp, + acc_xpPos, + acc_iData, + acc_iDataPos + ); + accumulate01 = 0; + } + } else { + let l = codInfo.width[sfb]; + + if (j + codInfo.width[sfb] > codInfo.max_nonzero_coeff) { + const usefullsize = codInfo.max_nonzero_coeff - j + 1; + fillArray(pi, codInfo.max_nonzero_coeff, 576, 0); + l = usefullsize; + + if (l < 0) { + l = 0; + } + + sfb = sfbmax + 1; + } + + if (accumulate === 0 && accumulate01 === 0) { + acc_iData = iData; + acc_iDataPos = iDataPos; + acc_xp = xp; + acc_xpPos = xpPos; + } + if ( + prevNoise !== null && + prevNoise.sfb_count1 > 0 && + sfb >= prevNoise.sfb_count1 && + prevNoise.step[sfb] > 0 && + step >= prevNoise.step[sfb] + ) { + if (accumulate !== 0) { + this.quantize_lines_xrpow( + accumulate, + istep, + acc_xp, + acc_xpPos, + acc_iData, + acc_iDataPos + ); + accumulate = 0; + acc_iData = iData; + acc_iDataPos = iDataPos; + acc_xp = xp; + acc_xpPos = xpPos; + } + accumulate01 += l; + } else { + if (accumulate01 !== 0) { + this.quantize_lines_xrpow_01( + accumulate01, + istep, + acc_xp, + acc_xpPos, + acc_iData, + acc_iDataPos + ); + accumulate01 = 0; + acc_iData = iData; + acc_iDataPos = iDataPos; + acc_xp = xp; + acc_xpPos = xpPos; + } + accumulate += l; + } + + if (l <= 0) { + if (accumulate01 !== 0) { + this.quantize_lines_xrpow_01( + accumulate01, + istep, + acc_xp, + acc_xpPos, + acc_iData, + acc_iDataPos + ); + accumulate01 = 0; + } + if (accumulate !== 0) { + this.quantize_lines_xrpow( + accumulate, + istep, + acc_xp, + acc_xpPos, + acc_iData, + acc_iDataPos + ); + accumulate = 0; + } + + break; + } + } + if (sfb <= sfbmax) { + iDataPos += codInfo.width[sfb]; + xpPos += codInfo.width[sfb]; + j += codInfo.width[sfb]; + } + } + + if (accumulate !== 0) { + this.quantize_lines_xrpow( + accumulate, + istep, + acc_xp, + acc_xpPos, + acc_iData, + acc_iDataPos + ); + } + + if (accumulate01 !== 0) { + this.quantize_lines_xrpow_01( + accumulate01, + istep, + acc_xp, + acc_xpPos, + acc_iData, + acc_iDataPos + ); + } + } + + private ix_max(ix: Int32Array, ixPos: number, endPos: number) { + let max1 = 0; + let max2 = 0; + + do { + const x1 = ix[ixPos++]; + const x2 = ix[ixPos++]; + if (max1 < x1) max1 = x1; + + if (max2 < x2) max2 = x2; + } while (ixPos < endPos); + if (max1 < max2) max1 = max2; + return max1; + } + + private count_bit_ESC( + ix: Int32Array, + ixPos: number, + end: number, + t1: number, + t2: number, + s: Bits + ) { + const linbits = tables.ht[t1].xlen * 65536 + tables.ht[t2].xlen; + let sum = 0; + do { + let x = ix[ixPos++]; + let y = ix[ixPos++]; + + if (x !== 0) { + if (x > 14) { + x = 15; + sum += linbits; + } + x *= 16; + } + + if (y !== 0) { + if (y > 14) { + y = 15; + sum += linbits; + } + x += y; + } + + sum += this.largetbl[x]; + } while (ixPos < end); + + const sum2 = sum & 0xffff; + sum >>= 16; + + if (sum > sum2) { + sum = sum2; + t1 = t2; + } + + s.bits += sum; + return t1; + } + + private count_bit_noESC(ix: Int32Array, ixPos: number, end: number, s: Bits) { + let sum1 = 0; + const hlen1 = tables.ht[1].hlen; + assert(hlen1 !== undefined); + + do { + const x = ix[ixPos + 0] * 2 + ix[ixPos + 1]; + ixPos += 2; + sum1 += hlen1[x]; + } while (ixPos < end); + + s.bits += sum1; + return 1; + } + + private count_bit_noESC_from2( + ix: Int32Array, + ixPos: number, + end: number, + t1: number, + s: Bits + ) { + let sum = 0; + const { xlen } = tables.ht[t1]; + let hlen; + if (t1 === 2) hlen = this.table23; + else hlen = this.table56; + + do { + const x = ix[ixPos + 0] * xlen + ix[ixPos + 1]; + ixPos += 2; + sum += hlen[x]; + } while (ixPos < end); + + const sum2 = sum & 0xffff; + sum >>= 16; + + if (sum > sum2) { + sum = sum2; + t1++; + } + + s.bits += sum; + return t1; + } + + private count_bit_noESC_from3( + ix: Int32Array, + ixPos: number, + end: number, + t1: number, + s: Bits + ) { + let sum1 = 0; + let sum2 = 0; + let sum3 = 0; + const { xlen } = tables.ht[t1]; + const hlen1 = tables.ht[t1].hlen; + const hlen2 = tables.ht[t1 + 1].hlen; + const hlen3 = tables.ht[t1 + 2].hlen; + + assert(hlen1 !== undefined); + assert(hlen2 !== undefined); + assert(hlen3 !== undefined); + + do { + const x = ix[ixPos + 0] * xlen + ix[ixPos + 1]; + ixPos += 2; + sum1 += hlen1[x]; + sum2 += hlen2[x]; + sum3 += hlen3[x]; + } while (ixPos < end); + let t = t1; + if (sum1 > sum2) { + sum1 = sum2; + t++; + } + if (sum1 > sum3) { + sum1 = sum3; + t = t1 + 2; + } + s.bits += sum1; + + return t; + } + + private readonly huf_tbl_noESC = [ + 1, 2, 5, 7, 7, 10, 10, 13, 13, 13, 13, 13, 13, 13, 13, + ] as const; + + private choose_table(ix: Int32Array, ixPos: number, endPos: number, s: Bits) { + let max = this.ix_max(ix, ixPos, endPos); + + switch (max) { + case 0: + return max; + + case 1: + return this.count_bit_noESC(ix, ixPos, endPos, s); + + case 2: + case 3: + return this.count_bit_noESC_from2( + ix, + ixPos, + endPos, + this.huf_tbl_noESC[max - 1], + s + ); + + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + return this.count_bit_noESC_from3( + ix, + ixPos, + endPos, + this.huf_tbl_noESC[max - 1], + s + ); + + default: + if (max > QuantizePVT.IXMAX_VAL) { + s.bits = Takehiro.LARGE_BITS; + return -1; + } + max -= 15; + let choice2; + for (choice2 = 24; choice2 < 32; choice2++) { + if (tables.ht[choice2].linmax >= max) { + break; + } + } + let choice; + for (choice = choice2 - 8; choice < 24; choice++) { + if (tables.ht[choice].linmax >= max) { + break; + } + } + return this.count_bit_ESC(ix, ixPos, endPos, choice, choice2, s); + } + } + + noquant_count_bits( + gfc: LameInternalFlags, + gi: GrInfo, + prev_noise: CalcNoiseData | null + ) { + const ix = gi.l3_enc; + let i = Math.min(576, ((gi.max_nonzero_coeff + 2) >> 1) << 1); + + if (prev_noise !== null) prev_noise.sfb_count1 = 0; + + for (; i > 1; i -= 2) if ((ix[i - 1] | ix[i - 2]) !== 0) break; + gi.count1 = i; + + let a1 = 0; + let a2 = 0; + for (; i > 3; i -= 4) { + if (((ix[i - 1] | ix[i - 2] | ix[i - 3] | ix[i - 4]) & 0x7fffffff) > 1) { + break; + } + const p = ((ix[i - 4] * 2 + ix[i - 3]) * 2 + ix[i - 2]) * 2 + ix[i - 1]; + a1 += tables.t32l[p]; + a2 += tables.t33l[p]; + } + let bits = a1; + gi.count1table_select = 0; + if (a1 > a2) { + bits = a2; + gi.count1table_select = 1; + } + + gi.count1bits = bits; + gi.big_values = i; + if (i === 0) return bits; + + if (gi.block_type === SHORT_TYPE) { + a1 = 3 * gfc.scalefac_band.s[3]; + if (a1 > gi.big_values) a1 = gi.big_values; + a2 = gi.big_values; + } else if (gi.block_type === NORM_TYPE) { + assert(i <= 576); + + a1 = gfc.bv_scf[i - 2]; + gi.region0_count = gfc.bv_scf[i - 2]; + a2 = gfc.bv_scf[i - 1]; + gi.region1_count = gfc.bv_scf[i - 1]; + + assert(a1 + a2 + 2 < SBPSY_l); + a2 = gfc.scalefac_band.l[a1 + a2 + 2]; + a1 = gfc.scalefac_band.l[a1 + 1]; + if (a2 < i) { + const bi = new Bits(bits); + gi.table_select[2] = this.choose_table(ix, a2, i, bi); + bits = bi.bits; + } + } else { + gi.region0_count = 7; + + gi.region1_count = SBMAX_l - 1 - 7 - 1; + a1 = gfc.scalefac_band.l[7 + 1]; + a2 = i; + if (a1 > a2) { + a1 = a2; + } + } + + a1 = Math.min(a1, i); + a2 = Math.min(a2, i); + + assert(a1 >= 0); + assert(a2 >= 0); + + if (a1 > 0) { + const bi = new Bits(bits); + gi.table_select[0] = this.choose_table(ix, 0, a1, bi); + bits = bi.bits; + } + if (a1 < a2) { + const bi = new Bits(bits); + gi.table_select[1] = this.choose_table(ix, a1, a2, bi); + bits = bi.bits; + } + if (gfc.use_best_huffman === 2) { + gi.part2_3_length = bits; + this.best_huffman_divide(gfc, gi); + bits = gi.part2_3_length; + } + + if (prev_noise !== null) { + if (gi.block_type === NORM_TYPE) { + let sfb = 0; + while (gfc.scalefac_band.l[sfb] < gi.big_values) { + sfb++; + } + prev_noise.sfb_count1 = sfb; + } + } + + return bits; + } + + count_bits( + gfc: LameInternalFlags, + xr: Float32Array, + gi: GrInfo, + prev_noise: CalcNoiseData | null + ) { + const ix = gi.l3_enc; + + const w = QuantizePVT.IXMAX_VAL / this.qupvt.ipow20(gi.global_gain); + + if (gi.xrpow_max > w) return Takehiro.LARGE_BITS; + + this.quantize_xrpow( + xr, + ix, + this.qupvt.ipow20(gi.global_gain), + gi, + prev_noise + ); + + if ((gfc.substep_shaping & 2) !== 0) { + let j = 0; + + const gain = gi.global_gain + gi.scalefac_scale; + const roundfac = 0.634521682242439 / this.qupvt.ipow20(gain); + for (let sfb = 0; sfb < gi.sfbmax; sfb++) { + const width = gi.width[sfb]; + assert(width >= 0); + if (gfc.pseudohalf[sfb] === 0) { + j += width; + } else { + let k; + for (k = j, j += width; k < j; ++k) { + ix[k] = xr[k] >= roundfac ? ix[k] : 0; + } + } + } + } + return this.noquant_count_bits(gfc, gi, prev_noise); + } + + private recalc_divide_init( + gfc: LameInternalFlags, + cod_info: GrInfo, + ix: Int32Array, + r01_bits: Int32Array, + r01_div: Int32Array, + r0_tbl: Int32Array, + r1_tbl: Int32Array + ) { + const bigv = cod_info.big_values; + + for (let r0 = 0; r0 <= 7 + 15; r0++) { + r01_bits[r0] = Takehiro.LARGE_BITS; + } + + for (let r0 = 0; r0 < 16; r0++) { + const a1 = gfc.scalefac_band.l[r0 + 1]; + if (a1 >= bigv) break; + let r0bits = 0; + let bi = new Bits(r0bits); + const r0t = this.choose_table(ix, 0, a1, bi); + r0bits = bi.bits; + + for (let r1 = 0; r1 < 8; r1++) { + const a2 = gfc.scalefac_band.l[r0 + r1 + 2]; + if (a2 >= bigv) break; + let bits = r0bits; + bi = new Bits(bits); + const r1t = this.choose_table(ix, a1, a2, bi); + bits = bi.bits; + if (r01_bits[r0 + r1] > bits) { + r01_bits[r0 + r1] = bits; + r01_div[r0 + r1] = r0; + r0_tbl[r0 + r1] = r0t; + r1_tbl[r0 + r1] = r1t; + } + } + } + } + + private recalc_divide_sub( + gfc: LameInternalFlags, + cod_info2: GrInfo, + gi: GrInfo, + ix: Int32Array, + r01_bits: Int32Array, + r01_div: Int32Array, + r0_tbl: Int32Array, + r1_tbl: Int32Array + ) { + const bigv = cod_info2.big_values; + + for (let r2 = 2; r2 < SBMAX_l + 1; r2++) { + const a2 = gfc.scalefac_band.l[r2]; + if (a2 >= bigv) break; + let bits = r01_bits[r2 - 2] + cod_info2.count1bits; + if (gi.part2_3_length <= bits) break; + + const bi = new Bits(bits); + const r2t = this.choose_table(ix, a2, bigv, bi); + bits = bi.bits; + if (gi.part2_3_length <= bits) continue; + + gi.assign(cod_info2); + gi.part2_3_length = bits; + gi.region0_count = r01_div[r2 - 2]; + gi.region1_count = r2 - 2 - r01_div[r2 - 2]; + gi.table_select[0] = r0_tbl[r2 - 2]; + gi.table_select[1] = r1_tbl[r2 - 2]; + gi.table_select[2] = r2t; + } + } + + best_huffman_divide(gfc: LameInternalFlags, gi: GrInfo) { + const cod_info2 = new GrInfo(); + const ix = gi.l3_enc; + const r01_bits = new Int32Array(7 + 15 + 1); + const r01_div = new Int32Array(7 + 15 + 1); + const r0_tbl = new Int32Array(7 + 15 + 1); + const r1_tbl = new Int32Array(7 + 15 + 1); + + if (gi.block_type === SHORT_TYPE && gfc.mode_gr === 1) return; + + cod_info2.assign(gi); + if (gi.block_type === NORM_TYPE) { + this.recalc_divide_init(gfc, gi, ix, r01_bits, r01_div, r0_tbl, r1_tbl); + this.recalc_divide_sub( + gfc, + cod_info2, + gi, + ix, + r01_bits, + r01_div, + r0_tbl, + r1_tbl + ); + } + let i = cod_info2.big_values; + if (i === 0 || (ix[i - 2] | ix[i - 1]) > 1) return; + + i = gi.count1 + 2; + if (i > 576) return; + + cod_info2.assign(gi); + cod_info2.count1 = i; + let a1 = 0; + let a2 = 0; + + assert(i <= 576); + + for (; i > cod_info2.big_values; i -= 4) { + const p = ((ix[i - 4] * 2 + ix[i - 3]) * 2 + ix[i - 2]) * 2 + ix[i - 1]; + a1 += tables.t32l[p]; + a2 += tables.t33l[p]; + } + cod_info2.big_values = i; + + cod_info2.count1table_select = 0; + if (a1 > a2) { + a1 = a2; + cod_info2.count1table_select = 1; + } + + cod_info2.count1bits = a1; + + if (cod_info2.block_type === NORM_TYPE) { + this.recalc_divide_sub( + gfc, + cod_info2, + gi, + ix, + r01_bits, + r01_div, + r0_tbl, + r1_tbl + ); + } else { + cod_info2.part2_3_length = a1; + a1 = gfc.scalefac_band.l[7 + 1]; + if (a1 > i) { + a1 = i; + } + if (a1 > 0) { + const bi = new Bits(cod_info2.part2_3_length); + cod_info2.table_select[0] = this.choose_table(ix, 0, a1, bi); + cod_info2.part2_3_length = bi.bits; + } + if (i > a1) { + const bi = new Bits(cod_info2.part2_3_length); + cod_info2.table_select[1] = this.choose_table(ix, a1, i, bi); + cod_info2.part2_3_length = bi.bits; + } + if (gi.part2_3_length > cod_info2.part2_3_length) gi.assign(cod_info2); + } + } + + static readonly slen1_tab = [ + 0, 0, 0, 0, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, + ] as const; + + static readonly slen2_tab = [ + 0, 1, 2, 3, 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 2, 3, + ] as const; + + private readonly slen1_n = [ + 1, 1, 1, 1, 8, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, + ] as const; + + private readonly slen2_n = [ + 1, 2, 4, 8, 1, 2, 4, 8, 2, 4, 8, 2, 4, 8, 4, 8, + ] as const; + + private scfsi_calc(ch: number, l3_side: IIISideInfo) { + let sfb; + const gi = l3_side.tt[1][ch]; + const g0 = l3_side.tt[0][ch]; + + for (let i = 0; i < this.scfsi_band.length - 1; i++) { + for (sfb = this.scfsi_band[i]; sfb < this.scfsi_band[i + 1]; sfb++) { + if (g0.scalefac[sfb] !== gi.scalefac[sfb] && gi.scalefac[sfb] >= 0) + break; + } + if (sfb === this.scfsi_band[i + 1]) { + for (sfb = this.scfsi_band[i]; sfb < this.scfsi_band[i + 1]; sfb++) { + gi.scalefac[sfb] = -1; + } + l3_side.scfsi[ch][i] = 1; + } + } + let s1 = 0; + let c1 = 0; + for (sfb = 0; sfb < 11; sfb++) { + if (gi.scalefac[sfb] === -1) continue; + c1++; + if (s1 < gi.scalefac[sfb]) s1 = gi.scalefac[sfb]; + } + let s2 = 0; + let c2 = 0; + for (; sfb < SBPSY_l; sfb++) { + if (gi.scalefac[sfb] === -1) continue; + c2++; + if (s2 < gi.scalefac[sfb]) s2 = gi.scalefac[sfb]; + } + + for (let i = 0; i < 16; i++) { + if (s1 < this.slen1_n[i] && s2 < this.slen2_n[i]) { + const c = Takehiro.slen1_tab[i] * c1 + Takehiro.slen2_tab[i] * c2; + if (gi.part2_length > c) { + gi.part2_length = c; + gi.scalefac_compress = i; + } + } + } + } + + best_scalefac_store( + gfc: LameInternalFlags, + gr: number, + ch: number, + l3_side: IIISideInfo + ) { + const gi = l3_side.tt[gr][ch]; + let sfb; + let i; + let j; + let l; + let recalc = 0; + + j = 0; + for (sfb = 0; sfb < gi.sfbmax; sfb++) { + const width = gi.width[sfb]; + assert(width >= 0); + j += width; + for (l = -width; l < 0; l++) { + if (gi.l3_enc[l + j] !== 0) break; + } + if (l === 0) { + gi.scalefac[sfb] = -2; + recalc = -2; + } + } + + if (gi.scalefac_scale === 0 && gi.preflag === 0) { + let s = 0; + for (sfb = 0; sfb < gi.sfbmax; sfb++) + if (gi.scalefac[sfb] > 0) s |= gi.scalefac[sfb]; + + if ((s & 1) === 0 && s !== 0) { + for (sfb = 0; sfb < gi.sfbmax; sfb++) { + if (gi.scalefac[sfb] > 0) { + gi.scalefac[sfb] >>= 1; + } + } + + gi.scalefac_scale = 1; + recalc = 1; + } + } + + if (gi.preflag === 0 && gi.block_type !== SHORT_TYPE && gfc.mode_gr === 2) { + for (sfb = 11; sfb < SBPSY_l; sfb++) + if ( + gi.scalefac[sfb] < this.qupvt.pretab[sfb] && + gi.scalefac[sfb] !== -2 + ) + break; + if (sfb === SBPSY_l) { + for (sfb = 11; sfb < SBPSY_l; sfb++) + if (gi.scalefac[sfb] > 0) gi.scalefac[sfb] -= this.qupvt.pretab[sfb]; + + gi.preflag = 1; + recalc = 1; + } + } + + for (i = 0; i < 4; i++) { + l3_side.scfsi[ch][i] = 0; + } + + if ( + gfc.mode_gr === 2 && + gr === 1 && + l3_side.tt[0][ch].block_type !== SHORT_TYPE && + l3_side.tt[1][ch].block_type !== SHORT_TYPE + ) { + this.scfsi_calc(ch, l3_side); + recalc = 0; + } + for (sfb = 0; sfb < gi.sfbmax; sfb++) { + if (gi.scalefac[sfb] === -2) { + gi.scalefac[sfb] = 0; + } + } + if (recalc !== 0) { + if (gfc.mode_gr === 2) { + this.scale_bitcount(gi); + } else { + this.scale_bitcount_lsf(gfc, gi); + } + } + } + + private all_scalefactors_not_negative(scalefac: Int32Array, n: number) { + for (let i = 0; i < n; ++i) { + if (scalefac[i] < 0) return false; + } + return true; + } + + private readonly scale_short = [ + 0, 18, 36, 54, 54, 36, 54, 72, 54, 72, 90, 72, 90, 108, 108, 126, + ] as const; + + private readonly scale_mixed = [ + 0, 18, 36, 54, 51, 35, 53, 71, 52, 70, 88, 69, 87, 105, 104, 122, + ] as const; + + private readonly scale_long = [ + 0, 10, 20, 30, 33, 21, 31, 41, 32, 42, 52, 43, 53, 63, 64, 74, + ] as const; + + scale_bitcount(cod_info: GrInfo) { + let k; + let sfb; + let max_slen1 = 0; + let max_slen2 = 0; + + let tab; + const { scalefac } = cod_info; + + assert(this.all_scalefactors_not_negative(scalefac, cod_info.sfbmax)); + + if (cod_info.block_type === SHORT_TYPE) { + tab = this.scale_short; + if (cod_info.mixed_block_flag !== 0) tab = this.scale_mixed; + } else { + tab = this.scale_long; + if (cod_info.preflag === 0) { + for (sfb = 11; sfb < SBPSY_l; sfb++) + if (scalefac[sfb] < this.qupvt.pretab[sfb]) break; + + if (sfb === SBPSY_l) { + cod_info.preflag = 1; + for (sfb = 11; sfb < SBPSY_l; sfb++) + scalefac[sfb] -= this.qupvt.pretab[sfb]; + } + } + } + + for (sfb = 0; sfb < cod_info.sfbdivide; sfb++) { + if (max_slen1 < scalefac[sfb]) { + max_slen1 = scalefac[sfb]; + } + } + + for (; sfb < cod_info.sfbmax; sfb++) { + if (max_slen2 < scalefac[sfb]) { + max_slen2 = scalefac[sfb]; + } + } + + cod_info.part2_length = Takehiro.LARGE_BITS; + for (k = 0; k < 16; k++) { + if ( + max_slen1 < this.slen1_n[k] && + max_slen2 < this.slen2_n[k] && + cod_info.part2_length > tab[k] + ) { + cod_info.part2_length = tab[k]; + cod_info.scalefac_compress = k; + } + } + return cod_info.part2_length === Takehiro.LARGE_BITS; + } + + private readonly max_range_sfac_tab = [ + [15, 15, 7, 7], + [15, 15, 7, 0], + [7, 3, 0, 0], + [15, 31, 31, 0], + [7, 7, 7, 0], + [3, 3, 0, 0], + ] as const; + + private readonly log2tab = [ + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + ] as const; + + scale_bitcount_lsf(_gfc: LameInternalFlags, cod_info: GrInfo) { + let table_number; + let row_in_table; + let partition; + let nr_sfb; + let window; + let over; + let i; + let sfb; + const max_sfac = new Int32Array(4); + + const { scalefac } = cod_info; + + if (cod_info.preflag !== 0) { + table_number = 2; + } else { + table_number = 0; + } + + for (i = 0; i < 4; i++) { + max_sfac[i] = 0; + } + + if (cod_info.block_type === SHORT_TYPE) { + row_in_table = 1; + const partition_table = + this.qupvt.nr_of_sfb_block[table_number][row_in_table]; + for (sfb = 0, partition = 0; partition < 4; partition++) { + nr_sfb = partition_table[partition] / 3; + for (i = 0; i < nr_sfb; i++, sfb++) { + for (window = 0; window < 3; window++) { + if (scalefac[sfb * 3 + window] > max_sfac[partition]) { + max_sfac[partition] = scalefac[sfb * 3 + window]; + } + } + } + } + } else { + row_in_table = 0; + const partition_table = + this.qupvt.nr_of_sfb_block[table_number][row_in_table]; + for (sfb = 0, partition = 0; partition < 4; partition++) { + nr_sfb = partition_table[partition]; + for (i = 0; i < nr_sfb; i++, sfb++) { + if (scalefac[sfb] > max_sfac[partition]) { + max_sfac[partition] = scalefac[sfb]; + } + } + } + } + + for (over = false, partition = 0; partition < 4; partition++) { + if ( + max_sfac[partition] > this.max_range_sfac_tab[table_number][partition] + ) { + over = true; + } + } + if (!over) { + cod_info.sfb_partition_table = + this.qupvt.nr_of_sfb_block[table_number][row_in_table]; + for (partition = 0; partition < 4; partition++) { + cod_info.slen[partition] = this.log2tab[max_sfac[partition]]; + } + + const slen1 = cod_info.slen[0]; + const slen2 = cod_info.slen[1]; + const slen3 = cod_info.slen[2]; + const slen4 = cod_info.slen[3]; + + switch (table_number) { + case 0: + cod_info.scalefac_compress = + ((slen1 * 5 + slen2) << 4) + (slen3 << 2) + slen4; + break; + + case 1: + cod_info.scalefac_compress = 400 + ((slen1 * 5 + slen2) << 2) + slen3; + break; + + case 2: + cod_info.scalefac_compress = 500 + slen1 * 3 + slen2; + break; + + default: + console.warn('intensity stereo not implemented yet'); + break; + } + } + if (!over) { + assert(cod_info.sfb_partition_table !== null); + cod_info.part2_length = 0; + for (partition = 0; partition < 4; partition++) + cod_info.part2_length += + cod_info.slen[partition] * cod_info.sfb_partition_table[partition]; + } + return over; + } + + huffman_init(gfc: LameInternalFlags) { + for (let i = 2; i <= 576; i += 2) { + let scfb_anz = 0; + let bv_index; + while (gfc.scalefac_band.l[++scfb_anz] < i); + + bv_index = this.subdv_table[scfb_anz][0]; + while (gfc.scalefac_band.l[bv_index + 1] > i) bv_index--; + + if (bv_index < 0) { + bv_index = this.subdv_table[scfb_anz][0]; + } + + gfc.bv_scf[i - 2] = bv_index; + + bv_index = this.subdv_table[scfb_anz][1]; + while (gfc.scalefac_band.l[bv_index + gfc.bv_scf[i - 2] + 2] > i) + bv_index--; + + if (bv_index < 0) { + bv_index = this.subdv_table[scfb_anz][1]; + } + + gfc.bv_scf[i - 1] = bv_index; + } + } +} diff --git a/packages/mp3-encoder/src/lame/TotalBytes.ts b/packages/mp3-encoder/src/lame/TotalBytes.ts new file mode 100644 index 0000000000..37789e349e --- /dev/null +++ b/packages/mp3-encoder/src/lame/TotalBytes.ts @@ -0,0 +1,3 @@ +export class TotalBytes { + total = 0; +} diff --git a/packages/mp3-encoder/src/lame/VBRPresets.ts b/packages/mp3-encoder/src/lame/VBRPresets.ts new file mode 100644 index 0000000000..dc1a2bb696 --- /dev/null +++ b/packages/mp3-encoder/src/lame/VBRPresets.ts @@ -0,0 +1,451 @@ +import type { LameGlobalFlags } from './LameGlobalFlags'; +import type { Quality } from './Quality'; +import { VbrMode } from './VbrMode'; +import { equals } from './math'; + +interface VBRPreset { + readonly vbr_q: Quality; + readonly quant_comp: 9; + readonly quant_comp_s: 9; + readonly expY: 0 | 1; + readonly st_lrm: number; + readonly st_s: number; + readonly masking_adj: number; + readonly masking_adj_short: number; + readonly ath_lower: number; + readonly ath_curve: number; + readonly ath_sensitivity: number; + readonly interch: number; + readonly safejoint: number; + readonly sfb21mod: number; + readonly msfix: number; +} + +type PresetMap = Record<Quality, VBRPreset>; + +export class VBRPresets { + private readonly oldSwitchMap = [ + { + vbr_q: 0, + quant_comp: 9, + quant_comp_s: 9, + expY: 0, + st_lrm: 5.2, + st_s: 125, + masking_adj: -4.2, + masking_adj_short: -6.3, + ath_lower: 4.8, + ath_curve: 1, + ath_sensitivity: 0, + interch: 0, + safejoint: 2, + sfb21mod: 21, + msfix: 0.97, + }, + { + vbr_q: 1, + quant_comp: 9, + quant_comp_s: 9, + expY: 0, + st_lrm: 5.3, + st_s: 125, + masking_adj: -3.6, + masking_adj_short: -5.6, + ath_lower: 4.5, + ath_curve: 1.5, + ath_sensitivity: 0, + interch: 0, + safejoint: 2, + sfb21mod: 21, + msfix: 1.35, + }, + { + vbr_q: 2, + quant_comp: 9, + quant_comp_s: 9, + expY: 0, + st_lrm: 5.6, + st_s: 125, + masking_adj: -2.2, + masking_adj_short: -3.5, + ath_lower: 2.8, + ath_curve: 2, + ath_sensitivity: 0, + interch: 0, + safejoint: 2, + sfb21mod: 21, + msfix: 1.49, + }, + { + vbr_q: 3, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 5.8, + st_s: 130, + masking_adj: -1.8, + masking_adj_short: -2.8, + ath_lower: 2.6, + ath_curve: 3, + ath_sensitivity: -4, + interch: 0, + safejoint: 2, + sfb21mod: 20, + msfix: 1.64, + }, + { + vbr_q: 4, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 6, + st_s: 135, + masking_adj: -0.7, + masking_adj_short: -1.1, + ath_lower: 1.1, + ath_curve: 3.5, + ath_sensitivity: -8, + interch: 0, + safejoint: 2, + sfb21mod: 0, + msfix: 1.79, + }, + { + vbr_q: 5, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 6.4, + st_s: 140, + masking_adj: 0.5, + masking_adj_short: 0.4, + ath_lower: -7.5, + ath_curve: 4, + ath_sensitivity: -12, + interch: 0.0002, + safejoint: 0, + sfb21mod: 0, + msfix: 1.95, + }, + { + vbr_q: 6, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 6.6, + st_s: 145, + masking_adj: 0.67, + masking_adj_short: 0.65, + ath_lower: -14.7, + ath_curve: 6.5, + ath_sensitivity: -19, + interch: 0.0004, + safejoint: 0, + sfb21mod: 0, + msfix: 2.3, + }, + { + vbr_q: 7, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 6.6, + st_s: 145, + masking_adj: 0.8, + masking_adj_short: 0.75, + ath_lower: -19.7, + ath_curve: 8, + ath_sensitivity: -22, + interch: 0.0006, + safejoint: 0, + sfb21mod: 0, + msfix: 2.7, + }, + { + vbr_q: 8, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 6.6, + st_s: 145, + masking_adj: 1.2, + masking_adj_short: 1.15, + ath_lower: -27.5, + ath_curve: 10, + ath_sensitivity: -23, + interch: 0.0007, + safejoint: 0, + sfb21mod: 0, + msfix: 0, + }, + { + vbr_q: 9, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 6.6, + st_s: 145, + masking_adj: 1.6, + masking_adj_short: 1.6, + ath_lower: -36, + ath_curve: 11, + ath_sensitivity: -25, + interch: 0.0008, + safejoint: 0, + sfb21mod: 0, + msfix: 0, + }, + ] as const satisfies PresetMap; + + private readonly switchMap = [ + { + vbr_q: 0, + quant_comp: 9, + quant_comp_s: 9, + expY: 0, + st_lrm: 4.2, + st_s: 25, + masking_adj: -7, + masking_adj_short: -4, + ath_lower: 7.5, + ath_curve: 1, + ath_sensitivity: 0, + interch: 0, + safejoint: 2, + sfb21mod: 26, + msfix: 0.97, + }, + { + vbr_q: 1, + quant_comp: 9, + quant_comp_s: 9, + expY: 0, + st_lrm: 4.2, + st_s: 25, + masking_adj: -5.6, + masking_adj_short: -3.6, + ath_lower: 4.5, + ath_curve: 1.5, + ath_sensitivity: 0, + interch: 0, + safejoint: 2, + sfb21mod: 21, + msfix: 1.35, + }, + { + vbr_q: 2, + quant_comp: 9, + quant_comp_s: 9, + expY: 0, + st_lrm: 4.2, + st_s: 25, + masking_adj: -4.4, + masking_adj_short: -1.8, + ath_lower: 2, + ath_curve: 2, + ath_sensitivity: 0, + interch: 0, + safejoint: 2, + sfb21mod: 18, + msfix: 1.49, + }, + { + vbr_q: 3, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 4.2, + st_s: 25, + masking_adj: -3.4, + masking_adj_short: -1.25, + ath_lower: 1.1, + ath_curve: 3, + ath_sensitivity: -4, + interch: 0, + safejoint: 2, + sfb21mod: 15, + msfix: 1.64, + }, + { + vbr_q: 4, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 4.2, + st_s: 25, + masking_adj: -2.2, + masking_adj_short: 0.1, + ath_lower: 0, + ath_curve: 3.5, + ath_sensitivity: -8, + interch: 0, + safejoint: 2, + sfb21mod: 0, + msfix: 1.79, + }, + { + vbr_q: 5, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 4.2, + st_s: 25, + masking_adj: -1, + masking_adj_short: 1.65, + ath_lower: -7.7, + ath_curve: 4, + ath_sensitivity: -12, + interch: 0.0002, + safejoint: 0, + sfb21mod: 0, + msfix: 1.95, + }, + { + vbr_q: 6, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 4.2, + st_s: 25, + masking_adj: -0, + masking_adj_short: 2.47, + ath_lower: -7.7, + ath_curve: 6.5, + ath_sensitivity: -19, + interch: 0.0004, + safejoint: 0, + sfb21mod: 0, + msfix: 2, + }, + { + vbr_q: 7, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 4.2, + st_s: 25, + masking_adj: 0.5, + masking_adj_short: 2, + ath_lower: -14.5, + ath_curve: 8, + ath_sensitivity: -22, + interch: 0.0006, + safejoint: 0, + sfb21mod: 0, + msfix: 2, + }, + { + vbr_q: 8, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 4.2, + st_s: 25, + masking_adj: 1, + masking_adj_short: 2.4, + ath_lower: -22, + ath_curve: 10, + ath_sensitivity: -23, + interch: 0.0007, + safejoint: 0, + sfb21mod: 0, + msfix: 2, + }, + { + vbr_q: 9, + quant_comp: 9, + quant_comp_s: 9, + expY: 1, + st_lrm: 4.2, + st_s: 25, + masking_adj: 1.5, + masking_adj_short: 2.95, + ath_lower: -30, + ath_curve: 11, + ath_sensitivity: -25, + interch: 0.0008, + safejoint: 0, + sfb21mod: 0, + msfix: 2, + }, + ] as const satisfies PresetMap; + + private setVBRQuality(gfp: LameGlobalFlags, VBR_q: Quality): void { + if (VBR_q < 0) { + VBR_q = 0; + } + + if (VBR_q > 9) { + VBR_q = 9; + } + + gfp.VBR_q = VBR_q; + } + + public apply(gfp: LameGlobalFlags, a: Quality) { + const presetMap = + gfp.VBR === VbrMode.vbr_rh ? this.oldSwitchMap : this.switchMap; + + const set: VBRPreset = presetMap[a]; + + this.setVBRQuality(gfp, set.vbr_q); + + if (equals(gfp.quant_comp, -1)) { + gfp.quant_comp = set.quant_comp; + } + + if (equals(gfp.quant_comp_short, -1)) { + gfp.quant_comp_short = set.quant_comp_s; + } + + if (set.expY !== 0) { + gfp.experimentalY = true; + } + + if (equals(gfp.internal_flags.nsPsy.attackthre, -1)) { + gfp.internal_flags.nsPsy.attackthre = set.st_lrm; + } + + if (equals(gfp.internal_flags.nsPsy.attackthre_s, -1)) { + gfp.internal_flags.nsPsy.attackthre_s = set.st_s; + } + + if (equals(gfp.maskingadjust, 0)) { + gfp.maskingadjust = set.masking_adj; + } + + if (equals(gfp.maskingadjust_short, 0)) { + gfp.maskingadjust_short = set.masking_adj_short; + } + + if (equals(-gfp.ATHlower * 10.0, 0)) { + gfp.ATHlower = -set.ath_lower / 10.0; + } + + if (equals(gfp.ATHcurve, -1)) { + gfp.ATHcurve = set.ath_curve; + } + + if (equals(gfp.athaa_sensitivity, -1)) { + gfp.athaa_sensitivity = set.ath_sensitivity; + } + + if (set.interch > 0 && equals(gfp.interChRatio, -1)) { + gfp.interChRatio = set.interch; + } + + if (set.safejoint > 0) { + gfp.exp_nspsytune |= set.safejoint; + } + + if (set.sfb21mod > 0) { + gfp.exp_nspsytune |= set.sfb21mod << 20; + } + + if (equals(gfp.msfix, -1)) { + gfp.msfix = set.msfix; + } + + gfp.VBR_q = a; + } +} diff --git a/packages/mp3-encoder/src/lame/VBRTag.ts b/packages/mp3-encoder/src/lame/VBRTag.ts new file mode 100644 index 0000000000..42b42aaf01 --- /dev/null +++ b/packages/mp3-encoder/src/lame/VBRTag.ts @@ -0,0 +1,50 @@ +export class VBRTag { + private readonly crc16Lookup = [ + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, + 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01, 0x0cc0, + 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, + 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0, 0x1980, 0xd941, + 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, + 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, + 0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, + 0x1040, 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, + 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, + 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981, + 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, + 0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, + 0xe7c1, 0xe681, 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, + 0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, + 0x6240, 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01, + 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, + 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0, 0x7f80, + 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, + 0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, + 0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, + 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, + 0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, 0x8801, + 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, + 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, + 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, + 0x4100, 0x81c1, 0x8081, 0x4040, + ] as const; + + private crcUpdateLookup(value: number, crc: number) { + const tmp = crc ^ value; + crc = (crc >> 8) ^ this.crc16Lookup[tmp & 0xff]; + return crc; + } + + updateMusicCRC( + crc: Int32Array, + buffer: Uint8Array, + bufferPos: number, + size: number + ) { + for (let i = 0; i < size; ++i) { + crc[0] = this.crcUpdateLookup(buffer[bufferPos + i], crc[0]); + } + } +} diff --git a/packages/mp3-encoder/src/lame/VbrMode.ts b/packages/mp3-encoder/src/lame/VbrMode.ts new file mode 100644 index 0000000000..8b85ae3a62 --- /dev/null +++ b/packages/mp3-encoder/src/lame/VbrMode.ts @@ -0,0 +1,8 @@ +export const enum VbrMode { + vbr_off, + vbr_mt, + vbr_rh, + vbr_abr, + vbr_mtrh, + vbr_default = VbrMode.vbr_mtrh, +} diff --git a/packages/mp3-encoder/src/lame/WavHeader.ts b/packages/mp3-encoder/src/lame/WavHeader.ts new file mode 100644 index 0000000000..2dae456ad0 --- /dev/null +++ b/packages/mp3-encoder/src/lame/WavHeader.ts @@ -0,0 +1,65 @@ +export class WavHeader { + private constructor( + public readonly dataOffset: number, + public readonly dataLen: number, + public readonly channels: number, + public readonly sampleRate: number + ) {} + + private static fourccToInt(fourcc: string) { + return ( + (fourcc.charCodeAt(0) << 24) | + (fourcc.charCodeAt(1) << 16) | + (fourcc.charCodeAt(2) << 8) | + fourcc.charCodeAt(3) + ); + } + + private static readonly RIFF = WavHeader.fourccToInt('RIFF'); + + private static readonly WAVE = WavHeader.fourccToInt('WAVE'); + + private static readonly fmt_ = WavHeader.fourccToInt('fmt '); + + private static readonly data = WavHeader.fourccToInt('data'); + + static readHeader(dataView: DataView) { + let header = dataView.getUint32(0, false); + if (WavHeader.RIFF !== header) { + throw new Error('Invalid WAV file'); + } + + dataView.getUint32(4, true); + if (WavHeader.WAVE !== dataView.getUint32(8, false)) { + throw new Error('Invalid WAV file'); + } + if (WavHeader.fmt_ !== dataView.getUint32(12, false)) { + throw new Error('Invalid WAV file'); + } + const fmtLen = dataView.getUint32(16, true); + let pos = 16 + 4; + let channels = 0; + let sampleRate = 0; + switch (fmtLen) { + case 16: + case 18: + channels = dataView.getUint16(pos + 2, true); + sampleRate = dataView.getUint32(pos + 4, true); + break; + default: + throw new Error('extended fmt chunk not implemented'); + } + pos += fmtLen; + let len = 0; + while (WavHeader.data !== header) { + header = dataView.getUint32(pos, false); + len = dataView.getUint32(pos + 4, true); + if (WavHeader.data === header) { + break; + } + pos += len + 8; + } + + return new WavHeader(pos + 8, len, channels, sampleRate); + } +} diff --git a/packages/mp3-encoder/src/lame/arrays.ts b/packages/mp3-encoder/src/lame/arrays.ts new file mode 100644 index 0000000000..b59393c45a --- /dev/null +++ b/packages/mp3-encoder/src/lame/arrays.ts @@ -0,0 +1,69 @@ +type TypedArray = + | Int8Array + | Uint8Array + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array; + +const hasSubarray = (a: unknown): a is Pick<TypedArray, 'subarray'> => + typeof a === 'object' && + a !== null && + 'subarray' in a && + typeof a.subarray === 'function'; + +const hasSet = (a: unknown): a is Pick<TypedArray, 'set'> => + typeof a === 'object' && + a !== null && + 'set' in a && + typeof a.set === 'function'; + +export function copyArray<T>( + src: ArrayLike<T>, + srcPos: number, + dest: { [index: number]: T }, + destPos: number, + length: number +) { + if (hasSubarray(src) && hasSet(dest)) { + dest.set(src.subarray(srcPos, srcPos + length), destPos); + return; + } + + const srcEnd = srcPos + length; + while (srcPos < srcEnd) dest[destPos++] = src[srcPos++]; +} + +export function sortArray(a: TypedArray, fromIndex: number, toIndex: number) { + const sorted = Array.from(a).slice(fromIndex, toIndex).sort(); + for (let i = fromIndex; i < toIndex; i++) { + a[i] = sorted[i - fromIndex]; + } +} + +export function fillArray(a: TypedArray, val: number): void; +export function fillArray( + a: TypedArray, + fromIndex: number, + toIndex: number, + val: number +): void; +export function fillArray( + ...args: + | [a: TypedArray, val: number] + | [a: TypedArray, fromIndex: number, toIndex: number, val: number] +) { + if (args.length === 2) { + const [a, val] = args; + for (let i = 0; i < a.length; i++) { + a[i] = val; + } + } else { + const [a, fromIndex, toIndex, val] = args; + for (let i = fromIndex; i < toIndex; i++) { + a[i] = val; + } + } +} diff --git a/packages/mp3-encoder/src/lame/assert.ts b/packages/mp3-encoder/src/lame/assert.ts new file mode 100644 index 0000000000..23722872d4 --- /dev/null +++ b/packages/mp3-encoder/src/lame/assert.ts @@ -0,0 +1,8 @@ +export function assert( + condition: boolean, + message?: string +): asserts condition { + if (!condition) { + throw new Error(message); + } +} diff --git a/packages/mp3-encoder/src/lame/bitrates.ts b/packages/mp3-encoder/src/lame/bitrates.ts new file mode 100644 index 0000000000..5bb18b0add --- /dev/null +++ b/packages/mp3-encoder/src/lame/bitrates.ts @@ -0,0 +1,88 @@ +export type Bitrate = + | 8 + | 16 + | 24 + | 32 + | 40 + | 48 + | 56 + | 64 + | 80 + | 96 + | 112 + | 128 + | 144 + | 160 + | 192 + | 224 + | 256 + | 320; + +const bitratesMap = { + mpeg2: [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1], + mpeg1: [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1], + mpeg2_5: [0, 8, 16, 24, 32, 40, 48, 56, 64, -1, -1, -1, -1, -1, -1, -1], +} as const satisfies Record<string, readonly (Bitrate | 0 | -1)[]>; + +const LOW_SAMPLE_RATE_THRESHOLD = 16000; + +const getBitrates = ( + version: 0 | 1, + samplerate = LOW_SAMPLE_RATE_THRESHOLD +) => { + if (samplerate < 16000) { + return bitratesMap.mpeg2_5; + } + + if (version === 0) { + return bitratesMap.mpeg2; + } + + return bitratesMap.mpeg1; +}; + +export function findNearestBitrate( + bRate: number, + version: 0 | 1, + samplerate: number +): Bitrate { + const bitrates = getBitrates(version, samplerate); + + let matchingBitrate: Bitrate = bitrates[1]; + + for (let i = 2; i <= 14; i++) { + const bitrate = bitrates[i]; + if (bitrate !== 0 && bitrate !== -1) { + if (Math.abs(bitrate - bRate) < Math.abs(matchingBitrate - bRate)) { + matchingBitrate = bitrate; + } + } + } + return matchingBitrate; +} + +export type BitrateIndex = number & { __brand: 'BitrateIndex' }; + +export function findBitrateIndex( + bRate: number, + version: 0 | 1, + samplerate: number +): BitrateIndex { + const bitrates = getBitrates(version, samplerate); + + for (let i = 1; i <= 14; i++) { + const bitrate = bitrates[i]; + if (bitrate > 0) { + if (bitrate === bRate) { + return i as BitrateIndex; + } + } + } + + throw new Error(`Invalid bitrate ${bRate}`); +} + +export function getBitrate(version: 0 | 1, index: number): Bitrate | 0 | -1 { + const bitrates = getBitrates(version); + return bitrates[index]; +} diff --git a/packages/mp3-encoder/src/lame/constants.ts b/packages/mp3-encoder/src/lame/constants.ts new file mode 100644 index 0000000000..0577366273 --- /dev/null +++ b/packages/mp3-encoder/src/lame/constants.ts @@ -0,0 +1,65 @@ +export const MAX_FLOAT32_VALUE = 3.4028235e38; + +export const LOG10 = 2.302585092994046; + +export const SBMAX_l = 22; + +export const SBMAX_s = 13; + +export const PSFB21 = 6; + +export const PSFB12 = 6; + +export const CBANDS = 64; + +export const NORM_TYPE = 0; + +export const START_TYPE = 1; + +export const SHORT_TYPE = 2; + +export const STOP_TYPE = 3; + +export const LAME_MAXALBUMART = 128 * 1024; + +export const LAME_MAXMP3BUFFER = 16384 + LAME_MAXALBUMART; + +export const BLKSIZE = 1024; + +export const HBLKSIZE = BLKSIZE / 2 + 1; + +export const BLKSIZE_s = 256; + +export const HBLKSIZE_s = BLKSIZE_s / 2 + 1; + +export const SFBMAX = SBMAX_s * 3; + +export const MPG_MD_LR_LR = 0; + +export const MPG_MD_MS_LR = 2; + +export const SBPSY_l = 21; + +export const SBPSY_s = 12; + +export const SBLIMIT = 32; + +export const ENCDELAY = 576; + +export const POSTDELAY = 1152; + +export const MDCTDELAY = 48; + +export const FFTOFFSET = 224 + MDCTDELAY; + +export const LAME_ID = 0xfff88e3b; + +export const BPC = 320; + +export const MAX_BITS_PER_GRANULE = 7680; + +export const MAX_BITS_PER_CHANNEL = 4095; + +export const MAX_HEADER_BUF = 256; + +export const MFSIZE = 3 * 1152 + ENCDELAY - MDCTDELAY; diff --git a/packages/mp3-encoder/src/lame/getLameShortVersion.ts b/packages/mp3-encoder/src/lame/getLameShortVersion.ts new file mode 100644 index 0000000000..3153f9f8dc --- /dev/null +++ b/packages/mp3-encoder/src/lame/getLameShortVersion.ts @@ -0,0 +1,9 @@ +const LAME_MAJOR_VERSION = 3; + +const LAME_MINOR_VERSION = 98; + +const LAME_PATCH_VERSION = 4; + +export function getLameShortVersion() { + return `${LAME_MAJOR_VERSION}.${LAME_MINOR_VERSION}.${LAME_PATCH_VERSION}` as const; +} diff --git a/packages/mp3-encoder/src/lame/index.ts b/packages/mp3-encoder/src/lame/index.ts new file mode 100644 index 0000000000..68fa6b60e5 --- /dev/null +++ b/packages/mp3-encoder/src/lame/index.ts @@ -0,0 +1 @@ +export { Mp3Encoder } from './Mp3Encoder'; diff --git a/packages/mp3-encoder/src/lame/math.ts b/packages/mp3-encoder/src/lame/math.ts new file mode 100644 index 0000000000..ad3ea40dd9 --- /dev/null +++ b/packages/mp3-encoder/src/lame/math.ts @@ -0,0 +1,31 @@ +export function blackmanWindow(x: number, fcn: number, l: number) { + const wcn = Math.PI * fcn; + + x /= l; + if (x < 0) x = 0; + if (x > 1) x = 1; + const x2 = x - 0.5; + + const bkwn = + 0.42 - 0.5 * Math.cos(2 * x * Math.PI) + 0.08 * Math.cos(4 * x * Math.PI); + if (Math.abs(x2) < 1e-9) return wcn / Math.PI; + return (bkwn * Math.sin(l * wcn * x2)) / (Math.PI * l * x2); +} + +export function gcd(i: number, j: number): number { + return j !== 0 ? gcd(j, i % j) : i; +} + +export function isCloseToEachOther(a: number, b: number) { + return Math.abs(a) > Math.abs(b) + ? Math.abs(a - b) <= Math.abs(a) * 1e-6 + : Math.abs(a - b) <= Math.abs(b) * 1e-6; +} + +export function fsqr(d: number) { + return d * d; +} + +export function equals(a: number, b: number) { + return !(Math.abs(a - b) > 0); +} diff --git a/packages/mp3-encoder/src/lame/sampleRates.ts b/packages/mp3-encoder/src/lame/sampleRates.ts new file mode 100644 index 0000000000..980840186d --- /dev/null +++ b/packages/mp3-encoder/src/lame/sampleRates.ts @@ -0,0 +1,23 @@ +export type SampleRate = + | 44100 + | 48000 + | 32000 + | 24000 + | 22050 + | 16000 + | 12000 + | 11025 + | 8000; + +export function findNearestSampleRate(freq: number): SampleRate { + if (freq <= 8000) return 8000; + if (freq <= 11025) return 11025; + if (freq <= 12000) return 12000; + if (freq <= 16000) return 16000; + if (freq <= 22050) return 22050; + if (freq <= 24000) return 24000; + if (freq <= 32000) return 32000; + if (freq <= 44100) return 44100; + + return 48000; +} diff --git a/packages/mp3-encoder/src/lamejs.d.ts b/packages/mp3-encoder/src/lamejs.d.ts deleted file mode 100644 index 68af610574..0000000000 --- a/packages/mp3-encoder/src/lamejs.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -declare module 'lamejs' { - export class Mp3Encoder { - constructor(numChannels: number, sampleRate: number, bitRate: number); - - encodeBuffer(buffer: Int16Array): Iterable<number>; - - flush(): Iterable<number>; - } -} diff --git a/packages/mp3-encoder/testdata/Left44100.wav b/packages/mp3-encoder/testdata/Left44100.wav new file mode 100644 index 0000000000..e7dfc6f26b Binary files /dev/null and b/packages/mp3-encoder/testdata/Left44100.wav differ diff --git a/packages/mp3-encoder/testdata/Right44100.wav b/packages/mp3-encoder/testdata/Right44100.wav new file mode 100644 index 0000000000..1366b74859 Binary files /dev/null and b/packages/mp3-encoder/testdata/Right44100.wav differ diff --git a/packages/mp3-encoder/tsconfig.build.json b/packages/mp3-encoder/tsconfig.build.json new file mode 100644 index 0000000000..8c0f74bb02 --- /dev/null +++ b/packages/mp3-encoder/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["**/*.spec.ts"] +} diff --git a/packages/mp3-encoder/tsconfig.json b/packages/mp3-encoder/tsconfig.json index 553f3b420e..ef367fd30c 100644 --- a/packages/mp3-encoder/tsconfig.json +++ b/packages/mp3-encoder/tsconfig.json @@ -1,12 +1,13 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "es5", - "lib": ["es2015", "webworker"], - "strict": false, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, "rootDir": "./src", - "skipLibCheck": true, - "importsNotUsedAsValues": "error" - } + "module": "ESNext", + "lib": ["ES2015", "WebWorker"], + "outDir": "./dist", + "esModuleInterop": true, + "moduleResolution": "node", + "skipLibCheck": true + }, + "include": ["src"] } diff --git a/packages/onboarding-ui/.storybook/main.ts b/packages/onboarding-ui/.storybook/main.ts index 554b7c71c2..6ad476e8f2 100644 --- a/packages/onboarding-ui/.storybook/main.ts +++ b/packages/onboarding-ui/.storybook/main.ts @@ -1,7 +1,14 @@ -module.exports = { - addons: ['@storybook/addon-essentials', 'storybook-dark-mode/register'], - stories: ['../src/**/*.stories.tsx', '../src/**/stories.tsx'], +import type { StorybookConfig } from '@storybook/react/types'; + +const config: StorybookConfig = { + core: { + builder: 'webpack5', + }, features: { postcss: false, }, + addons: ['@storybook/addon-essentials', 'storybook-dark-mode'], + stories: ['../src/**/*.stories.tsx', '../src/**/stories.tsx'], }; + +export default config; diff --git a/packages/onboarding-ui/.storybook/manager.ts b/packages/onboarding-ui/.storybook/manager.ts deleted file mode 100644 index 194ef4014c..0000000000 --- a/packages/onboarding-ui/.storybook/manager.ts +++ /dev/null @@ -1,17 +0,0 @@ -import colorTokens from '@rocket.chat/fuselage-tokens/colors.json'; -import { addons } from '@storybook/addons'; -import { create } from '@storybook/theming'; - -import manifest from '../package.json'; -import logo from './logo.svg'; - -addons.setConfig({ - theme: create({ - base: 'light', - brandTitle: manifest.name, - brandImage: logo, - brandUrl: manifest.homepage, - colorPrimary: colorTokens.n500, - colorSecondary: colorTokens.p500, - }), -}); diff --git a/packages/onboarding-ui/.storybook/preview.tsx b/packages/onboarding-ui/.storybook/preview.tsx index ecb71a22d5..58372b6c13 100644 --- a/packages/onboarding-ui/.storybook/preview.tsx +++ b/packages/onboarding-ui/.storybook/preview.tsx @@ -1,16 +1,21 @@ import { DarkModeProvider } from '@rocket.chat/layout'; import { DocsPage, DocsContainer } from '@storybook/addon-docs'; -import type { DecoratorFunction } from '@storybook/addons'; -import { addParameters } from '@storybook/react'; -import '@rocket.chat/icons/dist/rocketchat.css'; -import '@rocket.chat/fuselage-polyfills'; +import type { Parameters } from '@storybook/addons'; +import type { DecoratorFn } from '@storybook/react'; +import { themes } from '@storybook/theming'; import i18next from 'i18next'; -import type { ElementType, ReactElement } from 'react'; import { Suspense } from 'react'; import { I18nextProvider, initReactI18next } from 'react-i18next'; import { useDarkMode } from 'storybook-dark-mode'; -addParameters({ +import manifest from '../package.json'; +import logo from './logo.svg'; + +import '@rocket.chat/fuselage/dist/fuselage.css'; +import '@rocket.chat/icons/dist/rocketchat.css'; +import '@rocket.chat/fuselage-polyfills'; + +export const parameters: Parameters = { backgrounds: { grid: { cellSize: 4, @@ -25,7 +30,21 @@ addParameters({ options: { storySort: ([, a], [, b]) => a.kind.localeCompare(b.kind), }, -}); + darkMode: { + dark: { + ...themes.dark, + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + }, + light: { + ...themes.normal, + brandTitle: manifest.name, + brandImage: logo, + brandUrl: manifest.homepage, + }, + }, +}; const getI18n = () => { const i18n = i18next.createInstance().use(initReactI18next); @@ -45,9 +64,10 @@ const getI18n = () => { return i18n; }; -export const decorators: DecoratorFunction<ReactElement>[] = [ - (Story: ElementType): ReactElement => { +export const decorators: DecoratorFn[] = [ + (Story) => { const dark = useDarkMode(); + return ( <Suspense fallback={null}> <I18nextProvider i18n={getI18n()}> diff --git a/packages/onboarding-ui/jest.config.js b/packages/onboarding-ui/jest.config.js index 842bc24304..777ceb6333 100644 --- a/packages/onboarding-ui/jest.config.js +++ b/packages/onboarding-ui/jest.config.js @@ -4,12 +4,4 @@ module.exports = { testMatch: ['<rootDir>/src/**/*.spec.ts?(x)'], testEnvironment: 'jsdom', setupFilesAfterEnv: ['<rootDir>/.jest/setup.ts'], - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - }, - }, - }, }; diff --git a/packages/onboarding-ui/package.json b/packages/onboarding-ui/package.json index 8f0148b629..7a4b0a6ef6 100644 --- a/packages/onboarding-ui/package.json +++ b/packages/onboarding-ui/package.json @@ -35,7 +35,7 @@ "lint-staged": "lint-staged", "test": "jest --runInBand", "docs": "typedoc", - "storybook": "start-storybook -p 6006", + "storybook": "start-storybook -p 6006 --no-version-updates", "build-storybook": "build-storybook", "bump-next": "bump-next" }, @@ -53,35 +53,35 @@ "@rocket.chat/logo": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", "@rocket.chat/styled": "workspace:~", - "@storybook/addon-essentials": "~6.5.15", - "@storybook/addons": "~6.5.15", - "@storybook/react": "~6.5.15", - "@storybook/source-loader": "~6.5.15", - "@storybook/theming": "~6.5.15", - "@types/jest": "~27.4.1", - "@types/react": "~17.0.53", - "@types/react-dom": "^17.0.18", + "@storybook/addon-essentials": "~6.5.16", + "@storybook/addons": "~6.5.16", + "@storybook/builder-webpack5": "~6.5.16", + "@storybook/manager-webpack5": "~6.5.16", + "@storybook/react": "~6.5.16", + "@storybook/theming": "~6.5.16", + "@types/jest": "~29.5.0", + "@types/react": "~17.0.57", + "@types/react-dom": "^17.0.19", "bump": "workspace:~", "countries-list": "^2.6.1", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "react": "^17.0.2", "react-dom": "^17.0.2", "react-i18next": "~11.15.7", "rimraf": "^3.0.2", "storybook-dark-mode": "^1.1.2", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "dependencies": { "i18next": "~21.6.16", - "react-hook-form": "~7.27.1", - "tslib": "~2.3.1" + "react-hook-form": "~7.27.1" }, "peerDependencies": { "@rocket.chat/fuselage": "*", diff --git a/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.tsx b/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.tsx index d33e593814..01becc1d95 100644 --- a/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.tsx +++ b/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.tsx @@ -157,8 +157,8 @@ const AdminInfoForm = ({ )} </Field> {keepPosted && ( - <Box mbe='x8' display='block' color='info' fontScale='c1'> - <CheckBox id='keepPosted' mie='x8' {...register('keepPosted')} /> + <Box mbe={8} display='block' color='info' fontScale='c1'> + <CheckBox id='keepPosted' mie={8} {...register('keepPosted')} /> <label htmlFor='keepPosted'> {t('form.adminInfoForm.fields.keepPosted.label')} </label> diff --git a/packages/onboarding-ui/src/forms/CreateCloudWorkspaceForm/CreateCloudWorkspaceForm.tsx b/packages/onboarding-ui/src/forms/CreateCloudWorkspaceForm/CreateCloudWorkspaceForm.tsx index 0ee3d16f67..0e14d8e1e9 100644 --- a/packages/onboarding-ui/src/forms/CreateCloudWorkspaceForm/CreateCloudWorkspaceForm.tsx +++ b/packages/onboarding-ui/src/forms/CreateCloudWorkspaceForm/CreateCloudWorkspaceForm.tsx @@ -74,7 +74,7 @@ const CreateCloudWorkspaceForm = ({ <Form.Title>{t('form.createCloudWorkspace.title')}</Form.Title> </Form.Header> - <FieldGroup mbs='x16'> + <FieldGroup mbs={16}> <Field> <Field.Label> {t('form.createCloudWorkspace.fields.orgEmail.label')} @@ -96,7 +96,7 @@ const CreateCloudWorkspaceForm = ({ <Field> <Field.Label> - <Box display='inline' mie='x8'> + <Box display='inline' mie={8}> {t('form.createCloudWorkspace.fields.workspaceName.label')} </Box> </Field.Label> @@ -115,7 +115,7 @@ const CreateCloudWorkspaceForm = ({ <Field> <Field.Label> - <Box display='inline' mie='x8'> + <Box display='inline' mie={8}> {t('form.createCloudWorkspace.fields.workspaceUrl.label')} </Box> </Field.Label> @@ -134,11 +134,11 @@ const CreateCloudWorkspaceForm = ({ )} </Field> - <Grid mb='x16'> + <Grid mb={16}> <Grid.Item> <Field> <Field.Label> - <Box display='inline' mie='x8'> + <Box display='inline' mie={8}> {t('form.createCloudWorkspace.fields.serverRegion.label')} </Box> <Tooltip @@ -170,7 +170,7 @@ const CreateCloudWorkspaceForm = ({ <Grid.Item> <Field> <Field.Label> - <Box display='inline' mie='x8'> + <Box display='inline' mie={8}> {t('form.createCloudWorkspace.fields.language.label')} </Box> <Tooltip @@ -198,11 +198,11 @@ const CreateCloudWorkspaceForm = ({ </Grid.Item> </Grid> - <Divider mb='x0' /> + <Divider mb={0} /> <Field> <Field.Row justifyContent='flex-start'> - <CheckBox {...register('agreement', { required: true })} mie='x8' /> + <CheckBox {...register('agreement', { required: true })} mie={8} /> <Box is='label' htmlFor='agreement' withRichContent fontScale='c1'> <Trans i18nKey='component.form.termsAndConditions'> I agree with @@ -231,7 +231,7 @@ const CreateCloudWorkspaceForm = ({ <Field> <Field.Row justifyContent='flex-start'> - <CheckBox {...register('updates')} mie='x8' /> + <CheckBox {...register('updates')} mie={8} /> <Box fontScale='c1'> {t('form.createCloudWorkspace.fields.keepMeInformed')} </Box> diff --git a/packages/onboarding-ui/src/forms/CreateCloudWorkspaceForm/WorkspaceUrlInput.tsx b/packages/onboarding-ui/src/forms/CreateCloudWorkspaceForm/WorkspaceUrlInput.tsx index ccbcb360b7..79b3f1beb1 100644 --- a/packages/onboarding-ui/src/forms/CreateCloudWorkspaceForm/WorkspaceUrlInput.tsx +++ b/packages/onboarding-ui/src/forms/CreateCloudWorkspaceForm/WorkspaceUrlInput.tsx @@ -1,6 +1,6 @@ import { Box, InputBox } from '@rocket.chat/fuselage'; import type { ComponentProps, ReactNode, Ref } from 'react'; -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; type WorkspaceUrlInputProps = Omit<ComponentProps<typeof InputBox>, 'type'> & { addon?: ReactNode; @@ -23,10 +23,10 @@ const WorkspaceUrlInput = forwardRef(function TextInput( <Box borderInlineStart='2px solid' mb='neg-x8' - pb='x8' + pb={8} borderColor='neutral-500' color='info' - pis='x12' + pis={12} > {domain} </Box> diff --git a/packages/onboarding-ui/src/forms/CreateFirstMemberForm/CreateFirstMemberForm.tsx b/packages/onboarding-ui/src/forms/CreateFirstMemberForm/CreateFirstMemberForm.tsx index e38970372b..3bf542c439 100644 --- a/packages/onboarding-ui/src/forms/CreateFirstMemberForm/CreateFirstMemberForm.tsx +++ b/packages/onboarding-ui/src/forms/CreateFirstMemberForm/CreateFirstMemberForm.tsx @@ -56,10 +56,10 @@ const CreateFirstMemberForm = ({ {t('form.createFirstMemberForm.subtitle', { organizationName })} </Form.Subtitle> - <FieldGroup mbs='x16'> + <FieldGroup mbs={16}> <Field> <Field.Label> - <Box display='inline' mie='x8'> + <Box display='inline' mie={8}> {t('form.createFirstMemberForm.fields.username.label')} </Box> </Field.Label> diff --git a/packages/onboarding-ui/src/forms/LoginForm/LoginForm.tsx b/packages/onboarding-ui/src/forms/LoginForm/LoginForm.tsx index 4f4e2fbb02..e02dbb5d9d 100644 --- a/packages/onboarding-ui/src/forms/LoginForm/LoginForm.tsx +++ b/packages/onboarding-ui/src/forms/LoginForm/LoginForm.tsx @@ -116,7 +116,7 @@ const LoginForm = ({ </LoginActionsWrapper> {!isPasswordLess && ( - <Box mbs='x24' fontScale='p2' textAlign='left'> + <Box mbs={24} fontScale='p2' textAlign='left'> <Trans i18nKey='form.loginForm.resetPassword'> Forgot your password? <ActionLink fontScale='p2' onClick={onResetPassword}> diff --git a/packages/onboarding-ui/src/forms/NewAccountForm/NewAccountForm.tsx b/packages/onboarding-ui/src/forms/NewAccountForm/NewAccountForm.tsx index 85f08bb377..602d5f423b 100644 --- a/packages/onboarding-ui/src/forms/NewAccountForm/NewAccountForm.tsx +++ b/packages/onboarding-ui/src/forms/NewAccountForm/NewAccountForm.tsx @@ -118,7 +118,7 @@ const NewAccountForm = ({ <Field.Row justifyContent='flex-start'> <CheckBox {...register('agreement', { required: true })} - mie='x8' + mie={8} /> <Box is='label' diff --git a/packages/onboarding-ui/src/forms/RegisterServerForm/RegisterServerForm.tsx b/packages/onboarding-ui/src/forms/RegisterServerForm/RegisterServerForm.tsx index 26f0ecd647..eb3d891e65 100644 --- a/packages/onboarding-ui/src/forms/RegisterServerForm/RegisterServerForm.tsx +++ b/packages/onboarding-ui/src/forms/RegisterServerForm/RegisterServerForm.tsx @@ -76,7 +76,7 @@ const RegisterServerForm = ({ <Form.Steps currentStep={currentStep} stepCount={stepCount} /> <Form.Title>{t('form.registeredServerForm.title')}</Form.Title> </Form.Header> - <Box mbe='x24' mbs='x16'> + <Box mbe={24} mbs={16}> <List> <List.Item fontScale='p2' icon='check'> {t('form.registeredServerForm.included.push')} @@ -102,7 +102,7 @@ const RegisterServerForm = ({ title={t( 'form.registeredServerForm.fields.accountEmail.tooltipLabel' )} - mis='x4' + mis={4} size='x16' name='info' /> @@ -123,16 +123,16 @@ const RegisterServerForm = ({ <Field.Error>{t('component.form.requiredField')}</Field.Error> )} </Field> - <Box mbs='x24'> + <Box mbs={24}> <Box - mbe='x8' + mbe={8} display='flex' flexDirection='row' alignItems='flex-start' fontScale='c1' lineHeight={20} > - <CheckBox mie='x8' {...register('updates')} />{' '} + <CheckBox mie={8} {...register('updates')} />{' '} <Box is='label' htmlFor='updates'> {t('form.registeredServerForm.keepInformed')} </Box> @@ -146,7 +146,7 @@ const RegisterServerForm = ({ lineHeight={20} > <CheckBox - mie='x8' + mie={8} {...register('agreement', { required: true })} />{' '} <Box is='label' htmlFor='agreement' withRichContent> @@ -171,7 +171,7 @@ const RegisterServerForm = ({ </Box> </Box> - <Box mbs='x32' fontScale='c1' htmlFor='agreement' withRichContent> + <Box mbs={32} fontScale='c1' htmlFor='agreement' withRichContent> {t('form.registeredServerForm.agreeToReceiveUpdates')} </Box> </Box> @@ -179,7 +179,7 @@ const RegisterServerForm = ({ )} {offline && ( <Form.Container> - <Box mbs='x32' fontScale='c1' withRichContent> + <Box mbs={32} fontScale='c1' withRichContent> {t('form.registeredServerForm.notConnectedToInternet')} </Box> </Form.Container> diff --git a/packages/onboarding-ui/src/forms/RequestTrialForm/RequestTrialForm.tsx b/packages/onboarding-ui/src/forms/RequestTrialForm/RequestTrialForm.tsx index 04824367c9..29b1950c25 100644 --- a/packages/onboarding-ui/src/forms/RequestTrialForm/RequestTrialForm.tsx +++ b/packages/onboarding-ui/src/forms/RequestTrialForm/RequestTrialForm.tsx @@ -140,16 +140,16 @@ const RequestTrialForm = ({ </Field.Row> </Field> <Field> - <Box mbs='x24'> + <Box mbs={24}> <Box - mbe='x8' + mbe={8} display='flex' flexDirection='row' alignItems='flex-start' fontScale='c1' lineHeight={20} > - <CheckBox mie='x8' {...register('updates')} />{' '} + <CheckBox mie={8} {...register('updates')} />{' '} <Box is='label' htmlFor='updates'> {t('form.registeredServerForm.keepInformed')} </Box> @@ -163,7 +163,7 @@ const RequestTrialForm = ({ lineHeight={20} > <CheckBox - mie='x8' + mie={8} {...register('agreement', { required: true })} />{' '} <Box is='label' htmlFor='agreement' withRichContent> diff --git a/packages/onboarding-ui/src/forms/StandaloneServerForm/StandaloneServerForm.tsx b/packages/onboarding-ui/src/forms/StandaloneServerForm/StandaloneServerForm.tsx index 499d49b060..95c97b70a1 100644 --- a/packages/onboarding-ui/src/forms/StandaloneServerForm/StandaloneServerForm.tsx +++ b/packages/onboarding-ui/src/forms/StandaloneServerForm/StandaloneServerForm.tsx @@ -47,7 +47,7 @@ const StandaloneServerForm = ({ <Form.Title>{t('form.standaloneServerForm.title')}</Form.Title> </Form.Header> - <Box mbe='x24' mbs='x16'> + <Box mbe={24} mbs={16}> <List> <List.Item fontScale='c2' icon='warning' iconColor='warning'> {t('form.standaloneServerForm.servicesUnavailable')} diff --git a/packages/onboarding-ui/src/pages/AwaitingConfirmationPage/AwaitingConfirmationPage.tsx b/packages/onboarding-ui/src/pages/AwaitingConfirmationPage/AwaitingConfirmationPage.tsx index 94d5d4ecf8..dbcdb8d8bf 100644 --- a/packages/onboarding-ui/src/pages/AwaitingConfirmationPage/AwaitingConfirmationPage.tsx +++ b/packages/onboarding-ui/src/pages/AwaitingConfirmationPage/AwaitingConfirmationPage.tsx @@ -34,7 +34,7 @@ const AwaitingConfirmationPage = ({ <Box maxWidth={498} - padding='x18' + padding={18} width='full' fontSize='x22' lineHeight='x32' diff --git a/packages/onboarding-ui/src/pages/CreateCloudWorkspacePage/CreateCloudWorkspacePage.tsx b/packages/onboarding-ui/src/pages/CreateCloudWorkspacePage/CreateCloudWorkspacePage.tsx index ea76800b3f..6f4e3982ca 100644 --- a/packages/onboarding-ui/src/pages/CreateCloudWorkspacePage/CreateCloudWorkspacePage.tsx +++ b/packages/onboarding-ui/src/pages/CreateCloudWorkspacePage/CreateCloudWorkspacePage.tsx @@ -26,7 +26,7 @@ const CreateCloudWorkspacePage = ( > <CreateCloudWorkspaceForm {...props} /> - <Box mbs='x28' display='inline' textAlign='center'> + <Box mbs={28} display='inline' textAlign='center'> <Trans i18nKey='page.alreadyHaveAccount'> Already have an account? <Box diff --git a/packages/onboarding-ui/src/pages/CreateCloudWorkspacePage/Description.tsx b/packages/onboarding-ui/src/pages/CreateCloudWorkspacePage/Description.tsx index 7c45bae48b..ca0642f2f2 100644 --- a/packages/onboarding-ui/src/pages/CreateCloudWorkspacePage/Description.tsx +++ b/packages/onboarding-ui/src/pages/CreateCloudWorkspacePage/Description.tsx @@ -33,7 +33,7 @@ const Description = (): ReactElement => { const listItem = (text: string, id: number) => ( <List.Item key={id} fontScale='p1'> - <Icon name='check' size='x24' mie='x12' /> + <Icon name='check' size='x24' mie={12} /> {text} </List.Item> ); diff --git a/packages/onboarding-ui/src/pages/CreateNewPasswordPage/CreateNewPasswordPage.tsx b/packages/onboarding-ui/src/pages/CreateNewPasswordPage/CreateNewPasswordPage.tsx index b7c93763cd..6d1b032eb3 100644 --- a/packages/onboarding-ui/src/pages/CreateNewPasswordPage/CreateNewPasswordPage.tsx +++ b/packages/onboarding-ui/src/pages/CreateNewPasswordPage/CreateNewPasswordPage.tsx @@ -32,7 +32,7 @@ const ResetPasswordPage = ({ styleProps={pageLayoutStyleProps} > <CreateNewPasswordForm {...props} /> - <Box fontScale='p2' pbs='x40'> + <Box fontScale='p2' pbs={40}> <Trans i18nKey='component.wantToLogin'> Want to log in? <ActionLink fontScale='p2' onClick={onLogin}> diff --git a/packages/onboarding-ui/src/pages/OauthAuthorizationPage/OauthAuthorizationPage.tsx b/packages/onboarding-ui/src/pages/OauthAuthorizationPage/OauthAuthorizationPage.tsx index e844420dae..764c86a2ce 100644 --- a/packages/onboarding-ui/src/pages/OauthAuthorizationPage/OauthAuthorizationPage.tsx +++ b/packages/onboarding-ui/src/pages/OauthAuthorizationPage/OauthAuthorizationPage.tsx @@ -30,14 +30,14 @@ const OauthAuthorizationPage = ({ {t('page.oauthAuthorizationPage.title')} </VerticalWizardLayoutTitle> <VerticalWizardLayoutForm> - <Box fontScale='p1' p='x40' textAlign='start' color={colors.n900}> + <Box fontScale='p1' p={40} textAlign='start' color={colors.n900}> {!clientName || error.message ? ( <> - <Box fontScale='h1' mbe='x18'> + <Box fontScale='h1' mbe={18}> Error </Box> {error.message} - <Box mbs='x24'> + <Box mbs={24}> <Button onClick={error.onGoBack} primary> {t('page.oauthAuthorizationPage.buttons.goBack')} </Button> @@ -54,7 +54,7 @@ const OauthAuthorizationPage = ({ to login with your Rocket.Chat Cloud Account? </Trans> - <Box mbs='x24'> + <Box mbs={24}> <Button onClick={onClickAuthorizeOAuth} primary> {t('page.oauthAuthorizationPage.buttons.authorize')} </Button> diff --git a/packages/onboarding-ui/src/pages/ResetPasswordPage/ResetPasswordPage.tsx b/packages/onboarding-ui/src/pages/ResetPasswordPage/ResetPasswordPage.tsx index 415155501e..1eb53f6565 100644 --- a/packages/onboarding-ui/src/pages/ResetPasswordPage/ResetPasswordPage.tsx +++ b/packages/onboarding-ui/src/pages/ResetPasswordPage/ResetPasswordPage.tsx @@ -32,7 +32,7 @@ const ResetPasswordPage = ({ styleProps={pageLayoutStyleProps} > <ResetPasswordForm {...props} /> - <Box fontScale='p2' pbs='x40'> + <Box fontScale='p2' pbs={40}> <Trans i18nKey='component.wantToLogin'> Want to log in? <ActionLink fontScale='p2' onClick={onLogin}> diff --git a/packages/onboarding-ui/tsconfig-cjs.json b/packages/onboarding-ui/tsconfig-cjs.json index 94db2d7e97..0b4e6ec415 100644 --- a/packages/onboarding-ui/tsconfig-cjs.json +++ b/packages/onboarding-ui/tsconfig-cjs.json @@ -1,8 +1,10 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "module": "commonjs", + "module": "CommonJS", + "rootDir": "./src", "outDir": "./dist/cjs" }, - "exclude": ["./**/*.stories.tsx", "./**/*.spec.tsx"] + "include": ["./src"], + "exclude": ["**/*.spec.ts"] } diff --git a/packages/onboarding-ui/tsconfig-esm.json b/packages/onboarding-ui/tsconfig-esm.json index 5201be01de..d6567ecc56 100644 --- a/packages/onboarding-ui/tsconfig-esm.json +++ b/packages/onboarding-ui/tsconfig-esm.json @@ -1,4 +1,10 @@ { "extends": "./tsconfig.json", - "exclude": ["./**/*.stories.tsx", "./**/*.spec.tsx"] + "compilerOptions": { + "module": "ESNext", + "rootDir": "./src", + "outDir": "./dist/esm" + }, + "include": ["./src"], + "exclude": ["**/*.spec.ts"] } diff --git a/packages/onboarding-ui/tsconfig.json b/packages/onboarding-ui/tsconfig.json index 29fa137f6f..1808d73a6a 100644 --- a/packages/onboarding-ui/tsconfig.json +++ b/packages/onboarding-ui/tsconfig.json @@ -1,19 +1,16 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "./src", - "target": "es5", - "module": "ESNext", - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "./dist/esm", - "strict": true, + "rootDirs": ["./src", "./.storybook"], + "target": "ES5", + "module": "CommonJS", + "outDir": "./dist", "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "resolveJsonModule": true, - "jsx": "react-jsx", - "importsNotUsedAsValues": "error" - } + "jsx": "react-jsx" + }, + "include": ["./src", "./.jest/**/*", "./.storybook/**/*"], + "exclude": ["./dist"] } diff --git a/packages/peggy-loader/jest.config.js b/packages/peggy-loader/jest.config.js index aba24c1c97..21526c604f 100644 --- a/packages/peggy-loader/jest.config.js +++ b/packages/peggy-loader/jest.config.js @@ -2,12 +2,4 @@ module.exports = { preset: 'ts-jest', errorOnDeprecated: true, testMatch: ['<rootDir>/src/**/*.spec.[jt]s?(x)'], - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - }, - }, - }, }; diff --git a/packages/peggy-loader/package.json b/packages/peggy-loader/package.json index 78c920730a..58d288d8f2 100644 --- a/packages/peggy-loader/package.json +++ b/packages/peggy-loader/package.json @@ -47,20 +47,17 @@ "devDependencies": { "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@types/node": "~15.14.9", + "@types/node": "~14.18.42", "bump": "workspace:~", - "eslint": "~8.26.0", + "eslint": "~8.38.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", "peggy": "3.0.2", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "rimraf": "^3.0.2", - "ts-jest": "~27.1.5", - "typescript": "~4.9.4", - "webpack": "~5.76.0" - }, - "dependencies": { - "tslib": "^2.3.1" + "ts-jest": "~29.1.0", + "typescript": "~5.0.4", + "webpack": "~5.78.0" } } diff --git a/packages/peggy-loader/tsconfig.json b/packages/peggy-loader/tsconfig.json index 4b343b2ef3..9d2804b026 100644 --- a/packages/peggy-loader/tsconfig.json +++ b/packages/peggy-loader/tsconfig.json @@ -11,7 +11,6 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "node", - "resolveJsonModule": true, - "importsNotUsedAsValues": "error" + "resolveJsonModule": true } } diff --git a/packages/prettier-config/package.json b/packages/prettier-config/package.json index d8cf43533e..0162c88ddb 100644 --- a/packages/prettier-config/package.json +++ b/packages/prettier-config/package.json @@ -39,12 +39,12 @@ }, "devDependencies": { "bump": "workspace:~", - "eslint": "~8.26.0", - "eslint-config-prettier": "~8.5.0", + "eslint": "~8.38.0", + "eslint-config-prettier": "~8.8.0", "eslint-plugin-import": "~2.26.0", "eslint-plugin-prettier": "~4.2.1", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", - "prettier": "~2.7.1" + "lint-staged": "~13.2.1", + "prettier": "~2.8.7" } } diff --git a/packages/string-helpers/package.json b/packages/string-helpers/package.json index b01b3dd9af..32635aeff0 100644 --- a/packages/string-helpers/package.json +++ b/packages/string-helpers/package.json @@ -44,21 +44,18 @@ "devDependencies": { "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@types/jest": "~27.4.1", + "@types/jest": "~29.5.0", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "rimraf": "^3.0.2", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" - }, - "dependencies": { - "tslib": "^2.3.1" + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "eslintConfig": { "extends": "@rocket.chat/eslint-config-alt/typescript", @@ -72,14 +69,6 @@ "errorOnDeprecated": true, "testMatch": [ "<rootDir>/src/**/*.spec.[jt]s?(x)" - ], - "globals": { - "ts-jest": { - "tsconfig": { - "noUnusedLocals": false, - "noUnusedParameters": false - } - } - } + ] } } diff --git a/packages/string-helpers/tsconfig.json b/packages/string-helpers/tsconfig.json index de9332fa5a..2db09fcceb 100644 --- a/packages/string-helpers/tsconfig.json +++ b/packages/string-helpers/tsconfig.json @@ -11,8 +11,7 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "node", - "resolveJsonModule": true, - "importsNotUsedAsValues": "error" + "resolveJsonModule": true }, "typedocOptions": { "entryPoints": ["src/index.ts"], diff --git a/packages/styled/jest.config.js b/packages/styled/jest.config.js index 0dbb3c664a..0d581323df 100644 --- a/packages/styled/jest.config.js +++ b/packages/styled/jest.config.js @@ -3,12 +3,4 @@ module.exports = { errorOnDeprecated: true, testMatch: ['<rootDir>/src/**/*.spec.ts'], testEnvironment: 'jsdom', - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - }, - }, - }, }; diff --git a/packages/styled/package.json b/packages/styled/package.json index e2df01ecd3..85a7c05f9d 100644 --- a/packages/styled/package.json +++ b/packages/styled/package.json @@ -40,21 +40,20 @@ "devDependencies": { "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@types/jest": "~27.4.1", + "@types/jest": "~29.5.0", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "rimraf": "^3.0.2", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "dependencies": { - "@rocket.chat/css-in-js": "workspace:~", - "tslib": "^2.3.1" + "@rocket.chat/css-in-js": "workspace:~" } } diff --git a/packages/styled/tsconfig.json b/packages/styled/tsconfig.json index 4b343b2ef3..9d2804b026 100644 --- a/packages/styled/tsconfig.json +++ b/packages/styled/tsconfig.json @@ -11,7 +11,6 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "node", - "resolveJsonModule": true, - "importsNotUsedAsValues": "error" + "resolveJsonModule": true } } diff --git a/packages/stylis-logical-props-middleware/jest.config.js b/packages/stylis-logical-props-middleware/jest.config.js index 3cf182aa74..565c7abe54 100644 --- a/packages/stylis-logical-props-middleware/jest.config.js +++ b/packages/stylis-logical-props-middleware/jest.config.js @@ -2,12 +2,4 @@ module.exports = { preset: 'ts-jest', errorOnDeprecated: true, testMatch: ['<rootDir>/src/**/*.spec.ts'], - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - }, - }, - }, }; diff --git a/packages/stylis-logical-props-middleware/package.json b/packages/stylis-logical-props-middleware/package.json index 0acb71dbb3..88e3f5a1f5 100644 --- a/packages/stylis-logical-props-middleware/package.json +++ b/packages/stylis-logical-props-middleware/package.json @@ -40,23 +40,22 @@ "devDependencies": { "@rocket.chat/eslint-config-alt": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@types/jest": "~27.4.1", + "@types/jest": "~29.5.0", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "rimraf": "^3.0.2", "stylis": "4.1.3", - "ts-jest": "~27.1.5", - "typedoc": "~0.22.18", - "typescript": "~4.9.4" + "ts-jest": "~29.1.0", + "typedoc": "~0.24.1", + "typescript": "~5.0.4" }, "dependencies": { - "@rocket.chat/css-supports": "workspace:~", - "tslib": "^2.3.1" + "@rocket.chat/css-supports": "workspace:~" }, "peerDependencies": { "stylis": "4.0.10" diff --git a/packages/stylis-logical-props-middleware/tsconfig.json b/packages/stylis-logical-props-middleware/tsconfig.json index 4b343b2ef3..9d2804b026 100644 --- a/packages/stylis-logical-props-middleware/tsconfig.json +++ b/packages/stylis-logical-props-middleware/tsconfig.json @@ -11,7 +11,6 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "node", - "resolveJsonModule": true, - "importsNotUsedAsValues": "error" + "resolveJsonModule": true } } diff --git a/packages/ui-kit/README.md b/packages/ui-kit/README.md index cf02d0ed03..ff39be68a1 100644 --- a/packages/ui-kit/README.md +++ b/packages/ui-kit/README.md @@ -22,6 +22,16 @@ <!--install--> +Firstly, install the peer dependencies (prerequisites): + +```sh +npm i @rocket.chat/icons + +# or, if you are using yarn: + +yarn add @rocket.chat/icons +``` + Add `@rocket.chat/ui-kit` as a dependency: ```sh diff --git a/packages/ui-kit/jest.config.js b/packages/ui-kit/jest.config.js index 011ba307d6..8583028a5a 100644 --- a/packages/ui-kit/jest.config.js +++ b/packages/ui-kit/jest.config.js @@ -3,12 +3,4 @@ module.exports = { testEnvironment: 'node', errorOnDeprecated: true, testMatch: ['<rootDir>/src/**/*.spec.[jt]s?(x)'], - globals: { - 'ts-jest': { - tsconfig: { - noUnusedLocals: false, - noUnusedParameters: false, - }, - }, - }, }; diff --git a/packages/ui-kit/package.json b/packages/ui-kit/package.json index c88408fb20..c266b69253 100644 --- a/packages/ui-kit/package.json +++ b/packages/ui-kit/package.json @@ -37,27 +37,31 @@ "bump-next": "bump-next" }, "devDependencies": { - "@babel/core": "~7.19.6", - "@babel/eslint-parser": "~7.19.1", - "@babel/plugin-transform-runtime": "~7.19.6", - "@babel/preset-env": "~7.19.4", + "@babel/core": "~7.21.4", + "@babel/eslint-parser": "~7.21.3", + "@babel/plugin-transform-runtime": "~7.21.4", + "@babel/preset-env": "~7.21.4", "@rocket.chat/eslint-config-alt": "workspace:~", + "@rocket.chat/icons": "workspace:~", "@rocket.chat/prettier-config": "workspace:~", - "@types/jest": "~27.4.1", - "babel-loader": "~8.2.5", + "@types/jest": "~29.5.0", + "babel-loader": "~9.1.2", "bump": "workspace:~", - "eslint": "~8.26.0", - "jest": "~27.5.1", + "eslint": "~8.38.0", + "jest": "~29.5.0", "lint-all": "workspace:~", - "lint-staged": "~12.3.8", + "lint-staged": "~13.2.1", "npm-run-all": "^4.1.5", - "prettier": "~2.7.1", + "prettier": "~2.8.7", "rimraf": "^3.0.2", - "ts-jest": "~27.1.5", + "ts-jest": "~29.1.0", "ts-loader": "~9.4.2", - "typedoc": "~0.22.18", - "typescript": "~4.9.4", - "webpack": "~5.76.0", - "webpack-cli": "~4.10.0" + "typedoc": "~0.24.1", + "typescript": "~5.0.4", + "webpack": "~5.78.0", + "webpack-cli": "~5.0.1" + }, + "peerDependencies": { + "@rocket.chat/icons": "*" } } diff --git a/packages/ui-kit/src/blocks/ActionableElement.ts b/packages/ui-kit/src/blocks/ActionableElement.ts index 20ac6506ae..2a641c03b4 100644 --- a/packages/ui-kit/src/blocks/ActionableElement.ts +++ b/packages/ui-kit/src/blocks/ActionableElement.ts @@ -1,7 +1,9 @@ import type { ButtonElement } from './elements/ButtonElement'; import type { ChannelsSelectElement } from './elements/ChannelsSelectElement'; +import type { CheckboxElement } from './elements/CheckboxElement'; import type { ConversationsSelectElement } from './elements/ConversationsSelectElement'; import type { DatePickerElement } from './elements/DatePickerElement'; +import type { ExperimentalTabElement } from './elements/ExperimentalTabElement'; import type { LinearScaleElement } from './elements/LinearScaleElement'; import type { MultiChannelsSelectElement } from './elements/MultiChannelsSelectElement'; import type { MultiConversationsSelectElement } from './elements/MultiConversationsSelectElement'; @@ -9,7 +11,10 @@ import type { MultiStaticSelectElement } from './elements/MultiStaticSelectEleme import type { MultiUsersSelectElement } from './elements/MultiUsersSelectElement'; import type { OverflowElement } from './elements/OverflowElement'; import type { PlainTextInputElement } from './elements/PlainTextInputElement'; +import type { RadioButtonElement } from './elements/RadioButtonElement'; import type { StaticSelectElement } from './elements/StaticSelectElement'; +import type { TimePickerElement } from './elements/TimePickerElement'; +import type { ToggleSwitchElement } from './elements/ToggleSwitchElement'; import type { UsersSelectElement } from './elements/UsersSelectElement'; export type ActionableElement = @@ -25,4 +30,9 @@ export type ActionableElement = | OverflowElement | PlainTextInputElement | StaticSelectElement - | UsersSelectElement; + | UsersSelectElement + | ToggleSwitchElement + | RadioButtonElement + | CheckboxElement + | TimePickerElement + | ExperimentalTabElement; diff --git a/packages/ui-kit/src/blocks/BlockElement.ts b/packages/ui-kit/src/blocks/BlockElement.ts index 751dff66ff..dbceee41f7 100644 --- a/packages/ui-kit/src/blocks/BlockElement.ts +++ b/packages/ui-kit/src/blocks/BlockElement.ts @@ -1,7 +1,9 @@ import type { ButtonElement } from './elements/ButtonElement'; import type { ChannelsSelectElement } from './elements/ChannelsSelectElement'; +import type { CheckboxElement } from './elements/CheckboxElement'; import type { ConversationsSelectElement } from './elements/ConversationsSelectElement'; import type { DatePickerElement } from './elements/DatePickerElement'; +import type { ExperimentalTabElement } from './elements/ExperimentalTabElement'; import type { ImageElement } from './elements/ImageElement'; import type { LinearScaleElement } from './elements/LinearScaleElement'; import type { MultiChannelsSelectElement } from './elements/MultiChannelsSelectElement'; @@ -10,7 +12,10 @@ import type { MultiStaticSelectElement } from './elements/MultiStaticSelectEleme import type { MultiUsersSelectElement } from './elements/MultiUsersSelectElement'; import type { OverflowElement } from './elements/OverflowElement'; import type { PlainTextInputElement } from './elements/PlainTextInputElement'; +import type { RadioButtonElement } from './elements/RadioButtonElement'; import type { StaticSelectElement } from './elements/StaticSelectElement'; +import type { TimePickerElement } from './elements/TimePickerElement'; +import type { ToggleSwitchElement } from './elements/ToggleSwitchElement'; import type { UsersSelectElement } from './elements/UsersSelectElement'; export type BlockElement = @@ -27,4 +32,9 @@ export type BlockElement = | OverflowElement | PlainTextInputElement | StaticSelectElement - | UsersSelectElement; + | UsersSelectElement + | ToggleSwitchElement + | RadioButtonElement + | CheckboxElement + | TimePickerElement + | ExperimentalTabElement; diff --git a/packages/ui-kit/src/blocks/BlockElementType.ts b/packages/ui-kit/src/blocks/BlockElementType.ts index 981d7bcd42..73b7ef59cd 100644 --- a/packages/ui-kit/src/blocks/BlockElementType.ts +++ b/packages/ui-kit/src/blocks/BlockElementType.ts @@ -23,11 +23,16 @@ export enum BlockElementType { MULTI_CHANNELS_SELECT = 'multi_channels_select', MULTI_CONVERSATIONS_SELECT = 'multi_conversations_select', MULTI_USERS_SELECT = 'multi_users_select', + TOGGLE_SWITCH = 'toggle_switch', + RADIO_BUTTON = 'radio_button', + CHECKBOX = 'checkbox', + TIME_PICKER = 'time_picker', + TAB = 'tab', } // eslint-disable-next-line @typescript-eslint/no-unused-vars type AssertEnumKeysFromBlockUnionTypes = { [B in BlockElement as Uppercase< B['type'] - >]: typeof BlockElementType[Uppercase<B['type']>]; + >]: (typeof BlockElementType)[Uppercase<B['type']>]; }; diff --git a/packages/ui-kit/src/blocks/ElementType.ts b/packages/ui-kit/src/blocks/ElementType.ts index d91973275b..614ab9d0fc 100644 --- a/packages/ui-kit/src/blocks/ElementType.ts +++ b/packages/ui-kit/src/blocks/ElementType.ts @@ -19,4 +19,5 @@ export enum ElementType { MULTI_STATIC_SELECT = 'multi_static_select', DATEPICKER = 'datepicker', LINEAR_SCALE = 'linear_scale', + TAB = 'tab', } diff --git a/packages/ui-kit/src/blocks/LayoutBlock.ts b/packages/ui-kit/src/blocks/LayoutBlock.ts index 3679d7819c..a1f3cd342f 100644 --- a/packages/ui-kit/src/blocks/LayoutBlock.ts +++ b/packages/ui-kit/src/blocks/LayoutBlock.ts @@ -1,7 +1,9 @@ import type { ActionsBlock } from './layout/ActionsBlock'; +import type { CalloutBlock } from './layout/CalloutBlock'; import type { ConditionalBlock } from './layout/ConditionalBlock'; import type { ContextBlock } from './layout/ContextBlock'; import type { DividerBlock } from './layout/DividerBlock'; +import type { ExperimentalTabNavigationBlock } from './layout/ExperimentalTabNavigationBlock'; import type { ImageBlock } from './layout/ImageBlock'; import type { InputBlock } from './layout/InputBlock'; import type { PreviewBlock } from './layout/PreviewBlock'; @@ -18,4 +20,6 @@ export type LayoutBlock = | InputBlock | SectionBlock | VideoConferenceBlock - | PreviewBlock; + | PreviewBlock + | CalloutBlock + | ExperimentalTabNavigationBlock; diff --git a/packages/ui-kit/src/blocks/LayoutBlockType.ts b/packages/ui-kit/src/blocks/LayoutBlockType.ts index 0986621ff7..4dfb96c658 100644 --- a/packages/ui-kit/src/blocks/LayoutBlockType.ts +++ b/packages/ui-kit/src/blocks/LayoutBlockType.ts @@ -10,11 +10,13 @@ export enum LayoutBlockType { CONDITIONAL = 'conditional', PREVIEW = 'preview', VIDEO_CONF = 'video_conf', + CALLOUT = 'callout', + TAB_NAVIGATION = 'tab_navigation', } // eslint-disable-next-line @typescript-eslint/no-unused-vars type AssertEnumKeysFromBlockUnionTypes = { - [B in LayoutBlock as Uppercase<B['type']>]: typeof LayoutBlockType[Uppercase< + [B in LayoutBlock as Uppercase< B['type'] - >]; + >]: (typeof LayoutBlockType)[Uppercase<B['type']>]; }; diff --git a/packages/ui-kit/src/blocks/TextObject.ts b/packages/ui-kit/src/blocks/TextObject.ts index e825851c61..925ec15496 100644 --- a/packages/ui-kit/src/blocks/TextObject.ts +++ b/packages/ui-kit/src/blocks/TextObject.ts @@ -1,4 +1,11 @@ import type { Markdown } from './text/Markdown'; import type { PlainText } from './text/PlainText'; -export type TextObject = PlainText | Markdown; +type I18n = { + i18n?: { + key: string; + args?: { [key: string]: string | number }; + }; +}; + +export type TextObject = (PlainText | Markdown) & I18n; diff --git a/packages/ui-kit/src/blocks/TextObjectType.ts b/packages/ui-kit/src/blocks/TextObjectType.ts index 8155cf65e2..5ff491846c 100644 --- a/packages/ui-kit/src/blocks/TextObjectType.ts +++ b/packages/ui-kit/src/blocks/TextObjectType.ts @@ -11,7 +11,7 @@ export enum TextObjectType { // eslint-disable-next-line @typescript-eslint/no-unused-vars type AssertEnumKeysFromBlockUnionTypes = { - [B in TextObject as Uppercase<B['type']>]: typeof TextObjectType[Uppercase< + [B in TextObject as Uppercase<B['type']>]: (typeof TextObjectType)[Uppercase< B['type'] >]; }; diff --git a/packages/ui-kit/src/blocks/elements/ButtonElement.ts b/packages/ui-kit/src/blocks/elements/ButtonElement.ts index 566c525405..834479ecec 100644 --- a/packages/ui-kit/src/blocks/elements/ButtonElement.ts +++ b/packages/ui-kit/src/blocks/elements/ButtonElement.ts @@ -6,5 +6,6 @@ export type ButtonElement = Actionable<{ text: PlainText; url?: string; value?: string; - style?: 'primary' | 'danger'; + style?: 'primary' | 'secondary' | 'danger' | 'warning' | 'success'; + secondary?: boolean; }>; diff --git a/packages/ui-kit/src/blocks/elements/CheckboxElement.ts b/packages/ui-kit/src/blocks/elements/CheckboxElement.ts new file mode 100644 index 0000000000..2411e37c17 --- /dev/null +++ b/packages/ui-kit/src/blocks/elements/CheckboxElement.ts @@ -0,0 +1,8 @@ +import type { Actionable } from '../Actionable'; +import type { Option } from '../Option'; + +export type CheckboxElement = Actionable<{ + type: 'checkbox'; + options: Option[]; + initialOptions?: Option[]; +}>; diff --git a/packages/ui-kit/src/blocks/elements/ExperimentalTabElement.ts b/packages/ui-kit/src/blocks/elements/ExperimentalTabElement.ts new file mode 100644 index 0000000000..be4fc4f5ac --- /dev/null +++ b/packages/ui-kit/src/blocks/elements/ExperimentalTabElement.ts @@ -0,0 +1,9 @@ +import type { Actionable } from '../Actionable'; +import type { TextObject } from '../TextObject'; + +export type ExperimentalTabElement = Actionable<{ + type: 'tab'; + title: TextObject; + disabled?: boolean; + selected?: boolean; +}>; diff --git a/packages/ui-kit/src/blocks/elements/RadioButtonElement.ts b/packages/ui-kit/src/blocks/elements/RadioButtonElement.ts new file mode 100644 index 0000000000..5c87ad86e6 --- /dev/null +++ b/packages/ui-kit/src/blocks/elements/RadioButtonElement.ts @@ -0,0 +1,8 @@ +import type { Actionable } from '../Actionable'; +import type { Option } from '../Option'; + +export type RadioButtonElement = Actionable<{ + type: 'radio_button'; + options: Option[]; + initialOption?: Option; +}>; diff --git a/packages/ui-kit/src/blocks/elements/TimePickerElement.ts b/packages/ui-kit/src/blocks/elements/TimePickerElement.ts new file mode 100644 index 0000000000..fa23f8ff48 --- /dev/null +++ b/packages/ui-kit/src/blocks/elements/TimePickerElement.ts @@ -0,0 +1,8 @@ +import type { Actionable } from '../Actionable'; +import type { TextObject } from '../TextObject'; + +export type TimePickerElement = Actionable<{ + type: 'time_picker'; + placeholder?: TextObject; + initialTime?: string; +}>; diff --git a/packages/ui-kit/src/blocks/elements/ToggleSwitchElement.ts b/packages/ui-kit/src/blocks/elements/ToggleSwitchElement.ts new file mode 100644 index 0000000000..8b7e245d5b --- /dev/null +++ b/packages/ui-kit/src/blocks/elements/ToggleSwitchElement.ts @@ -0,0 +1,8 @@ +import type { Actionable } from '../Actionable'; +import type { Option } from '../Option'; + +export type ToggleSwitchElement = Actionable<{ + type: 'toggle_switch'; + options: Option[]; + initialOptions?: Option[]; +}>; diff --git a/packages/ui-kit/src/blocks/isActionsBlockElement.ts b/packages/ui-kit/src/blocks/isActionsBlockElement.ts index e4915688f0..d57f9da5eb 100644 --- a/packages/ui-kit/src/blocks/isActionsBlockElement.ts +++ b/packages/ui-kit/src/blocks/isActionsBlockElement.ts @@ -12,6 +12,10 @@ export const isActionsBlockElement = ( case BlockElementType.MULTI_STATIC_SELECT: case BlockElementType.OVERFLOW: case BlockElementType.STATIC_SELECT: + case BlockElementType.TOGGLE_SWITCH: + case BlockElementType.CHECKBOX: + case BlockElementType.RADIO_BUTTON: + case BlockElementType.TIME_PICKER: return true; default: diff --git a/packages/ui-kit/src/blocks/layout/ActionsBlock.ts b/packages/ui-kit/src/blocks/layout/ActionsBlock.ts index 436b009a3b..11e952a97d 100644 --- a/packages/ui-kit/src/blocks/layout/ActionsBlock.ts +++ b/packages/ui-kit/src/blocks/layout/ActionsBlock.ts @@ -1,6 +1,7 @@ import type { LayoutBlockish } from '../LayoutBlockish'; import type { ButtonElement } from '../elements/ButtonElement'; import type { ChannelsSelectElement } from '../elements/ChannelsSelectElement'; +import type { CheckboxElement } from '../elements/CheckboxElement'; import type { ConversationsSelectElement } from '../elements/ConversationsSelectElement'; import type { DatePickerElement } from '../elements/DatePickerElement'; import type { LinearScaleElement } from '../elements/LinearScaleElement'; @@ -9,7 +10,10 @@ import type { MultiConversationsSelectElement } from '../elements/MultiConversat import type { MultiStaticSelectElement } from '../elements/MultiStaticSelectElement'; import type { MultiUsersSelectElement } from '../elements/MultiUsersSelectElement'; import type { OverflowElement } from '../elements/OverflowElement'; +import type { RadioButtonElement } from '../elements/RadioButtonElement'; import type { StaticSelectElement } from '../elements/StaticSelectElement'; +import type { TimePickerElement } from '../elements/TimePickerElement'; +import type { ToggleSwitchElement } from '../elements/ToggleSwitchElement'; import type { UsersSelectElement } from '../elements/UsersSelectElement'; export type ActionsBlock = LayoutBlockish<{ @@ -27,5 +31,9 @@ export type ActionsBlock = LayoutBlockish<{ | OverflowElement | StaticSelectElement | UsersSelectElement + | ToggleSwitchElement + | CheckboxElement + | RadioButtonElement + | TimePickerElement )[]; }>; diff --git a/packages/ui-kit/src/blocks/layout/CalloutBlock.ts b/packages/ui-kit/src/blocks/layout/CalloutBlock.ts new file mode 100644 index 0000000000..0b0cfe76fd --- /dev/null +++ b/packages/ui-kit/src/blocks/layout/CalloutBlock.ts @@ -0,0 +1,12 @@ +import type { Keys } from '@rocket.chat/icons'; + +import type { LayoutBlockish } from '../LayoutBlockish'; +import type { TextObject } from '../TextObject'; + +export type CalloutBlock = LayoutBlockish<{ + type: 'callout'; + title?: TextObject; + icon?: Keys; + text: TextObject; + variant?: 'info' | 'danger' | 'warning' | 'success'; +}>; diff --git a/packages/ui-kit/src/blocks/layout/ExperimentalTabNavigationBlock.ts b/packages/ui-kit/src/blocks/layout/ExperimentalTabNavigationBlock.ts new file mode 100644 index 0000000000..90a9bf39d2 --- /dev/null +++ b/packages/ui-kit/src/blocks/layout/ExperimentalTabNavigationBlock.ts @@ -0,0 +1,7 @@ +import type { LayoutBlockish } from '../LayoutBlockish'; +import type { ExperimentalTabElement } from '../elements/ExperimentalTabElement'; + +export type ExperimentalTabNavigationBlock = LayoutBlockish<{ + type: 'tab_navigation'; + tabs: readonly ExperimentalTabElement[]; +}>; diff --git a/packages/ui-kit/src/blocks/layout/InputBlock.ts b/packages/ui-kit/src/blocks/layout/InputBlock.ts index 9b9de53b45..ae06b0d2c0 100644 --- a/packages/ui-kit/src/blocks/layout/InputBlock.ts +++ b/packages/ui-kit/src/blocks/layout/InputBlock.ts @@ -1,5 +1,6 @@ import type { LayoutBlockish } from '../LayoutBlockish'; import type { ChannelsSelectElement } from '../elements/ChannelsSelectElement'; +import type { CheckboxElement } from '../elements/CheckboxElement'; import type { ConversationsSelectElement } from '../elements/ConversationsSelectElement'; import type { DatePickerElement } from '../elements/DatePickerElement'; import type { LinearScaleElement } from '../elements/LinearScaleElement'; @@ -8,7 +9,10 @@ import type { MultiConversationsSelectElement } from '../elements/MultiConversat import type { MultiStaticSelectElement } from '../elements/MultiStaticSelectElement'; import type { MultiUsersSelectElement } from '../elements/MultiUsersSelectElement'; import type { PlainTextInputElement } from '../elements/PlainTextInputElement'; +import type { RadioButtonElement } from '../elements/RadioButtonElement'; import type { StaticSelectElement } from '../elements/StaticSelectElement'; +import type { TimePickerElement } from '../elements/TimePickerElement'; +import type { ToggleSwitchElement } from '../elements/ToggleSwitchElement'; import type { UsersSelectElement } from '../elements/UsersSelectElement'; import type { PlainText } from '../text/PlainText'; @@ -26,7 +30,11 @@ export type InputBlock = LayoutBlockish<{ | MultiUsersSelectElement | PlainTextInputElement | StaticSelectElement - | UsersSelectElement; + | UsersSelectElement + | CheckboxElement + | RadioButtonElement + | TimePickerElement + | ToggleSwitchElement; hint?: PlainText; optional?: boolean; }>; diff --git a/packages/ui-kit/src/index.ts b/packages/ui-kit/src/index.ts index a5e901865c..4d1b97046d 100644 --- a/packages/ui-kit/src/index.ts +++ b/packages/ui-kit/src/index.ts @@ -22,7 +22,12 @@ export { OverflowElement } from './blocks/elements/OverflowElement'; export { PlainTextInputElement } from './blocks/elements/PlainTextInputElement'; export { StaticSelectElement } from './blocks/elements/StaticSelectElement'; export { UsersSelectElement } from './blocks/elements/UsersSelectElement'; +export { ToggleSwitchElement } from './blocks/elements/ToggleSwitchElement'; +export { CheckboxElement } from './blocks/elements/CheckboxElement'; +export { RadioButtonElement } from './blocks/elements/RadioButtonElement'; +export { TimePickerElement } from './blocks/elements/TimePickerElement'; export { BlockElement } from './blocks/BlockElement'; +export { ExperimentalTabElement } from './blocks/elements/ExperimentalTabElement'; export { ActionsBlock } from './blocks/layout/ActionsBlock'; export { ConditionalBlock } from './blocks/layout/ConditionalBlock'; @@ -34,6 +39,8 @@ export { InputBlock } from './blocks/layout/InputBlock'; export { SectionBlock } from './blocks/layout/SectionBlock'; export { VideoConferenceBlock } from './blocks/layout/VideoConferenceBlock'; export { LayoutBlock } from './blocks/LayoutBlock'; +export { CalloutBlock } from './blocks/layout/CalloutBlock'; +export { ExperimentalTabNavigationBlock } from './blocks/layout/ExperimentalTabNavigationBlock'; export { InputElementDispatchAction } from './blocks/InputElementDispatchAction'; @@ -51,6 +58,7 @@ export { uiKitAttachment } from './rendering/surfaces/uiKitAttachment'; export { uiKitBanner } from './rendering/surfaces/uiKitBanner'; export { uiKitMessage } from './rendering/surfaces/uiKitMessage'; export { uiKitModal } from './rendering/surfaces/uiKitModal'; +export { uiKitContextualBar } from './rendering/surfaces/uiKitContextualBar'; export { UiKitParserAttachment, @@ -68,6 +76,10 @@ export { UiKitParserModal, ModalSurfaceLayout, } from './rendering/surfaces/UiKitParserModal'; +export { + UiKitParserContextualBar, + ContextualBarSurfaceLayout, +} from './rendering/surfaces/UiKitParserContextualBar'; export { SurfaceRenderer } from './rendering/SurfaceRenderer'; export { ActionableElement } from './blocks/ActionableElement'; diff --git a/packages/ui-kit/src/rendering/ActionOf.ts b/packages/ui-kit/src/rendering/ActionOf.ts index 9d52239f93..65221eb5ed 100644 --- a/packages/ui-kit/src/rendering/ActionOf.ts +++ b/packages/ui-kit/src/rendering/ActionOf.ts @@ -1,6 +1,8 @@ import type { ActionableElement } from '../blocks/ActionableElement'; +import type { Option } from '../blocks/Option'; import type { ButtonElement } from '../blocks/elements/ButtonElement'; import type { ChannelsSelectElement } from '../blocks/elements/ChannelsSelectElement'; +import type { CheckboxElement } from '../blocks/elements/CheckboxElement'; import type { ConversationsSelectElement } from '../blocks/elements/ConversationsSelectElement'; import type { DatePickerElement } from '../blocks/elements/DatePickerElement'; import type { LinearScaleElement } from '../blocks/elements/LinearScaleElement'; @@ -10,7 +12,10 @@ import type { MultiStaticSelectElement } from '../blocks/elements/MultiStaticSel import type { MultiUsersSelectElement } from '../blocks/elements/MultiUsersSelectElement'; import type { OverflowElement } from '../blocks/elements/OverflowElement'; import type { PlainTextInputElement } from '../blocks/elements/PlainTextInputElement'; +import type { RadioButtonElement } from '../blocks/elements/RadioButtonElement'; import type { StaticSelectElement } from '../blocks/elements/StaticSelectElement'; +import type { TimePickerElement } from '../blocks/elements/TimePickerElement'; +import type { ToggleSwitchElement } from '../blocks/elements/ToggleSwitchElement'; import type { UsersSelectElement } from '../blocks/elements/UsersSelectElement'; export type ActionOf<TElement extends ActionableElement> = @@ -40,4 +45,12 @@ export type ActionOf<TElement extends ActionableElement> = ? StaticSelectElement['initialValue'] : TElement extends UsersSelectElement ? unknown + : TElement extends ToggleSwitchElement + ? Option['value'][] + : TElement extends RadioButtonElement + ? Option['value'] | undefined + : TElement extends CheckboxElement + ? Option['value'][] | undefined + : TElement extends TimePickerElement + ? TimePickerElement['initialTime'] : never; diff --git a/packages/ui-kit/src/rendering/SurfaceRenderer.ts b/packages/ui-kit/src/rendering/SurfaceRenderer.ts index a365286846..6432eb994b 100644 --- a/packages/ui-kit/src/rendering/SurfaceRenderer.ts +++ b/packages/ui-kit/src/rendering/SurfaceRenderer.ts @@ -25,20 +25,23 @@ import { renderTextObject } from './renderTextObject'; import { resolveConditionalBlocks } from './resolveConditionalBlocks'; export abstract class SurfaceRenderer< - T, - B extends RenderableLayoutBlock = RenderableLayoutBlock -> implements BlockRenderers<T> + TOutputObject, + TAllowedLayoutBlock extends RenderableLayoutBlock = RenderableLayoutBlock +> implements BlockRenderers<TOutputObject> { - protected readonly allowedLayoutBlockTypes: Set<B['type']>; + protected readonly allowedLayoutBlockTypes: Set<TAllowedLayoutBlock['type']>; - public constructor(allowedLayoutBlockTypes: B['type'][]) { + public constructor(allowedLayoutBlockTypes: TAllowedLayoutBlock['type'][]) { this.allowedLayoutBlockTypes = new Set(allowedLayoutBlockTypes); } - private isAllowedLayoutBlock = (block: Block): block is B => - this.allowedLayoutBlockTypes.has(block.type as B['type']); + private isAllowedLayoutBlock = (block: Block): block is TAllowedLayoutBlock => + this.allowedLayoutBlockTypes.has(block.type as TAllowedLayoutBlock['type']); - public render(blocks: readonly Block[], conditions?: Conditions): T[] { + public render( + blocks: readonly Block[], + conditions?: Conditions + ): TOutputObject[] { if (!Array.isArray(blocks)) { return []; } @@ -54,14 +57,14 @@ export abstract class SurfaceRenderer< textObject: TextObject, index: number, context: BlockContext - ): T | null { + ): TOutputObject | null { return renderTextObject(this, context)(textObject, index); } public renderActionsBlockElement( block: BlockElement, index: number - ): T | null { + ): TOutputObject | null { if ( this.allowedLayoutBlockTypes.has(LayoutBlockType.ACTIONS) === false && !isActionsBlockElement(block) @@ -78,14 +81,14 @@ export abstract class SurfaceRenderer< _context: BlockContext, _: undefined, index: number - ): T | null { + ): TOutputObject | null { return this.renderActionsBlockElement(element, index); } public renderContextBlockElement( block: TextObject | BlockElement, index: number - ): T | null { + ): TOutputObject | null { if ( this.allowedLayoutBlockTypes.has(LayoutBlockType.CONTEXT) === false && !isContextBlockElement(block) @@ -106,11 +109,14 @@ export abstract class SurfaceRenderer< _context: BlockContext, _: undefined, index: number - ): T | null { + ): TOutputObject | null { return this.renderContextBlockElement(element, index); } - public renderInputBlockElement(block: BlockElement, index: number): T | null { + public renderInputBlockElement( + block: BlockElement, + index: number + ): TOutputObject | null { if ( this.allowedLayoutBlockTypes.has(LayoutBlockType.INPUT) === false && !isInputBlockElement(block) @@ -127,14 +133,14 @@ export abstract class SurfaceRenderer< _context: BlockContext, _: undefined, index: number - ): T | null { + ): TOutputObject | null { return this.renderInputBlockElement(element, index); } public renderSectionAccessoryBlockElement( block: BlockElement, index: number - ): T | null { + ): TOutputObject | null { if ( this.allowedLayoutBlockTypes.has(LayoutBlockType.SECTION) === false && !isSectionBlockAccessoryElement(block) @@ -151,7 +157,7 @@ export abstract class SurfaceRenderer< _context: BlockContext, _: undefined, index: number - ): T | null { + ): TOutputObject | null { return this.renderSectionAccessoryBlockElement(element, index); } @@ -160,7 +166,7 @@ export abstract class SurfaceRenderer< element: PlainText, context: BlockContext = BlockContext.NONE, index = 0 - ): T | null { + ): TOutputObject | null { return this[TextObjectType.PLAIN_TEXT](element, context, index); } @@ -169,7 +175,7 @@ export abstract class SurfaceRenderer< textObject: TextObject, context: BlockContext = BlockContext.NONE, index = 0 - ): T | null { + ): TOutputObject | null { switch (textObject.type) { case TextObjectType.PLAIN_TEXT: return this.plain_text(textObject, context, index); @@ -186,11 +192,11 @@ export abstract class SurfaceRenderer< textObject: PlainText, context: BlockContext, index: number - ): T | null; + ): TOutputObject | null; public abstract mrkdwn( textObject: Markdown, context: BlockContext, index: number - ): T | null; + ): TOutputObject | null; } diff --git a/packages/ui-kit/src/rendering/createSurfaceRenderer.ts b/packages/ui-kit/src/rendering/createSurfaceRenderer.ts index 838dd9c537..ebad8de65e 100644 --- a/packages/ui-kit/src/rendering/createSurfaceRenderer.ts +++ b/packages/ui-kit/src/rendering/createSurfaceRenderer.ts @@ -4,7 +4,10 @@ import type { Conditions } from './Conditions'; import type { SurfaceRenderer } from './SurfaceRenderer'; export const createSurfaceRenderer = - <T, B extends RenderableLayoutBlock>() => - (surfaceRenderer: SurfaceRenderer<T, B>, conditions?: Conditions) => - (blocks: readonly { type: string }[]): T[] => + <TAllowedLayoutBlock extends RenderableLayoutBlock>() => + <TOutputObject>( + surfaceRenderer: SurfaceRenderer<TOutputObject, TAllowedLayoutBlock>, + conditions?: Conditions + ) => + (blocks: readonly { type: string }[]): TOutputObject[] => surfaceRenderer.render(blocks as Block[], conditions); diff --git a/packages/ui-kit/src/rendering/surfaces/UiKitParserAttachment.ts b/packages/ui-kit/src/rendering/surfaces/UiKitParserAttachment.ts index 82768f8170..eba83cbf4c 100644 --- a/packages/ui-kit/src/rendering/surfaces/UiKitParserAttachment.ts +++ b/packages/ui-kit/src/rendering/surfaces/UiKitParserAttachment.ts @@ -1,4 +1,5 @@ import type { ActionsBlock } from '../../blocks/layout/ActionsBlock'; +import type { CalloutBlock } from '../../blocks/layout/CalloutBlock'; import type { ContextBlock } from '../../blocks/layout/ContextBlock'; import type { DividerBlock } from '../../blocks/layout/DividerBlock'; import type { ImageBlock } from '../../blocks/layout/ImageBlock'; @@ -10,14 +11,15 @@ type AttachmentSurfaceLayoutBlock = | ContextBlock | DividerBlock | ImageBlock - | SectionBlock; + | SectionBlock + | CalloutBlock; export abstract class UiKitParserAttachment<T> extends SurfaceRenderer< T, AttachmentSurfaceLayoutBlock > { public constructor() { - super(['actions', 'context', 'divider', 'image', 'section']); + super(['actions', 'context', 'divider', 'image', 'section', 'callout']); } } diff --git a/packages/ui-kit/src/rendering/surfaces/UiKitParserBanner.ts b/packages/ui-kit/src/rendering/surfaces/UiKitParserBanner.ts index 81f069cf41..8d36c15675 100644 --- a/packages/ui-kit/src/rendering/surfaces/UiKitParserBanner.ts +++ b/packages/ui-kit/src/rendering/surfaces/UiKitParserBanner.ts @@ -1,4 +1,5 @@ import type { ActionsBlock } from '../../blocks/layout/ActionsBlock'; +import type { CalloutBlock } from '../../blocks/layout/CalloutBlock'; import type { ContextBlock } from '../../blocks/layout/ContextBlock'; import type { DividerBlock } from '../../blocks/layout/DividerBlock'; import type { ImageBlock } from '../../blocks/layout/ImageBlock'; @@ -12,7 +13,8 @@ type BannerSurfaceLayoutBlock = | DividerBlock | ImageBlock | InputBlock - | SectionBlock; + | SectionBlock + | CalloutBlock; export abstract class UiKitParserBanner<T> extends SurfaceRenderer< T, diff --git a/packages/ui-kit/src/rendering/surfaces/UiKitParserContextualBar.spec.ts b/packages/ui-kit/src/rendering/surfaces/UiKitParserContextualBar.spec.ts new file mode 100644 index 0000000000..4cb869b5ab --- /dev/null +++ b/packages/ui-kit/src/rendering/surfaces/UiKitParserContextualBar.spec.ts @@ -0,0 +1,1950 @@ +import type { PlainText, OverflowElement } from '../..'; +import { uiKitContextualBar, UiKitParserContextualBar } from '../..'; +import { BlockContext } from '../BlockContext'; + +class TestParser extends UiKitParserContextualBar<unknown> { + plain_text = (element: any, context: any, index: any): any => ({ + component: 'text', + props: { + key: index, + children: element.text, + emoji: element.emoji, + block: context === BlockContext.BLOCK, + }, + }); + + mrkdwn = (element: any, context: any, index: any): any => ({ + component: 'markdown', + props: { + key: index, + children: element.text, + verbatim: Boolean(element.verbatim), + block: context === BlockContext.BLOCK, + }, + }); + + divider = (_element: any, context: any, index: any): any => ({ + component: 'divider', + props: { + key: index, + block: context === BlockContext.BLOCK, + }, + }); + + section = (element: any, context: any, index: any): any => { + let key = 0; + return { + component: 'section', + props: { + key: index, + children: [ + ...(element.text + ? [this.text(element.text, BlockContext.SECTION, key++)] + : []), + ...(element.fields?.map((field: any) => + this.text(field, BlockContext.SECTION, key++) + ) ?? []), + ...(element.accessory + ? [ + this.renderAccessories( + element.accessory, + BlockContext.SECTION, + undefined, + key++ + ), + ] + : []), + ], + block: context === BlockContext.BLOCK, + }, + }; + }; + + actions = (element: any, context: any, index: any): any => ({ + component: 'actions', + props: { + key: index, + children: element.elements.map((element: any, key: number) => + this.renderActions(element, BlockContext.ACTION, undefined, key) + ), + block: context === BlockContext.BLOCK, + }, + }); + + context = (element: any, context: any, index: any): any => ({ + component: 'context', + props: { + key: index, + children: element.elements.map((element: any, key: number) => + this.renderContext(element, BlockContext.CONTEXT, undefined, key) + ), + block: context === BlockContext.BLOCK, + }, + }); + + input = (element: any, context: any, index: any): any => ({ + component: 'input-group', + props: { + key: index, + children: [ + this.plainText(element.label, BlockContext.FORM, 0), + this.renderInputs(element.element, BlockContext.FORM, undefined, 1), + ...(element.hint + ? [this.plainText(element.hint, BlockContext.FORM, 2)] + : []), + ], + block: context === BlockContext.BLOCK, + }, + }); + + button = (element: any, context: any, index: any): any => ({ + component: 'button', + props: { + key: index, + children: element.text + ? [this.text(element.text, BlockContext.SECTION, 0)] + : [], + ...(element.url && { href: element.url }), + ...(element.value && { value: element.value }), + variant: element.style ?? 'normal', + block: context === BlockContext.BLOCK, + }, + }); + + image = (element: any, context: any, index: any): any => { + if (context === BlockContext.BLOCK) { + let key = 0; + return { + component: 'image-container', + props: { + key: index, + children: [ + { + component: 'image', + props: { + key: key++, + src: element.imageUrl, + alt: element.altText, + block: false, + }, + }, + ...(element.title + ? [this.plainText(element.title, -1, key++)] + : []), + ], + block: true, + }, + }; + } + + return { + component: 'image', + props: { + key: index, + src: element.imageUrl, + alt: element.altText, + block: false, + }, + }; + }; + + overflow = (element: OverflowElement, _context: any, index: any): any => ({ + component: 'menu', + props: { + key: index, + children: element.options.map((option, key) => ({ + component: 'menu-item', + props: { + key, + children: [ + this.text(option.text, -1, 0), + ...(option.description + ? [this.plainText(option.description, -1, 1)] + : []), + ], + value: option.value, + ...(option.url && { url: option.url }), + }, + })), + }, + }); + + datePicker = (element: any, _context: any, index: any): any => ({ + component: 'input', + props: { + key: index, + type: 'date', + ...(element.placeholder && { + placeholder: this.text(element.placeholder, -1, 0), + }), + ...(element.initialDate && { defaultValue: element.initialDate }), + }, + }); + + staticSelect = (element: any, _context: any, index: any): any => ({ + component: 'select', + props: { + key: index, + ...(element.placeholder && { + placeholder: this.text(element.placeholder, -1, 0), + }), + children: element.options.map((option: any, key: any) => ({ + component: 'option', + props: { + key, + children: this.text(option.text, -1, 0), + value: option.value, + ...(option.description && { + description: this.text(option.description, -1, 0), + }), + }, + })), + ...(element.initialOption && { + defaultValue: element.options.find( + (option: any) => option.value === element.initialOption.value + )?.value, + }), + }, + }); + + multiStaticSelect = (element: any, _context: any, index: any): any => ({ + component: 'select', + props: { + key: index, + ...(element.placeholder && { + placeholder: this.text(element.placeholder, -1, 0), + }), + multiple: true, + children: element.options.map((option: any, key: any) => ({ + component: 'option', + props: { + key, + children: this.text(option.text, -1, 0), + value: option.value, + ...(option.description && { + description: this.text(option.description, -1, 0), + }), + }, + })), + ...(element.initialOptions && { + defaultValue: element.options + .filter((option: any) => + element.initialOptions.some( + (initialOption: any) => option.value === initialOption.value + ) + ) + .map((option: any) => option.value), + }), + }, + }); + + plainInput = (element: any, _context: any, index: any): any => ({ + component: 'input', + props: { + key: index, + type: 'text', + ...(element.placeholder && { + placeholder: this.plainText(element.placeholder, -1, 0), + }), + ...(element.initialValue && { defaultValue: element.initialValue }), + multiline: element.multiline ?? false, + ...(typeof element.minLength !== 'undefined' && { + minLength: element.minLength, + }), + ...(typeof element.maxLength !== 'undefined' && { + maxLength: element.maxLength, + }), + }, + }); + + linearScale = ( + { minValue = 0, maxValue = 10 }: any, + _context: any, + index: any + ): any => ({ + component: 'linear-scale', + props: { + key: index, + children: Array.from({ length: maxValue - minValue + 1 }).map( + (_, key: any) => ({ + component: 'linear-scale-point', + props: { + key, + children: [ + this.text( + { + type: 'plain_text', + text: String(minValue + key), + emoji: true, + } as PlainText, + -1, + 0 + ), + ], + }, + }) + ), + }, + }); +} + +const parser = new TestParser(); +const parse = uiKitContextualBar(parser); + +const conditionalParse = uiKitContextualBar(parser, { + engine: 'rocket.chat', +}); + +describe('divider', () => { + it('renders', () => { + const payload = [ + { + type: 'divider', + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'divider', + props: { + key: 0, + block: true, + }, + }, + ]); + }); +}); + +describe('section', () => { + it('renders text as plain_text', () => { + const payload = [ + { + type: 'section', + text: { + type: 'plain_text', + text: 'This is a plain text section block.', + emoji: true, + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'section', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'This is a plain text section block.', + emoji: true, + block: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('render text as mrkdwn', () => { + const payload = [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'This is a mrkdwn section block :ghost: *this is bold*, and ~this is crossed out~, and <https://google.com|this is a link>', + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'section', + props: { + key: 0, + children: [ + { + component: 'markdown', + props: { + key: 0, + children: + 'This is a mrkdwn section block :ghost: *this is bold*, and ~this is crossed out~, and <https://google.com|this is a link>', + verbatim: false, + block: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders text fields', () => { + const payload = [ + { + type: 'section', + fields: [ + { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + ], + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'section', + props: { + key: 0, + block: true, + children: [ + { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + { + component: 'text', + props: { + key: 1, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + { + component: 'text', + props: { + key: 2, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + { + component: 'text', + props: { + key: 3, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + { + component: 'text', + props: { + key: 4, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + ], + }, + }, + ]); + }); + + it('renders accessory as button', () => { + const payload = [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'This is a section block with a button.', + }, + accessory: { + type: 'button', + text: { + type: 'plain_text', + text: 'Click Me', + emoji: true, + }, + value: 'click_me_123', + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'section', + props: { + key: 0, + block: true, + children: [ + { + component: 'markdown', + props: { + key: 0, + children: 'This is a section block with a button.', + verbatim: false, + block: false, + }, + }, + { + component: 'button', + props: { + key: 1, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'Click Me', + emoji: true, + block: false, + }, + }, + ], + value: 'click_me_123', + variant: 'normal', + block: false, + }, + }, + ], + }, + }, + ]); + }); + + it('renders accessory as image', () => { + const payload = [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'This is a section block with an accessory image.', + }, + accessory: { + type: 'image', + imageUrl: + 'https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg', + altText: 'cute cat', + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'section', + props: { + key: 0, + block: true, + children: [ + { + component: 'markdown', + props: { + key: 0, + children: 'This is a section block with an accessory image.', + verbatim: false, + block: false, + }, + }, + { + component: 'image', + props: { + key: 1, + src: 'https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg', + alt: 'cute cat', + block: false, + }, + }, + ], + }, + }, + ]); + }); + + it('renders accessory as overflow menu', () => { + const payload = [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'This is a section block with an overflow menu.', + }, + accessory: { + type: 'overflow', + options: [ + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-0', + }, + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-1', + }, + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-2', + }, + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-3', + }, + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-4', + }, + ], + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'section', + props: { + key: 0, + block: true, + children: [ + { + component: 'markdown', + props: { + key: 0, + children: 'This is a section block with an overflow menu.', + verbatim: false, + block: false, + }, + }, + { + component: 'menu', + props: { + key: 1, + children: [ + { + component: 'menu-item', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + ], + value: 'value-0', + }, + }, + { + component: 'menu-item', + props: { + key: 1, + children: [ + { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + ], + value: 'value-1', + }, + }, + { + component: 'menu-item', + props: { + key: 2, + children: [ + { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + ], + value: 'value-2', + }, + }, + { + component: 'menu-item', + props: { + key: 3, + children: [ + { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + ], + value: 'value-3', + }, + }, + { + component: 'menu-item', + props: { + key: 4, + children: [ + { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + ], + value: 'value-4', + }, + }, + ], + }, + }, + ], + }, + }, + ]); + }); + + it('renders accessory as datepicker', () => { + const payload = [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Pick a date for the deadline.', + }, + accessory: { + type: 'datepicker', + initial_date: '1990-04-28', + placeholder: { + type: 'plain_text', + text: 'Select a date', + emoji: true, + }, + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'section', + props: { + key: 0, + block: true, + children: [ + { + component: 'markdown', + props: { + key: 0, + children: 'Pick a date for the deadline.', + verbatim: false, + block: false, + }, + }, + { + component: 'input', + props: { + key: 1, + type: 'date', + placeholder: { + component: 'text', + props: { + key: 0, + children: 'Select a date', + emoji: true, + block: false, + }, + }, + }, + }, + ], + }, + }, + ]); + }); +}); + +describe('image', () => { + it('renders with title', () => { + const payload = [ + { + type: 'image', + title: { + type: 'plain_text', + text: 'I Need a Marg', + emoji: true, + }, + imageUrl: + 'https://assets3.thrillist.com/v1/image/1682388/size/tl-horizontal_main.jpg', + altText: 'marg', + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'image-container', + props: { + key: 0, + children: [ + { + component: 'image', + props: { + key: 0, + src: 'https://assets3.thrillist.com/v1/image/1682388/size/tl-horizontal_main.jpg', + alt: 'marg', + block: false, + }, + }, + { + component: 'text', + props: { + key: 1, + children: 'I Need a Marg', + emoji: true, + block: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders with no title', () => { + const payload = [ + { + type: 'image', + imageUrl: + 'https://i1.wp.com/thetempest.co/wp-content/uploads/2017/08/The-wise-words-of-Michael-Scott-Imgur-2.jpg?w=1024&ssl=1', + altText: 'inspiration', + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'image-container', + props: { + key: 0, + children: [ + { + component: 'image', + props: { + key: 0, + src: 'https://i1.wp.com/thetempest.co/wp-content/uploads/2017/08/The-wise-words-of-Michael-Scott-Imgur-2.jpg?w=1024&ssl=1', + alt: 'inspiration', + block: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); +}); + +describe('actions', () => { + it('renders all selects', () => { + const payload = [ + { + type: 'actions', + elements: [ + { + type: 'conversations_select', + placeholder: { + type: 'plain_text', + text: 'Select a conversation', + emoji: true, + }, + }, + { + type: 'channels_select', + placeholder: { + type: 'plain_text', + text: 'Select a channel', + emoji: true, + }, + }, + { + type: 'users_select', + placeholder: { + type: 'plain_text', + text: 'Select a user', + emoji: true, + }, + }, + { + type: 'static_select', + placeholder: { + type: 'plain_text', + text: 'Select an item', + emoji: true, + }, + options: [ + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-0', + }, + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-1', + }, + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-2', + }, + ], + }, + ], + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'actions', + props: { + key: 0, + children: [ + null, + null, + null, + { + component: 'select', + props: { + key: 3, + children: [ + { + component: 'option', + props: { + key: 0, + children: { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + value: 'value-0', + }, + }, + { + component: 'option', + props: { + key: 1, + children: { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + value: 'value-1', + }, + }, + { + component: 'option', + props: { + key: 2, + children: { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + value: 'value-2', + }, + }, + ], + placeholder: { + component: 'text', + props: { + key: 0, + children: 'Select an item', + emoji: true, + block: false, + }, + }, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders filtered conversations select', () => { + const payload = [ + { + type: 'actions', + elements: [ + { + type: 'conversations_select', + placeholder: { + type: 'plain_text', + text: 'Select private conversation', + emoji: true, + }, + filter: { + include: ['private'], + }, + }, + ], + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'actions', + props: { + key: 0, + children: [null], + block: true, + }, + }, + ]); + }); + + it('renders selects with initial options', () => { + const payload = [ + { + type: 'actions', + elements: [ + { + type: 'conversations_select', + placeholder: { + type: 'plain_text', + text: 'Select a conversation', + emoji: true, + }, + initialConversation: 'D123', + }, + { + type: 'users_select', + placeholder: { + type: 'plain_text', + text: 'Select a user', + emoji: true, + }, + initialUser: 'U123', + }, + { + type: 'channels_select', + placeholder: { + type: 'plain_text', + text: 'Select a channel', + emoji: true, + }, + initialChannel: 'C123', + }, + ], + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'actions', + props: { + key: 0, + children: [null, null, null], + block: true, + }, + }, + ]); + }); + + it('renders button', () => { + const payload = [ + { + type: 'actions', + elements: [ + { + type: 'button', + text: { + type: 'plain_text', + text: 'Click Me', + emoji: true, + }, + value: 'click_me_123', + }, + ], + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'actions', + props: { + key: 0, + children: [ + { + component: 'button', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'Click Me', + emoji: true, + block: false, + }, + }, + ], + value: 'click_me_123', + variant: 'normal', + block: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders datepicker', () => { + const payload = [ + { + type: 'actions', + elements: [ + { + type: 'datepicker', + initialDate: '1990-04-28', + placeholder: { + type: 'plain_text', + text: 'Select a date', + emoji: true, + }, + }, + { + type: 'datepicker', + initialDate: '1990-04-28', + placeholder: { + type: 'plain_text', + text: 'Select a date', + emoji: true, + }, + }, + ], + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'actions', + props: { + key: 0, + children: [ + { + component: 'input', + props: { + key: 0, + type: 'date', + defaultValue: '1990-04-28', + placeholder: { + component: 'text', + props: { + key: 0, + children: 'Select a date', + emoji: true, + block: false, + }, + }, + }, + }, + { + component: 'input', + props: { + key: 1, + type: 'date', + defaultValue: '1990-04-28', + placeholder: { + component: 'text', + props: { + key: 0, + children: 'Select a date', + emoji: true, + block: false, + }, + }, + }, + }, + ], + block: true, + }, + }, + ]); + }); +}); + +describe('context', () => { + it('renders plain text', () => { + const payload = [ + { + type: 'context', + elements: [ + { + type: 'plain_text', + text: 'Author: K A Applegate', + emoji: true, + }, + ], + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'context', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'Author: K A Applegate', + emoji: true, + block: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders mrkdwn', () => { + const payload = [ + { + type: 'context', + elements: [ + { + type: 'image', + imageUrl: + 'https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg', + altText: 'cute cat', + }, + { + type: 'mrkdwn', + text: '*Cat* has approved this message.', + }, + ], + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'context', + props: { + key: 0, + children: [ + { + component: 'image', + props: { + key: 0, + src: 'https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg', + alt: 'cute cat', + block: false, + }, + }, + { + component: 'markdown', + props: { + key: 1, + children: '*Cat* has approved this message.', + verbatim: false, + block: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders text and images', () => { + const payload = [ + { + type: 'context', + elements: [ + { + type: 'mrkdwn', + text: '*This* is :smile: markdown', + }, + { + type: 'image', + imageUrl: + 'https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg', + altText: 'cute cat', + }, + { + type: 'image', + imageUrl: + 'https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg', + altText: 'cute cat', + }, + { + type: 'image', + imageUrl: + 'https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg', + altText: 'cute cat', + }, + { + type: 'plain_text', + text: 'Author: K A Applegate', + emoji: true, + }, + ], + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'context', + props: { + key: 0, + children: [ + { + component: 'markdown', + props: { + key: 0, + children: '*This* is :smile: markdown', + verbatim: false, + block: false, + }, + }, + { + component: 'image', + props: { + key: 1, + src: 'https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg', + alt: 'cute cat', + block: false, + }, + }, + { + component: 'image', + props: { + key: 2, + src: 'https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg', + alt: 'cute cat', + block: false, + }, + }, + { + component: 'image', + props: { + key: 3, + src: 'https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg', + alt: 'cute cat', + block: false, + }, + }, + { + component: 'text', + props: { + key: 4, + children: 'Author: K A Applegate', + emoji: true, + block: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); +}); + +describe('input', () => { + it('renders multiline plain text input', () => { + const payload = [ + { + type: 'input', + element: { + type: 'plain_text_input', + multiline: true, + }, + label: { + type: 'plain_text', + text: 'Label', + emoji: true, + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'input-group', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'Label', + emoji: true, + block: false, + }, + }, + { + component: 'input', + props: { + key: 1, + type: 'text', + multiline: true, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders plain text input', () => { + const payload = [ + { + type: 'input', + element: { + type: 'plain_text_input', + }, + label: { + type: 'plain_text', + text: 'Label', + emoji: true, + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'input-group', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'Label', + emoji: true, + block: false, + }, + }, + { + component: 'input', + props: { + key: 1, + type: 'text', + multiline: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders multi users select', () => { + const payload = [ + { + type: 'input', + element: { + type: 'multi_users_select', + placeholder: { + type: 'plain_text', + text: 'Select users', + emoji: true, + }, + }, + label: { + type: 'plain_text', + text: 'Label', + emoji: true, + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'input-group', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'Label', + emoji: true, + block: false, + }, + }, + null, + ], + block: true, + }, + }, + ]); + }); + + it('renders static select', () => { + const payload = [ + { + type: 'input', + element: { + type: 'static_select', + placeholder: { + type: 'plain_text', + text: 'Select an item', + emoji: true, + }, + options: [ + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-0', + }, + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-1', + }, + { + text: { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true, + }, + value: 'value-2', + }, + ], + }, + label: { + type: 'plain_text', + text: 'Label', + emoji: true, + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'input-group', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'Label', + emoji: true, + block: false, + }, + }, + { + component: 'select', + props: { + key: 1, + children: [ + { + component: 'option', + props: { + key: 0, + children: { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + value: 'value-0', + }, + }, + { + component: 'option', + props: { + key: 1, + children: { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + value: 'value-1', + }, + }, + { + component: 'option', + props: { + key: 2, + children: { + component: 'text', + props: { + key: 0, + children: '*this is plain_text text*', + emoji: true, + block: false, + }, + }, + value: 'value-2', + }, + }, + ], + placeholder: { + component: 'text', + props: { + key: 0, + children: 'Select an item', + emoji: true, + block: false, + }, + }, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders datepicker', () => { + const payload = [ + { + type: 'input', + element: { + type: 'datepicker', + initialDate: '1990-04-28', + placeholder: { + type: 'plain_text', + text: 'Select a date', + emoji: true, + }, + }, + label: { + type: 'plain_text', + text: 'Label', + emoji: true, + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'input-group', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'Label', + emoji: true, + block: false, + }, + }, + { + component: 'input', + props: { + key: 1, + type: 'date', + defaultValue: '1990-04-28', + placeholder: { + component: 'text', + props: { + key: 0, + children: 'Select a date', + emoji: true, + block: false, + }, + }, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders linear scale', () => { + const payload = [ + { + type: 'input', + element: { + type: 'linear_scale', + maxValue: 2, + }, + label: { + type: 'plain_text', + text: 'Label', + emoji: true, + }, + }, + ]; + expect(parse(payload)).toStrictEqual([ + { + component: 'input-group', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'Label', + emoji: true, + block: false, + }, + }, + { + component: 'linear-scale', + props: { + key: 1, + children: [ + { + component: 'linear-scale-point', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: '0', + emoji: true, + block: false, + }, + }, + ], + }, + }, + { + component: 'linear-scale-point', + props: { + key: 1, + children: [ + { + component: 'text', + props: { + key: 0, + children: '1', + emoji: true, + block: false, + }, + }, + ], + }, + }, + { + component: 'linear-scale-point', + props: { + key: 2, + children: [ + { + component: 'text', + props: { + key: 0, + children: '2', + emoji: true, + block: false, + }, + }, + ], + }, + }, + ], + }, + }, + ], + block: true, + }, + }, + ]); + }); +}); + +describe('conditional', () => { + it('renders when conditions match', () => { + const blocks = [ + { + type: 'conditional', + when: { + engine: ['rocket.chat'], + }, + render: [ + { + type: 'section', + text: { + type: 'plain_text', + text: 'This is a plain text section block.', + emoji: true, + }, + }, + ], + }, + ]; + + expect(conditionalParse(blocks)).toStrictEqual([ + { + component: 'section', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'This is a plain text section block.', + emoji: true, + block: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('renders when no conditions are set', () => { + const blocks = [ + { + type: 'conditional', + when: { + engine: ['rocket.chat'], + }, + render: [ + { + type: 'section', + text: { + type: 'plain_text', + text: 'This is a plain text section block.', + emoji: true, + }, + }, + ], + }, + ]; + + expect(parse(blocks)).toStrictEqual([ + { + component: 'section', + props: { + key: 0, + children: [ + { + component: 'text', + props: { + key: 0, + children: 'This is a plain text section block.', + emoji: true, + block: false, + }, + }, + ], + block: true, + }, + }, + ]); + }); + + it('does not render when conditions match', () => { + const blocks = [ + { + type: 'conditional', + when: { + engine: ['livechat'], + }, + render: [ + { + type: 'section', + text: { + type: 'plain_text', + text: 'This is a plain text section block.', + emoji: true, + }, + }, + ], + }, + ]; + + expect(conditionalParse(blocks)).toStrictEqual([]); + }); +}); diff --git a/packages/ui-kit/src/rendering/surfaces/UiKitParserContextualBar.ts b/packages/ui-kit/src/rendering/surfaces/UiKitParserContextualBar.ts new file mode 100644 index 0000000000..abab07551b --- /dev/null +++ b/packages/ui-kit/src/rendering/surfaces/UiKitParserContextualBar.ts @@ -0,0 +1,25 @@ +import type { ActionsBlock } from '../../blocks/layout/ActionsBlock'; +import type { ContextBlock } from '../../blocks/layout/ContextBlock'; +import type { DividerBlock } from '../../blocks/layout/DividerBlock'; +import type { ImageBlock } from '../../blocks/layout/ImageBlock'; +import type { InputBlock } from '../../blocks/layout/InputBlock'; +import type { SectionBlock } from '../../blocks/layout/SectionBlock'; +import { SurfaceRenderer } from '../SurfaceRenderer'; + +type ContextualBarSurfaceLayoutBlock = + | ActionsBlock + | ContextBlock + | DividerBlock + | ImageBlock + | InputBlock + | SectionBlock; + +export abstract class UiKitParserContextualBar< + OutputElement +> extends SurfaceRenderer<OutputElement, ContextualBarSurfaceLayoutBlock> { + public constructor() { + super(['actions', 'context', 'divider', 'image', 'input', 'section']); + } +} + +export type ContextualBarSurfaceLayout = ContextualBarSurfaceLayoutBlock[]; diff --git a/packages/ui-kit/src/rendering/surfaces/UiKitParserMessage.ts b/packages/ui-kit/src/rendering/surfaces/UiKitParserMessage.ts index 9fcee3fa8b..739f7db5f3 100644 --- a/packages/ui-kit/src/rendering/surfaces/UiKitParserMessage.ts +++ b/packages/ui-kit/src/rendering/surfaces/UiKitParserMessage.ts @@ -1,4 +1,5 @@ import type { ActionsBlock } from '../../blocks/layout/ActionsBlock'; +import type { CalloutBlock } from '../../blocks/layout/CalloutBlock'; import type { ContextBlock } from '../../blocks/layout/ContextBlock'; import type { DividerBlock } from '../../blocks/layout/DividerBlock'; import type { ImageBlock } from '../../blocks/layout/ImageBlock'; @@ -14,7 +15,8 @@ type MessageSurfaceLayoutBlock = | ImageBlock | SectionBlock | VideoConferenceBlock - | PreviewBlock; + | PreviewBlock + | CalloutBlock; export abstract class UiKitParserMessage<OutputElement> extends SurfaceRenderer< OutputElement, @@ -29,6 +31,7 @@ export abstract class UiKitParserMessage<OutputElement> extends SurfaceRenderer< 'section', 'preview', 'video_conf', + 'callout', ]); } } diff --git a/packages/ui-kit/src/rendering/surfaces/UiKitParserModal.ts b/packages/ui-kit/src/rendering/surfaces/UiKitParserModal.ts index 119f11511d..d2993ce583 100644 --- a/packages/ui-kit/src/rendering/surfaces/UiKitParserModal.ts +++ b/packages/ui-kit/src/rendering/surfaces/UiKitParserModal.ts @@ -1,4 +1,5 @@ import type { ActionsBlock } from '../../blocks/layout/ActionsBlock'; +import type { CalloutBlock } from '../../blocks/layout/CalloutBlock'; import type { ContextBlock } from '../../blocks/layout/ContextBlock'; import type { DividerBlock } from '../../blocks/layout/DividerBlock'; import type { ImageBlock } from '../../blocks/layout/ImageBlock'; @@ -12,14 +13,23 @@ type ModalSurfaceLayoutBlock = | DividerBlock | ImageBlock | InputBlock - | SectionBlock; + | SectionBlock + | CalloutBlock; export abstract class UiKitParserModal<OutputElement> extends SurfaceRenderer< OutputElement, ModalSurfaceLayoutBlock > { public constructor() { - super(['actions', 'context', 'divider', 'image', 'input', 'section']); + super([ + 'actions', + 'context', + 'divider', + 'image', + 'input', + 'section', + 'callout', + ]); } } diff --git a/packages/ui-kit/src/rendering/surfaces/uiKitAttachment.ts b/packages/ui-kit/src/rendering/surfaces/uiKitAttachment.ts index 860b036ea0..abe6b576fe 100644 --- a/packages/ui-kit/src/rendering/surfaces/uiKitAttachment.ts +++ b/packages/ui-kit/src/rendering/surfaces/uiKitAttachment.ts @@ -1,7 +1,5 @@ import { createSurfaceRenderer } from '../createSurfaceRenderer'; import type { AttachmentSurfaceLayout } from './UiKitParserAttachment'; -export const uiKitAttachment = createSurfaceRenderer< - unknown, - AttachmentSurfaceLayout[number] ->(); +export const uiKitAttachment = + createSurfaceRenderer<AttachmentSurfaceLayout[number]>(); diff --git a/packages/ui-kit/src/rendering/surfaces/uiKitBanner.ts b/packages/ui-kit/src/rendering/surfaces/uiKitBanner.ts index b98907b328..590c36aa2e 100644 --- a/packages/ui-kit/src/rendering/surfaces/uiKitBanner.ts +++ b/packages/ui-kit/src/rendering/surfaces/uiKitBanner.ts @@ -1,7 +1,4 @@ import { createSurfaceRenderer } from '../createSurfaceRenderer'; import type { BannerSurfaceLayout } from './UiKitParserBanner'; -export const uiKitBanner = createSurfaceRenderer< - unknown, - BannerSurfaceLayout[number] ->(); +export const uiKitBanner = createSurfaceRenderer<BannerSurfaceLayout[number]>(); diff --git a/packages/ui-kit/src/rendering/surfaces/uiKitContextualBar.ts b/packages/ui-kit/src/rendering/surfaces/uiKitContextualBar.ts new file mode 100644 index 0000000000..f6312d9f19 --- /dev/null +++ b/packages/ui-kit/src/rendering/surfaces/uiKitContextualBar.ts @@ -0,0 +1,5 @@ +import { createSurfaceRenderer } from '../createSurfaceRenderer'; +import type { ContextualBarSurfaceLayout } from './UiKitParserContextualBar'; + +export const uiKitContextualBar = + createSurfaceRenderer<ContextualBarSurfaceLayout[number]>(); diff --git a/packages/ui-kit/src/rendering/surfaces/uiKitMessage.ts b/packages/ui-kit/src/rendering/surfaces/uiKitMessage.ts index 7b17adc40d..7cd741eb9a 100644 --- a/packages/ui-kit/src/rendering/surfaces/uiKitMessage.ts +++ b/packages/ui-kit/src/rendering/surfaces/uiKitMessage.ts @@ -1,7 +1,5 @@ import { createSurfaceRenderer } from '../createSurfaceRenderer'; import type { MessageSurfaceLayout } from './UiKitParserMessage'; -export const uiKitMessage = createSurfaceRenderer< - unknown, - MessageSurfaceLayout[number] ->(); +export const uiKitMessage = + createSurfaceRenderer<MessageSurfaceLayout[number]>(); diff --git a/packages/ui-kit/src/rendering/surfaces/uiKitModal.ts b/packages/ui-kit/src/rendering/surfaces/uiKitModal.ts index f55f845ee8..f8f51eadbc 100644 --- a/packages/ui-kit/src/rendering/surfaces/uiKitModal.ts +++ b/packages/ui-kit/src/rendering/surfaces/uiKitModal.ts @@ -1,7 +1,4 @@ import { createSurfaceRenderer } from '../createSurfaceRenderer'; import type { ModalSurfaceLayout } from './UiKitParserModal'; -export const uiKitModal = createSurfaceRenderer< - unknown, - ModalSurfaceLayout[number] ->(); +export const uiKitModal = createSurfaceRenderer<ModalSurfaceLayout[number]>(); diff --git a/packages/ui-kit/tsconfig.json b/packages/ui-kit/tsconfig.json index 2dab93babd..f173167a09 100644 --- a/packages/ui-kit/tsconfig.json +++ b/packages/ui-kit/tsconfig.json @@ -12,8 +12,7 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "node", - "resolveJsonModule": true, - "importsNotUsedAsValues": "error" + "resolveJsonModule": true }, "exclude": ["dist/", "src/**/*.spec.ts"] } diff --git a/packages/uikit-playground/.eslintignore b/packages/uikit-playground/.eslintignore deleted file mode 100644 index 70e2c25fce..0000000000 --- a/packages/uikit-playground/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -/dist -/build -/node_modules -/storybook-static -!/.jest -!/.storybook -/.storybook/jest-results.json diff --git a/packages/uikit-playground/.eslintrc.js b/packages/uikit-playground/.eslintrc.js deleted file mode 100644 index 0384b7a623..0000000000 --- a/packages/uikit-playground/.eslintrc.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = { - extends: ['@rocket.chat/eslint-config-alt/typescript'], - rules: { - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-use-before-define': 'off', - 'react/display-name': 'off', - 'react/no-multi-comp': 'off', - }, - env: { - jest: true, - }, - overrides: [ - { - files: ['*.mdx'], - extends: [ - '@rocket.chat/eslint-config-alt/react', - 'plugin:mdx/recommended', - ], - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - rules: { - 'new-cap': 'off', - 'prefer-arrow-callback': 'off', - 'semi': 'off', - }, - }, - ], -}; diff --git a/packages/uikit-playground/.gitignore b/packages/uikit-playground/.gitignore deleted file mode 100644 index 4d29575de8..0000000000 --- a/packages/uikit-playground/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/packages/uikit-playground/.prettierignore b/packages/uikit-playground/.prettierignore deleted file mode 100644 index 5cb8608252..0000000000 --- a/packages/uikit-playground/.prettierignore +++ /dev/null @@ -1,6 +0,0 @@ -/dist -/build -/node_modules -/storybook-static -/.storybook/jest-results.json -/**/*.mdx diff --git a/packages/uikit-playground/.prettierrc.js b/packages/uikit-playground/.prettierrc.js deleted file mode 100644 index b57f474edb..0000000000 --- a/packages/uikit-playground/.prettierrc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@rocket.chat/prettier-config/fuselage'); diff --git a/packages/uikit-playground/README.md b/packages/uikit-playground/README.md deleted file mode 100644 index b87cb00449..0000000000 --- a/packages/uikit-playground/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Getting Started with Create React App - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.\ -You will also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/packages/uikit-playground/package.json b/packages/uikit-playground/package.json deleted file mode 100644 index 15ca3f7391..0000000000 --- a/packages/uikit-playground/package.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "name": "@rocket.chat/uikit-playground", - "version": "0.31.22", - "homepage": "./", - "private": true, - "dependencies": { - "@codemirror/lang-javascript": "^6.1.2", - "@codemirror/lang-json": "^6.0.1", - "@codemirror/tooltip": "^0.19.16", - "@lezer/highlight": "^1.1.3", - "@rocket.chat/css-in-js": "^0.31.12", - "@rocket.chat/fuselage": "workspace:~", - "@rocket.chat/fuselage-hooks": "workspace:~", - "@rocket.chat/fuselage-polyfills": "workspace:~", - "@rocket.chat/fuselage-tokens": "workspace:~", - "@rocket.chat/fuselage-ui-kit": "workspace:~", - "@rocket.chat/icons": "workspace:~", - "@rocket.chat/logo": "workspace:~", - "@rocket.chat/styled": "workspace:~", - "@types/jest": "^27.5.2", - "@types/node": "^16.11.36", - "@types/react": "^17.0.53", - "@types/react-dom": "^17.0.18", - "codemirror": "^6.0.1", - "eslint4b-prebuilt": "^6.7.2", - "json5": "^2.2.3", - "react": "^17.0.2", - "react-beautiful-dnd": "^13.1.1", - "react-dom": "^17.0.2", - "react-router-dom": "^6.3.0", - "react-scripts": "5.0.1", - "react-split-pane": "^0.1.92", - "react-virtuoso": "~3.1.5", - "typescript": "~4.9.4", - "use-subscription": "^1.7.0", - "web-vitals": "^2.1.4", - "webpack": "^5.74.0" - }, - "scripts": { - "lint": "lint", - "lint-and-fix": "lint-and-fix", - "lint-staged": "lint-staged", - "predeploy": "npm run build", - "deploy": "gh-pages -d build", - "start": "react-scripts start", - "build": "react-scripts build", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "devDependencies": { - "@babel/eslint-parser": "^7.19.1", - "@rocket.chat/eslint-config-alt": "workspace:~", - "@types/react-beautiful-dnd": "^13.1.2", - "eslint": "~8.26.0", - "eslint-plugin-import": "^2.26.0", - "gh-pages": "^4.0.0", - "lint-all": "workspace:~", - "lint-staged": "~12.3.8", - "prettier": "~2.7.1" - }, - "postcss": { - "plugins": { - "postcss-plugin": {} - } - } -} diff --git a/packages/uikit-playground/public/favicon.ico b/packages/uikit-playground/public/favicon.ico deleted file mode 100644 index a11777cc47..0000000000 Binary files a/packages/uikit-playground/public/favicon.ico and /dev/null differ diff --git a/packages/uikit-playground/public/index.html b/packages/uikit-playground/public/index.html deleted file mode 100644 index aa069f27cb..0000000000 --- a/packages/uikit-playground/public/index.html +++ /dev/null @@ -1,43 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <meta name="theme-color" content="#000000" /> - <meta - name="description" - content="Web site created using create-react-app" - /> - <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> - <!-- - manifest.json provides metadata used when your web app is installed on a - user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ - --> - <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> - <!-- - Notice the use of %PUBLIC_URL% in the tags above. - It will be replaced with the URL of the `public` folder during the build. - Only files inside the `public` folder can be referenced from the HTML. - - Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will - work correctly both with client-side routing and a non-root public URL. - Learn how to configure a non-root public URL by running `npm run build`. - --> - <title>React App - - - -
- - - diff --git a/packages/uikit-playground/public/logo192.png b/packages/uikit-playground/public/logo192.png deleted file mode 100644 index fc44b0a379..0000000000 Binary files a/packages/uikit-playground/public/logo192.png and /dev/null differ diff --git a/packages/uikit-playground/public/logo512.png b/packages/uikit-playground/public/logo512.png deleted file mode 100644 index a4e47a6545..0000000000 Binary files a/packages/uikit-playground/public/logo512.png and /dev/null differ diff --git a/packages/uikit-playground/public/manifest.json b/packages/uikit-playground/public/manifest.json deleted file mode 100644 index 080d6c77ac..0000000000 --- a/packages/uikit-playground/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/packages/uikit-playground/public/robots.txt b/packages/uikit-playground/public/robots.txt deleted file mode 100644 index e9e57dc4d4..0000000000 --- a/packages/uikit-playground/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/packages/uikit-playground/src/App.css b/packages/uikit-playground/src/App.css deleted file mode 100644 index 10e4996c0f..0000000000 --- a/packages/uikit-playground/src/App.css +++ /dev/null @@ -1,39 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } -} diff --git a/packages/uikit-playground/src/App.tsx b/packages/uikit-playground/src/App.tsx deleted file mode 100644 index 750c4df479..0000000000 --- a/packages/uikit-playground/src/App.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import './App.css'; -import './cssVariables.css'; -import { BrowserRouter, Routes, Route } from 'react-router-dom'; - -import Playground from './Pages/Playground'; - -function App() { - return ( - - - } /> - } /> - - - ); -} - -export default App; diff --git a/packages/uikit-playground/src/Components/CodeEditor/Extensions/HighlightStyle.ts b/packages/uikit-playground/src/Components/CodeEditor/Extensions/HighlightStyle.ts deleted file mode 100644 index 3329c3c6a8..0000000000 --- a/packages/uikit-playground/src/Components/CodeEditor/Extensions/HighlightStyle.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'; -import { tags as t } from '@lezer/highlight'; - -const highLightStyle = () => { - const style = HighlightStyle.define([ - { tag: t.literal, color: 'var(--RCPG-primary-color)' }, - { tag: t.bool, color: 'var(--RCPG-tertary-color)' }, - { tag: t.number, color: 'var(--RCPG-secondary-color)' }, - ]); - - return syntaxHighlighting(style); -}; - -export default highLightStyle(); diff --git a/packages/uikit-playground/src/Components/CodeEditor/Extensions/basicSetup.ts b/packages/uikit-playground/src/Components/CodeEditor/Extensions/basicSetup.ts deleted file mode 100644 index e5973a7922..0000000000 --- a/packages/uikit-playground/src/Components/CodeEditor/Extensions/basicSetup.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - completionKeymap, - closeBrackets, - closeBracketsKeymap, -} from '@codemirror/autocomplete'; -import { - defaultKeymap, - history, - historyKeymap, - indentWithTab, -} from '@codemirror/commands'; -import { - defaultHighlightStyle, - syntaxHighlighting, - indentOnInput, - bracketMatching, - foldGutter, - foldKeymap, -} from '@codemirror/language'; -import { lintKeymap } from '@codemirror/lint'; -import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; -import type { Extension } from '@codemirror/state'; -import { - keymap, - drawSelection, - dropCursor, - rectangularSelection, - crosshairCursor, - lineNumbers, - EditorView, -} from '@codemirror/view'; - -const basicSetup: Extension = (() => [ - lineNumbers(), - history(), - foldGutter(), - drawSelection(), - dropCursor(), - indentOnInput(), - EditorView.lineWrapping, - syntaxHighlighting(defaultHighlightStyle, { fallback: true }), - bracketMatching(), - closeBrackets(), - rectangularSelection(), - crosshairCursor(), - highlightSelectionMatches(), - keymap.of([ - ...closeBracketsKeymap, - ...defaultKeymap, - ...searchKeymap, - ...historyKeymap, - ...foldKeymap, - ...completionKeymap, - ...lintKeymap, - indentWithTab, - ]), -])(); - -export default basicSetup; diff --git a/packages/uikit-playground/src/Components/CodeEditor/Extensions/index.ts b/packages/uikit-playground/src/Components/CodeEditor/Extensions/index.ts deleted file mode 100644 index 9dda5b45b5..0000000000 --- a/packages/uikit-playground/src/Components/CodeEditor/Extensions/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { javascript } from '@codemirror/lang-javascript'; - -import highlightStyle from './HighlightStyle'; -import basicSetup from './basicSetup'; -import lint from './lint'; -import theme from './theme'; - -const extensions = [highlightStyle, javascript(), lint, basicSetup, ...theme]; - -export default extensions; diff --git a/packages/uikit-playground/src/Components/CodeEditor/Extensions/lint.ts b/packages/uikit-playground/src/Components/CodeEditor/Extensions/lint.ts deleted file mode 100644 index d8eb7870f0..0000000000 --- a/packages/uikit-playground/src/Components/CodeEditor/Extensions/lint.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { esLint } from '@codemirror/lang-javascript'; -import { lintGutter, linter } from '@codemirror/lint'; -import Linter from 'eslint4b-prebuilt'; - -export default [lintGutter(), linter(esLint(new Linter()))]; diff --git a/packages/uikit-playground/src/Components/CodeEditor/Extensions/theme.ts b/packages/uikit-playground/src/Components/CodeEditor/Extensions/theme.ts deleted file mode 100644 index 71938add61..0000000000 --- a/packages/uikit-playground/src/Components/CodeEditor/Extensions/theme.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Extension } from '@codemirror/state'; -import { EditorView } from '@codemirror/view'; - -const gutters: Extension = EditorView.theme({ - '.cm-gutters': { - backgroundColor: 'transparent', - border: 'none', - userSelect: 'none', - minWidth: '32px', - display: 'flex', - justifyContent: 'flex-end', - }, - - '.cm-activeLineGutter': { - backgroundColor: 'transparent', - }, -}); - -const selection: Extension = EditorView.theme({ - '.cm-selectionBackground': { - backgroundColor: 'var(--RCPG-secondary-color) !important', - opacity: 0.3, - }, - - '.cm-selectionMatch': { - backgroundColor: '#74808930 !important', - }, - - '.cm-matchingBracket': { - backgroundColor: 'transparent !important', - border: '1px solid #1d74f580', - }, -}); - -const line: Extension = EditorView.theme({ - '.cm-activeLine': { - backgroundColor: 'transparent !important', - }, -}); - -export default [gutters, selection, line] as const; diff --git a/packages/uikit-playground/src/Components/CodeEditor/index.tsx b/packages/uikit-playground/src/Components/CodeEditor/index.tsx deleted file mode 100644 index bb56002376..0000000000 --- a/packages/uikit-playground/src/Components/CodeEditor/index.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import type { Extension } from '@codemirror/state'; -import { Box } from '@rocket.chat/fuselage'; -import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import json5 from 'json5'; -import { useEffect, useContext } from 'react'; - -import { docAction, context } from '../../Context'; -import useCodeMirror from '../../hooks/useCodeMirror'; -import codePrettier from '../../utils/codePrettier'; - -type CodeMirrorProps = { - extensions?: Extension[]; -}; - -const CodeEditor = ({ extensions }: CodeMirrorProps) => { - const { state, dispatch } = useContext(context); - const { editor, changes, setValue } = useCodeMirror( - extensions, - json5.stringify(state.doc.payload, undefined, 4) - ); - const debounceValue = useDebouncedValue(changes?.value, 1500); - - useEffect(() => { - if (!changes?.isDispatch) { - try { - const parsedCode = json5.parse(changes.value); - dispatch( - docAction({ - payload: parsedCode, - changedByEditor: false, - }) - ); - - dispatch(docAction({ payload: parsedCode })); - } catch (e) { - console.log(e); - // do nothing - } - } - }, [changes?.value]); - - useEffect(() => { - if (!changes?.isDispatch) { - try { - const prettierCode = codePrettier(changes.value, changes.cursor); - setValue(prettierCode.formatted, { - cursor: prettierCode.cursorOffset, - }); - } catch (e) { - // do nothing - } - } - }, [debounceValue]); - - useEffect(() => { - if (!state.doc.changedByEditor) { - setValue(JSON.stringify(state.doc.payload, undefined, 4), {}); - } - }, [state.doc.payload]); - - return ( - <> - - - ); -}; - -export default CodeEditor; diff --git a/packages/uikit-playground/src/Components/ComponentSideBar/ScrollableSideBar.tsx b/packages/uikit-playground/src/Components/ComponentSideBar/ScrollableSideBar.tsx deleted file mode 100644 index d0cef58652..0000000000 --- a/packages/uikit-playground/src/Components/ComponentSideBar/ScrollableSideBar.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { css } from '@rocket.chat/css-in-js'; -import { Scrollable, Box } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; -import React from 'react'; - -import BlocksTree from '../../Payload/BlocksTree'; -import DropDown from '../DropDown'; - -const ScrollableSideBar: FC = () => ( - - - - - -); - -export default ScrollableSideBar; diff --git a/packages/uikit-playground/src/Components/ComponentSideBar/SideBar.tsx b/packages/uikit-playground/src/Components/ComponentSideBar/SideBar.tsx deleted file mode 100644 index ef58fbc732..0000000000 --- a/packages/uikit-playground/src/Components/ComponentSideBar/SideBar.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { css } from '@rocket.chat/css-in-js'; -import { Box } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; -import React, { useEffect, useContext } from 'react'; - -import { context, sidebarToggleAction } from '../../Context'; -import ScrollableSideBar from './ScrollableSideBar'; -import SliderBtn from './SliderBtn'; - -const SideBar: FC = () => { - const { state, dispatch } = useContext(context); - - useEffect(() => { - dispatch(sidebarToggleAction(false)); - }, [state?.isMobile, dispatch]); - - const slide = state?.isMobile - ? css` - width: 100%; - user-select: none; - transform: translateX(${state?.sideBarToggle ? '0' : '-100%'}); - transition: var(--animation-default); - ` - : css` - width: var(--sidebar-width); - user-select: none; - transition: var(--animation-default); - `; - - return ( - - - - - ); -}; - -export default SideBar; diff --git a/packages/uikit-playground/src/Components/ComponentSideBar/SliderBtn.tsx b/packages/uikit-playground/src/Components/ComponentSideBar/SliderBtn.tsx deleted file mode 100644 index 0f13639581..0000000000 --- a/packages/uikit-playground/src/Components/ComponentSideBar/SliderBtn.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { css } from '@rocket.chat/css-in-js'; -import { Box, Label } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; -import React, { useContext } from 'react'; - -import { context, sidebarToggleAction } from '../../Context'; - -const SliderBtn: FC = () => { - const { - state: { sideBarToggle, isMobile }, - dispatch, - } = useContext(context); - const slideBtnAnimation = sideBarToggle - ? css` - clip-path: polygon( - 10% 0, - 50% 40%, - 90% 0, - 100% 10%, - 60% 50%, - 100% 90%, - 90% 100%, - 50% 60%, - 10% 100%, - 0 90%, - 40% 50%, - 0 10% - ); - cursor: pointer; - transition: var(--animation-default); - ` - : css` - clip-path: polygon( - 32% 35%, - 32% 35%, - 79% 0, - 87% 10%, - 32% 50%, - 87% 90%, - 79% 100%, - 32% 64%, - 32% 65%, - 13% 50%, - 13% 50%, - 13% 50% - ); - transform: rotate(180deg); - transition: var(--animation-default); - `; - - // eslint-disable-next-line no-nested-ternary - const toggleStyle = !isMobile - ? css` - left: 0px; - ` - : sideBarToggle - ? css` - right: 0; - transition: var(--animation-default); - ` - : css` - right: 0; - transform: translateX(100%); - cursor: pointer; - transition: var(--animation-default); - `; - - return ( - - !sideBarToggle && dispatch(sidebarToggleAction(!sideBarToggle)) - } - zIndex={1} - className={toggleStyle} - > - - {isMobile && ( - - sideBarToggle && dispatch(sidebarToggleAction(!sideBarToggle)) - } - className={css` - cursor: pointer; - `} - > - - - )} - - ); -}; - -export default SliderBtn; diff --git a/packages/uikit-playground/src/Components/ComponentSideBar/index.tsx b/packages/uikit-playground/src/Components/ComponentSideBar/index.tsx deleted file mode 100644 index c90236635b..0000000000 --- a/packages/uikit-playground/src/Components/ComponentSideBar/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SideBar'; diff --git a/packages/uikit-playground/src/Components/Draggable/DraggableList.tsx b/packages/uikit-playground/src/Components/Draggable/DraggableList.tsx deleted file mode 100644 index 76e7af1553..0000000000 --- a/packages/uikit-playground/src/Components/Draggable/DraggableList.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import type { LayoutBlock } from '@rocket.chat/ui-kit'; -import * as React from 'react'; -import type { OnDragEndResponder } from 'react-beautiful-dnd'; -import { DragDropContext, Droppable } from 'react-beautiful-dnd'; - -import DraggableListItem from './DraggableListItem'; - -export type Block = { - id: string; - payload: LayoutBlock; -}; - -export type DraggableListProps = { - blocks: Block[]; - surface?: number; - onDragEnd: OnDragEndResponder; -}; - -const DraggableList = React.memo( - ({ blocks, surface, onDragEnd }: DraggableListProps) => ( - <> - - <> - - {(provided) => ( -
- <> - {blocks.map((block, index) => ( - - ))} - {provided.placeholder} - -
- )} -
- -
- - ) -); - -export default DraggableList; diff --git a/packages/uikit-playground/src/Components/Draggable/DraggableListItem.tsx b/packages/uikit-playground/src/Components/Draggable/DraggableListItem.tsx deleted file mode 100644 index 7751c295a4..0000000000 --- a/packages/uikit-playground/src/Components/Draggable/DraggableListItem.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as React from 'react'; -import { Draggable } from 'react-beautiful-dnd'; - -import RenderPayload from '../Preview/Display/RenderPayload/RenderPayload'; -import type { Block } from './DraggableList'; - -export type DraggableListItemProps = { - block: Block; - surface: number; - index: number; -}; - -const DraggableListItem = ({ - block, - surface, - index, -}: DraggableListItemProps) => ( - - {(provided) => ( -
- -
- )} -
-); - -export default DraggableListItem; diff --git a/packages/uikit-playground/src/Components/DropDown/DropDown.tsx b/packages/uikit-playground/src/Components/DropDown/DropDown.tsx deleted file mode 100644 index 33984572c1..0000000000 --- a/packages/uikit-playground/src/Components/DropDown/DropDown.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import React, { Fragment } from 'react'; - -import Items from './Items'; -import type { Item, ItemBranch } from './types'; - -interface DropDownProps { - readonly BlocksTree: Item; -} - -const DropDown = ({ BlocksTree }: DropDownProps) => { - const layer = 1; - - const recursiveComponentTree = (branch: ItemBranch, layer: number) => ( - - {branch.branches && - branch.branches.map((branch: ItemBranch, index: number) => ( - - {recursiveComponentTree(branch, layer + 1)} - - ))} - - ); - - return ( - - {BlocksTree.map((branch: ItemBranch, i: number) => ( - {recursiveComponentTree(branch, layer)} - ))} - - ); -}; - -export default DropDown; diff --git a/packages/uikit-playground/src/Components/DropDown/Items.tsx b/packages/uikit-playground/src/Components/DropDown/Items.tsx deleted file mode 100644 index c9f04995c0..0000000000 --- a/packages/uikit-playground/src/Components/DropDown/Items.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { css } from '@rocket.chat/css-in-js'; -import { Box, Label, Chevron } from '@rocket.chat/fuselage'; -import React, { useState, useContext } from 'react'; - -import { context, docAction } from '../../Context'; -import ItemsIcon from './ItemsIcon'; -import { itemStyle, labelStyle } from './itemsStyle'; -import type { ItemProps } from './types'; - -const Items = ({ label, children, layer, payload }: ItemProps) => { - const [isOpen, toggleItemOpen] = useState(layer === 1); - const [hover, setHover] = useState(false); - const { state, dispatch } = useContext(context); - - const itemClickHandler = () => { - toggleItemOpen(!isOpen); - payload && - dispatch( - docAction({ - payload: [...state.doc.payload, payload[0]], - changedByEditor: false, - }) - ); - }; - - return ( - - setHover(true)} - onMouseLeave={() => setHover(false)} - onClick={itemClickHandler} - > - - {children && children.length > 0 && ( - - - - )} - - - - - - - {isOpen && children} - - ); -}; - -export default Items; diff --git a/packages/uikit-playground/src/Components/DropDown/ItemsIcon.tsx b/packages/uikit-playground/src/Components/DropDown/ItemsIcon.tsx deleted file mode 100644 index 39d305292e..0000000000 --- a/packages/uikit-playground/src/Components/DropDown/ItemsIcon.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Icon } from '@rocket.chat/fuselage'; -import React from 'react'; - -const ItemsIcon = ({ - layer, - lastNode, - hover, -}: { - layer: number; - lastNode: boolean; - hover: boolean; -}) => { - const selectIcon = (layer: number, hover: boolean) => { - if (layer === 1) { - return ( - - ); - } - if (lastNode) { - return ; - } - return ; - }; - return <>{selectIcon(layer, hover)}; -}; - -export default ItemsIcon; diff --git a/packages/uikit-playground/src/Components/DropDown/index.tsx b/packages/uikit-playground/src/Components/DropDown/index.tsx deleted file mode 100644 index d76df1b0ff..0000000000 --- a/packages/uikit-playground/src/Components/DropDown/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './DropDown'; diff --git a/packages/uikit-playground/src/Components/DropDown/itemsStyle.ts b/packages/uikit-playground/src/Components/DropDown/itemsStyle.ts deleted file mode 100644 index da9c9ff0b2..0000000000 --- a/packages/uikit-playground/src/Components/DropDown/itemsStyle.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { css } from '@rocket.chat/css-in-js'; - -export const itemStyle = (layer: number, hover: boolean) => { - const style = css` - cursor: pointer; - padding-left: ${10 + (layer - 1) * 16}px; - background-color: ${hover ? 'var(--RCPG-primary-color)' : 'transparent'}; - `; - return style; -}; - -export const labelStyle = (layer: number, hover: boolean) => { - let customStyle; - const basicStyle = css` - cursor: pointer !important; - padding-left: 4px !important; - `; - switch (layer) { - case 1: - customStyle = css` - font-weight: 700; - font-size: 14px; - letter-spacing: 0.3px; - color: ${hover ? '#fff' : '#999'}; - text-transform: uppercase; - `; - break; - case 2: - customStyle = css` - letter-spacing: 0.1px; - font-size: 12px; - color: ${hover ? '#fff' : '#555'}; - text-transform: capitalize; - `; - break; - default: - customStyle = css` - font-size: 12px; - color: ${hover ? '#fff' : '#555'}; - text-transform: capitalize; - `; - break; - } - return [customStyle, basicStyle]; -}; diff --git a/packages/uikit-playground/src/Components/DropDown/types.ts b/packages/uikit-playground/src/Components/DropDown/types.ts deleted file mode 100644 index 3b0c505d38..0000000000 --- a/packages/uikit-playground/src/Components/DropDown/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { LayoutBlock } from '@rocket.chat/ui-kit'; - -export type ItemProps = { - label: string; - layer: number; - payload?: readonly LayoutBlock[]; - children?: ReadonlyArray; -}; - -export type ItemBranch = { - label: string; - branches?: Item; - payload?: readonly LayoutBlock[]; -}; - -export type Item = ItemBranch[]; diff --git a/packages/uikit-playground/src/Components/NavBar/BurgerIcon/BurgerIcon.tsx b/packages/uikit-playground/src/Components/NavBar/BurgerIcon/BurgerIcon.tsx deleted file mode 100644 index 4c1409153c..0000000000 --- a/packages/uikit-playground/src/Components/NavBar/BurgerIcon/BurgerIcon.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { usePrefersReducedMotion } from '@rocket.chat/fuselage-hooks'; -import type { ReactElement, ReactNode } from 'react'; -import React, { useContext } from 'react'; - -import { context } from '../../../Context'; -import Line from './Line'; -import Wrapper from './Wrapper'; - -const BurgerIcon = ({ children }: { children?: ReactNode }): ReactElement => { - const isReducedMotionPreferred = usePrefersReducedMotion(); - const { - state: { navMenuToggle }, - } = useContext(context); - - return ( - - - - - {children} - - ); -}; - -export default BurgerIcon; diff --git a/packages/uikit-playground/src/Components/NavBar/BurgerIcon/Line.tsx b/packages/uikit-playground/src/Components/NavBar/BurgerIcon/Line.tsx deleted file mode 100644 index d970bf55ba..0000000000 --- a/packages/uikit-playground/src/Components/NavBar/BurgerIcon/Line.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { css } from '@rocket.chat/css-in-js'; -import { Box } from '@rocket.chat/fuselage'; -import type { ReactElement } from 'react'; -import React from 'react'; - -const Line = ({ - animated, - moved, -}: { - animated: boolean; - moved?: boolean; -}): ReactElement => { - const animatedStyle = animated - ? css` - will-change: transform; - transition: transform 0.1s ease-out; - ` - : ''; - - const movedStyle = moved - ? css` - &:nth-child(1), - &:nth-child(3) { - transform-origin: 50%, 50%, 0; - } - &:nth-child(1) { - transform: translate(-25%, 3px) rotate(-45deg) scale(0.5, 1); - } - [dir='rtl'] &:nth-child(1) { - transform: translate(25%, 3px) rotate(45deg) scale(0.5, 1); - } - &:nth-child(3) { - transform: translate(-25%, -3px) rotate(45deg) scale(0.5, 1); - } - [dir='rtl'] &:nth-child(3) { - transform: translate(25%, -3px) rotate(-45deg) scale(0.5, 1); - } - ` - : ''; - - return ( -