From 11690aa9ceb98ea742921ceba761ce6ad9a5c37f Mon Sep 17 00:00:00 2001 From: Marlo Longley Date: Thu, 9 Jan 2025 11:53:38 -0500 Subject: [PATCH 1/3] Convert syntax to match eslint rules from Mirador core Prefer functional component arguments to defaultProps --- .eslintrc | 92 +++++++++++++++++--- __tests__/MiradorImageTools.test.js | 2 +- demo/src/index.js | 10 +-- src/plugins/ImageFlip.js | 2 +- src/plugins/ImageTool.js | 106 ++++++++--------------- src/plugins/MiradorImageTools.js | 105 +++++++--------------- src/plugins/MiradorImageToolsMenuItem.js | 10 +-- src/translations.js | 44 +++++----- 8 files changed, 187 insertions(+), 184 deletions(-) diff --git a/.eslintrc b/.eslintrc index 42cdf4f..5f0aed4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,19 +1,91 @@ { - "env": {}, - "extends": ["airbnb", "plugin:jest/recommended"], + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "extends": [ + "airbnb", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:testing-library/react" + ], "globals": { - "document": true + "page": true, + "document": true, + "vi": true }, - "parser": "@babel/eslint-parser", - "plugins": ["babel", "jest", "react", "react-hooks"], + "plugins": [ + "react", + "react-hooks", + "testing-library" + ], "rules": { + "import/no-unresolved": [ + 2, { "ignore": ["test-utils"] } + ], "import/prefer-default-export": "off", - "import/no-extraneous-dependencies": "off", "no-console": "off", + "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }], + "no-unused-vars": "off", + "no-undef": "off", + "no-restricted-syntax": ["warn", "WithStatement"], + "no-restricted-globals": ["error"], + "eqeqeq": ["warn", "smart"], + "no-use-before-define": [ + "warn", + { + "functions": false, + "classes": false, + "variables": false + }, + ], + "no-mixed-operators": [ + "warn", + { + "groups": [ + ["&", "|", "^", "~", "<<", ">>", ">>>"], + ["==", "!=", "===", "!==", ">", ">=", "<", "<="], + ["&&", "||"], + ["in", "instanceof"], + ], + "allowSamePrecedence": false, + }, + ], "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], - "react/jsx-fragments": "off", - "react/jsx-props-no-spreading": "off", + "no-underscore-dangle": "off", "react/prefer-stateless-function": "off", - "react/function-component-definition": "off" + "sort-keys": ["error", "asc", { + "caseSensitive": false, + "natural": false + }], + "react/jsx-props-no-spreading": "off", + "react/function-component-definition": "off", + "default-param-last": "off", + "arrow-parens": "off", + "import/no-anonymous-default-export": "off", + "import/no-extraneous-dependencies": "off", + "max-len": ["error", { + "code": 120, + "ignoreComments": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true, + "ignoreRegExpLiterals": true + }], + "react/jsx-uses-react": "off", + "react/react-in-jsx-scope": "off", + "react/require-default-props": [2, { + "functions": "defaultArguments" + }], + "react-hooks/exhaustive-deps": "error", + "testing-library/render-result-naming-convention": "off", + "testing-library/no-render-in-lifecycle": [ + "error", + { + "allowTestingFrameworkSetupHook": "beforeEach" + } + ] } -} +} \ No newline at end of file diff --git a/__tests__/MiradorImageTools.test.js b/__tests__/MiradorImageTools.test.js index e509137..7b5e286 100644 --- a/__tests__/MiradorImageTools.test.js +++ b/__tests__/MiradorImageTools.test.js @@ -4,8 +4,8 @@ import { TestableImageTools as MiradorImageTools } from '../src/plugins/MiradorI const mockPalette = { palette: { - shades: { main: 'rgb(255, 255, 255)' }, getContrastText: () => 'rgb(0, 0, 0)', + shades: { main: 'rgb(255, 255, 255)' }, }, }; diff --git a/demo/src/index.js b/demo/src/index.js index 14231c0..7a1f550 100644 --- a/demo/src/index.js +++ b/demo/src/index.js @@ -3,11 +3,6 @@ import { miradorImageToolsPlugin } from '../../src'; const config = { id: 'demo', - windows: [{ - imageToolsEnabled: true, - imageToolsOpen: true, - manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', - }], theme: { palette: { primary: { @@ -15,6 +10,11 @@ const config = { }, }, }, + windows: [{ + imageToolsEnabled: true, + imageToolsOpen: true, + manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', + }], }; Mirador.viewer(config, [ diff --git a/src/plugins/ImageFlip.js b/src/plugins/ImageFlip.js index d13cdef..80f1eb1 100644 --- a/src/plugins/ImageFlip.js +++ b/src/plugins/ImageFlip.js @@ -24,8 +24,8 @@ export default class ImageFlip extends Component { ImageFlip.propTypes = { backgroundColor: PropTypes.string, - foregroundColor: PropTypes.string, flipped: PropTypes.bool.isRequired, + foregroundColor: PropTypes.string, label: PropTypes.string.isRequired, }; diff --git a/src/plugins/ImageTool.js b/src/plugins/ImageTool.js index 22d57df..a0d8fbb 100644 --- a/src/plugins/ImageTool.js +++ b/src/plugins/ImageTool.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { MiradorMenuButton } from 'mirador'; import Slider from '@mui/material/Slider'; @@ -7,22 +7,22 @@ import { styled, alpha } from '@mui/material/styles'; const SliderContainer = styled('div')(({ small, theme: { palette } }) => ({ backgroundColor: alpha(palette.shades.main, 0.8), borderRadius: 25, - top: 48, - marginTop: 2, - position: 'absolute', height: 150, - zIndex: 100, marginLeft: 2, + marginTop: 2, padding: [[2, 7, 2, 7]], + position: 'absolute', ...(small && { - top: 'auto', - right: 48, - width: 150, height: 'auto', - marginTop: -46, marginBottom: 2, + marginTop: -46, padding: [[4, 2, 4, 2]], + right: 48, + top: 'auto', + width: 150, }), + top: 48, + zIndex: 100, })); const ImageToolToggleButton = styled(MiradorMenuButton)(({ @@ -37,58 +37,38 @@ const ImageToolToggleButton = styled(MiradorMenuButton)(({ }), })); -class ImageTool extends Component { - constructor(props) { - super(props); - this.state = { - open: props.open, - }; - this.handleChange = this.handleChange.bind(this); - this.handleClick = this.handleClick.bind(this); - } +const ImageTool = ({ + children, label, max = 100, min = 0, value, type, variant = 'slider', windowId, small = false, onChange, +}) => { + const [open, setOpen] = useState(false); - handleClick() { - const { value, variant } = this.props; + const handleClick = () => { switch (variant) { case 'toggle': - this.handleChange({}, value === 0 ? 100 : 0); + onChange(value === 0 ? 100 : 0); break; default: - this.setState((state) => ({ - open: !state.open, - })); + setOpen((prevState) => !prevState); } - } - - handleChange(e, val) { - const { onChange } = this.props; - onChange(val); - } - - render() { - const { - children, label, max, min, value, type, variant, windowId, small, - } = this.props; - const { open } = this.state; - - const toggled = variant === 'toggle' && value > 0; + }; - const id = `${windowId}-${type}`; + const toggled = variant === 'toggle' && value > 0; + const id = `${windowId}-${type}`; - return ( -
- - {children} - + return ( +
+ + {children} + - {open && ( + {open && ( onChange(val)} /> - )} -
- ); - } -} + )} +
+ ); +}; ImageTool.propTypes = { children: PropTypes.node.isRequired, label: PropTypes.string.isRequired, - min: PropTypes.number, max: PropTypes.number, + min: PropTypes.number, onChange: PropTypes.func.isRequired, - open: PropTypes.bool, small: PropTypes.bool, type: PropTypes.string.isRequired, value: PropTypes.number.isRequired, @@ -123,12 +101,4 @@ ImageTool.propTypes = { windowId: PropTypes.string.isRequired, }; -ImageTool.defaultProps = { - min: 0, - max: 100, - open: false, - small: false, - variant: 'slider', -}; - export default ImageTool; diff --git a/src/plugins/MiradorImageTools.js b/src/plugins/MiradorImageTools.js index 6d6751c..844a6ef 100644 --- a/src/plugins/MiradorImageTools.js +++ b/src/plugins/MiradorImageTools.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import BrightnessIcon from '@mui/icons-material/Brightness5'; import TonalityIcon from '@mui/icons-material/Tonality'; @@ -20,17 +20,6 @@ const SizeContainer = styled('div')(() => ({ position: 'static !important', })); -const ToggleContainer = styled('div')(() => ({ - border: 0, - borderImageSlice: 1, -})); - -const ToolContainer = styled('div')(() => ({ - border: 0, - borderImageSlice: 1, - display: 'flex', -})); - /** Styles for withStyles HOC */ const Root = styled('div')(({ small, theme: { palette } }) => { const backgroundColor = palette.shades.main; @@ -44,45 +33,28 @@ const Root = styled('div')(({ small, theme: { palette } }) => { const borderImageBottom = borderImageRight.replace('to bottom', 'to right'); return { backgroundColor: alpha(backgroundColor, 0.8), + borderBottom: small ? border : 'none', + borderImageSource: small ? borderImageBottom : borderImageRight, borderRadius: 25, + display: 'flex', + flexDirection: 'row', position: 'absolute', - top: 8, right: 8, + top: 8, zIndex: 999, - display: 'flex', - flexDirection: 'row', ...(small && { flexDirection: 'column' }), - [ToggleContainer]: { - ...(small && { - borderBottom: border, - borderImageSource: borderImageBottom, - display: 'flex', - }), - }, - [ToolContainer]: { - ...(!small && { - borderRight: border, - borderImageSource: borderImageRight, - flexDirection: 'row', - }), - ...(small && { - flexDirection: 'column', - borderBottom: border, - borderImageSource: borderImageBottom, - }), - }, }; }); -const MiradorImageTools = (({ - enabled, - innerRef, - open, +const MiradorImageTools = ({ + enabled = true, + open = true, + innerRef = null, t, updateViewport, updateWindow, - viewer, - viewConfig, + viewer = {}, + viewConfig = {}, windowId, }) => { const [isSmallDisplay, setIsSmallDisplay] = useState(false); @@ -102,18 +74,19 @@ const MiradorImageTools = (({ const handleReset = () => { const viewConfigReset = { - rotation: 0, - flip: false, brightness: 100, contrast: 100, - saturate: 100, + flip: false, grayscale: 0, invert: 0, + rotation: 0, + saturate: 100, }; updateViewport(windowId, viewConfigReset); }; - const applyFilters = () => { + // Wrap applyFilters in useCallback to prevent unnecessary re-renders + const applyFilters = useCallback(() => { const { canvas } = viewer || {}; if (!canvas) return; @@ -129,7 +102,7 @@ const MiradorImageTools = (({ newFilters.push(`grayscale(${grayscale}%)`); newFilters.push(`invert(${invert}%)`); canvas.style.filter = newFilters.join(' '); - }; + }, [viewer, brightness, contrast, saturate, grayscale, invert]); const toggleState = () => { updateWindow(windowId, { imageToolsOpen: !open }); @@ -152,12 +125,12 @@ const MiradorImageTools = (({ useEffect(() => { if (viewer) applyFilters(); - }, [viewer, viewConfig]); + }, [applyFilters, viewer]); if (!viewer || !enabled) return ; const toggleButton = ( - +
{open ? : } - +
); return ( @@ -174,8 +147,8 @@ const MiradorImageTools = (({ {isSmallDisplay && toggleButton} {open && ( - - + <> +
toggleRotate(90)} @@ -187,12 +160,12 @@ const MiradorImageTools = (({ variant="left" /> - - +
+
- - +
+
- - +
+ )} {!isSmallDisplay && toggleButton}
); -}); +}; MiradorImageTools.propTypes = { enabled: PropTypes.bool, innerRef: PropTypes.oneOfType([ PropTypes.func, PropTypes.any, - ]).isRequired, + ]), open: PropTypes.bool, - size: PropTypes.shape({ - width: PropTypes.number, - height: PropTypes.number, - }).isRequired, t: PropTypes.func.isRequired, updateViewport: PropTypes.func.isRequired, updateWindow: PropTypes.func.isRequired, - viewer: PropTypes.object, // eslint-disable-line react/forbid-prop-types viewConfig: PropTypes.object, // eslint-disable-line react/forbid-prop-types + viewer: PropTypes.object, // eslint-disable-line react/forbid-prop-types windowId: PropTypes.string.isRequired, }; -MiradorImageTools.defaultProps = { - enabled: true, - open: true, - viewer: undefined, - viewConfig: {}, -}; - -// Export without wrapping HOC for testing. export const TestableImageTools = MiradorImageTools; export default MiradorImageTools; diff --git a/src/plugins/MiradorImageToolsMenuItem.js b/src/plugins/MiradorImageToolsMenuItem.js index 24053cb..a1561bd 100644 --- a/src/plugins/MiradorImageToolsMenuItem.js +++ b/src/plugins/MiradorImageToolsMenuItem.js @@ -6,7 +6,11 @@ import ListItemText from '@mui/material/ListItemText'; import TuneSharpIcon from '@mui/icons-material/TuneSharp'; const MiradorImageToolsMenuItem = ({ - enabled, handleClose, t, updateWindow, windowId, + enabled = true, + handleClose, + t, + updateWindow, + windowId, }) => { const handleClickOpen = () => { handleClose(); @@ -33,8 +37,4 @@ MiradorImageToolsMenuItem.propTypes = { windowId: PropTypes.string.isRequired, }; -MiradorImageToolsMenuItem.defaultProps = { - enabled: true, -}; - export default MiradorImageToolsMenuItem; diff --git a/src/translations.js b/src/translations.js index 7ba92fe..b62f549 100644 --- a/src/translations.js +++ b/src/translations.js @@ -1,8 +1,8 @@ const translations = { de: { brightness: 'Helligkeit', - collapse_open: 'Bildbearbeitung ausblenden', collapse_close: 'Bildbearbeitung einblenden', + collapse_open: 'Bildbearbeitung ausblenden', contrast: 'Kontrast', flip: 'Spiegeln', greyscale: 'Darstellung in Graustufen', @@ -16,8 +16,8 @@ const translations = { }, en: { brightness: 'Brightness', - collapse_open: 'Collapse image tools', collapse_close: 'Expand image tools', + collapse_open: 'Collapse image tools', contrast: 'Contrast', flip: 'Flip', greyscale: 'Greyscale', @@ -29,25 +29,10 @@ const translations = { saturation: 'Saturation', show: 'Show image tools', }, - it: { - brightness: 'Luminosità', - collapse_open: 'Chiudere gli strumenti dell\'immagine', - collapse_close: 'Espandi gli strumenti dell\'immagine', - contrast: 'Contrasto', - flip: 'Inverti', - greyscale: 'Scala di grigio', - hide: 'Nascondi gli strumenti dell\'immagine', - invert: 'Inverti colori', - revert: 'Ripristinare l\'immagine', - rotateLeft: 'Ruota a sinistra', - rotateRight: 'Ruota a destra', - saturation: 'Saturazione', - show: 'Mostra gli strumenti dell\'immagine', - }, fr: { brightness: 'Luminosité', - collapse_open: 'Fermer les outils d\'image', collapse_close: 'Ouvrir les outils d\'image', + collapse_open: 'Fermer les outils d\'image', contrast: 'Contraste', flip: 'Miroir', greyscale: 'Niveaux de gris', @@ -59,10 +44,25 @@ const translations = { saturation: 'Saturation', show: 'Afficher les outils d\'image', }, + it: { + brightness: 'Luminosità', + collapse_close: 'Espandi gli strumenti dell\'immagine', + collapse_open: 'Chiudere gli strumenti dell\'immagine', + contrast: 'Contrasto', + flip: 'Inverti', + greyscale: 'Scala di grigio', + hide: 'Nascondi gli strumenti dell\'immagine', + invert: 'Inverti colori', + revert: 'Ripristinare l\'immagine', + rotateLeft: 'Ruota a sinistra', + rotateRight: 'Ruota a destra', + saturation: 'Saturazione', + show: 'Mostra gli strumenti dell\'immagine', + }, ja: { brightness: '明るさ', - collapse_open: '画像ツールを畳む', collapse_close: '画像ツールを開く', + collapse_open: '画像ツールを畳む', contrast: 'コントラスト', flip: 'フリップ', greyscale: 'グレースケール', @@ -76,8 +76,8 @@ const translations = { }, sr: { brightness: 'Осветљење', - collapse_open: 'Затворите алат', collapse_close: 'Отворите алат', + collapse_open: 'Затворите алат', contrast: 'Контраст', flip: 'Окрени', greyscale: 'Црнобело', @@ -91,8 +91,8 @@ const translations = { }, 'zh-CN': { brightness: '亮度', - collapse_open: '折叠图像工具', collapse_close: '展开图像工具', + collapse_open: '折叠图像工具', contrast: '对比度', flip: '翻转', greyscale: '灰度', @@ -106,8 +106,8 @@ const translations = { }, 'zh-TW': { brightness: '亮度', - collapse_open: '折疊圖像工具', collapse_close: '展開圖像工具', + collapse_open: '折疊圖像工具', contrast: '對比度', flip: '翻轉', greyscale: '灰度', From 8ab7d2208e3eeb45f41143823a4b5351ef3c430b Mon Sep 17 00:00:00 2001 From: Marlo Longley Date: Thu, 9 Jan 2025 11:54:28 -0500 Subject: [PATCH 2/3] Convert to vite and vitest Remove webpack, babel, jest --- __tests__/MiradorImageTools.test.js | 4 +- babel.config.js | 93 --------------------------- demo/src/index.html | 1 + jest.config.js | 21 ------- package.json | 84 +++++++++---------------- setupJest.js | 1 - setupTest.js | 2 + src/index.js | 17 ++--- vite.config.js | 88 ++++++++++++++++++++++++++ vitest.config.js | 49 +++++++++++++++ webpack.config.js | 98 ----------------------------- 11 files changed, 182 insertions(+), 276 deletions(-) delete mode 100644 babel.config.js delete mode 100644 jest.config.js delete mode 100644 setupJest.js create mode 100644 setupTest.js create mode 100644 vite.config.js create mode 100644 vitest.config.js delete mode 100644 webpack.config.js diff --git a/__tests__/MiradorImageTools.test.js b/__tests__/MiradorImageTools.test.js index 7b5e286..762590b 100644 --- a/__tests__/MiradorImageTools.test.js +++ b/__tests__/MiradorImageTools.test.js @@ -9,8 +9,8 @@ const mockPalette = { }, }; -jest.mock('@custom-react-hooks/use-element-size', () => ({ - useElementSize: () => ([undefined, { width: 100, height: 200 }]), +vi.mock('@custom-react-hooks/use-element-size', () => ({ + useElementSize: () => [undefined, { height: 200, width: 100 }], })); function createWrapper(props) { diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 7f9bb3d..0000000 --- a/babel.config.js +++ /dev/null @@ -1,93 +0,0 @@ -const moduleFormatMap = { - cjs: 'commonjs', - es: false, -}; - -module.exports = (api) => ({ - presets: [ - api.env('test') && [ - '@babel/preset-env', - { - modules: 'commonjs', - targets: { - node: 'current', - }, - }, - ], - (api.env('production') || api.env('development')) && [ - '@babel/preset-env', - { - corejs: 3, - exclude: ['transform-typeof-symbol'], - forceAllTransforms: true, - modules: moduleFormatMap[process.env.MODULE_FORMAT] || false, - useBuiltIns: 'entry', - }, - ], - [ - '@babel/preset-react', - { - development: api.env('development') || api.env('test'), - runtime: 'automatic', - useBuiltIns: true, - }, - ], - ].filter(Boolean), - plugins: [ - 'babel-plugin-macros', - '@babel/plugin-transform-destructuring', - [ - '@babel/plugin-proposal-class-properties', - { - loose: true, - }, - ], - ['@babel/plugin-proposal-private-property-in-object', { loose: true }], - ['@babel/plugin-proposal-private-methods', { loose: true }], - [ - '@babel/plugin-proposal-object-rest-spread', - { - useBuiltIns: true, - }, - ], - [ - '@babel/plugin-transform-runtime', - { - corejs: false, - helpers: false, // Needed to support IE/Edge - regenerator: true, - }, - ], - [ - '@babel/plugin-transform-regenerator', - { - async: false, - }, - ], - ['transform-react-remove-prop-types', - { - ignoreFilenames: ['node_modules'], - removeImport: true, - }, - ], - [ - "@emotion", - { - importMap: { - "@mui/system": { - styled: { - canonicalImport: ["@emotion/styled", "default"], - styledBaseImport: ["@mui/system", "styled"] - } - }, - "@mui/material/styles": { - styled: { - canonicalImport: ["@emotion/styled", "default"], - styledBaseImport: ["@mui/material/styles", "styled"] - } - } - } - } - ] - ].filter(Boolean) -}); diff --git a/demo/src/index.html b/demo/src/index.html index 750d866..81f9501 100644 --- a/demo/src/index.html +++ b/demo/src/index.html @@ -9,6 +9,7 @@
+ diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 3eea70d..0000000 --- a/jest.config.js +++ /dev/null @@ -1,21 +0,0 @@ -// For a detailed explanation regarding each configuration property, visit: -// https://jestjs.io/docs/en/configuration.html - -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - setupFilesAfterEnv: [ - '/setupJest.js', - ], - // Ignore Mirador code from jest transforms - transformIgnorePatterns: [ - '/node_modules/(?!mirador|@react-dnd|react-dnd|dnd-core|react-dnd-html5-backend|dnd-multi-backend|rdndmb-html5-to-touch)', - ], - testEnvironment: "jsdom", - testPathIgnorePatterns: [ - "/__tests__/test-utils.js", - ], -}; diff --git a/package.json b/package.json index 401b73c..16920ca 100644 --- a/package.json +++ b/package.json @@ -1,84 +1,62 @@ { "name": "mirador-image-tools", - "version": "1.0.0-alpha.1", + "version": "1.0.0-alpha.2", "description": "mirador-image-tools React component", - "main": "lib/index.js", - "module": "es/index.js", + "main": "./dist/mirador-image-tools.js", + "module": "./dist/mirador-image-tools.es.js", + "type": "module", "files": [ - "css", - "es", - "lib", - "umd" + "dist" ], + "exports": { + "./src": "./src/index.js" + }, "scripts": { - "build": "npm run build:umd", - "build:demo": "NODE_ENV=development webpack --mode=development", - "build:umd": "NODE_ENV=production webpack --mode=production", - "build:es": "mkdir -p es && cp -r src/* es && NODE_ENV=production MODULE_FORMAT=es npx babel es -d es", - "build:cjs": "mkdir -p lib && cp -r src/* lib && NODE_ENV=production MODULE_FORMAT=cjs npx babel lib -d lib", - "clean": "rm -rf ./umd && rm -rf ./es && rm -rf ./lib && rm -rf ./demo/dist", - "lint": "eslint ./src ./__tests__", - "prepublishOnly": "npm run clean && npm run build:es && npm run build:cjs && npm run build", - "start": "NODE_ENV=development webpack serve --open", - "test": "npm run lint && jest", - "test:coverage": "jest --coverage", - "test:watch": "jest --watch" + "build": "vite build --config vite.config.js", + "clean": "rm -rf ./dist", + "lint": "npx eslint ./", + "prepublishOnly": "npm run build", + "start": "vite", + "test": "npm run lint && npx vitest run", + "test:coverage": "npm run lint && npx vitest run --coverage" }, "dependencies": { - "@emotion/babel-plugin": "^11.11.0", "@emotion/cache": "^11.11.0", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6" }, "peerDependencies": { "@mui/material": "^5.x", - "mirador": "^4.0.0-alpha.5", + "mirador": "^4.0.0-alpha.6", "react": "18.x", "react-dom": "18.x" }, "devDependencies": { - "@babel/cli": "^7.17.6", - "@babel/core": "^7.17.7", - "@babel/eslint-parser": "^7.5.4", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.17.3", - "@babel/plugin-proposal-private-property-in-object": "^7.21.11", - "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-runtime": "^7.17.0", - "@babel/preset-env": "^7.16.11", - "@babel/preset-react": "^7.16.7", - "@mui/material": "^5.13.5", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", - "@testing-library/dom": "^9.2.0", - "@testing-library/jest-dom": "^6.1.5", - "@testing-library/react": "^14.1.0", + "@mui/material": "^5.x", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.4.3", - "babel-eslint": "^10.0.3", - "babel-jest": "^24.9.0", - "babel-loader": "^9.1.0", + "@vitejs/plugin-react": "^4.3.4", + "@vitest/coverage-v8": "^2.1.8", + "@vitest/ui": "^2.1.4", "eslint": "^8.11.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-react-app": "^7.0.0", - "eslint-plugin-babel": "^5.3.0", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^27.1.5", + "eslint-plugin-jest-dom": "^5.1.0", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.29.4", "eslint-plugin-react-hooks": "^4.6.0", - "html-loader": "^5.0.0", - "html-webpack-plugin": "^5.6.0", - "jest": "^29.3.1", - "jest-environment-jsdom": "^29.4.3", - "jest-puppeteer": "^9.0.2", - "mirador": "^4.0.0-alpha.5", - "puppeteer": "^21.0.0", + "eslint-plugin-testing-library": "^6.2.0", + "glob": "^10.3.0", + "happy-dom": "^15.11.7", + "mirador": "^4.0.0-alpha.6", "react": "^18.0.0", "react-dom": "^18.0.0", - "react-refresh": "^0.14.0", - "terser-webpack-plugin": "^5.3.1", - "webpack": "^5.70.0", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.7.4" + "vite": "^6.0.0", + "vitest": "^2.1.4", + "vitest-fetch-mock": "^0.4.2" }, "author": "", "homepage": "", diff --git a/setupJest.js b/setupJest.js deleted file mode 100644 index 010b0b5..0000000 --- a/setupJest.js +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom' \ No newline at end of file diff --git a/setupTest.js b/setupTest.js new file mode 100644 index 0000000..d20324d --- /dev/null +++ b/setupTest.js @@ -0,0 +1,2 @@ +import '@testing-library/jest-dom'; // add this import to make toBeInTheDocument work +import { vi } from 'vitest'; diff --git a/src/index.js b/src/index.js index 57220c8..34c12a7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ +import '@mui/material/styles/styled'; import { getContainerId, getWindowConfig, @@ -11,10 +12,13 @@ import translations from './translations'; export const miradorImageToolsPlugin = [ { - target: 'OpenSeadragonViewer', + component: MiradorImageTools, + config: { + translations, + }, mapDispatchToProps: { - updateWindow, updateViewport, + updateWindow, }, mapStateToProps: (state, { windowId }) => ({ containerId: getContainerId(state), @@ -23,20 +27,17 @@ export const miradorImageToolsPlugin = [ viewConfig: getViewer(state, { windowId }) || {}, }), mode: 'add', - component: MiradorImageTools, - config: { - translations, - }, + target: 'OpenSeadragonViewer', }, { - target: 'WindowTopBarPluginMenu', component: MiradorImageToolsMenuItem, - mode: 'add', mapDispatchToProps: { updateWindow, }, mapStateToProps: (state, { windowId }) => ({ enabled: getWindowConfig(state, { windowId }).imageToolsEnabled || false, }), + mode: 'add', + target: 'WindowTopBarPluginMenu', }, ]; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..f72af85 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,88 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import fs from 'fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'url'; +import { globSync } from 'glob'; +import pkg from './package.json'; + +/** +* Vite configuration +*/ +export default defineConfig({ + ...( + process.env.NETLIFY ? { + build: { + rollupOptions: { + external: ['__tests__/*', '__mocks__/*'], + input: Object.fromEntries( + globSync('./demo/src/*.html').map((file) => [ + // This remove `src/` as well as the file extension from each + // file, so e.g. src/nested/foo.js becomes nested/foo + path.relative( + 'demo/src/', + file.slice(0, file.length - path.extname(file).length), + ), + // This expands the relative paths to absolute paths, so e.g. + // src/nested/foo becomes /project/src/nested/foo.js + fileURLToPath(new URL(file, import.meta.url)), + ]), + ), + }, + sourcemap: true, + }, + } : { + build: { + lib: { + entry: './src/index.js', + fileName: (format) => (format === 'umd' ? 'mirador-dl-plugin.js' : 'mirador-dl-plugin.es.js'), + formats: ['es', 'umd'], + name: 'MiradorDlPlugin', + }, + rollupOptions: { + external: [...Object.keys(pkg.peerDependencies || {}), '__tests__/*', '__mocks__/*'], + output: { + assetFileNames: 'mirador-dl-plugin.[ext]', + }, + }, + sourcemap: true, + }, + } + ), + esbuild: { + exclude: [], + // Matches .js and .jsx in __tests__ and .jsx in src + include: [/__tests__\/.*\.(js|jsx)$/, /src\/.*\.jsx?$/], + loader: 'jsx', + }, + optimizeDeps: { + esbuildOptions: { + plugins: [ + { + name: 'load-js-files-as-jsx', + // TODO: rename all our files to .jsx ... + + setup(build) { + build.onLoad({ filter: /(src|__tests__)\/.*\.js$/ }, async (args) => ({ + contents: await fs.readFile(args.path, 'utf8'), + loader: 'jsx', + })); + }, + }, + ], + }, + include: [ + '@emotion/react', + ], + }, + plugins: [react()], + resolve: { + alias: { + '@tests/': fileURLToPath(new URL('./__tests__', import.meta.url)), + }, + }, + server: { + open: '/demo/src/index.html', + port: '4444', + }, +}); diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..3f49506 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,49 @@ +/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { fileURLToPath } from 'url'; +import fs from 'fs/promises'; + +export default defineConfig({ + esbuild: { + exclude: [], + include: /(src|__tests__)\/.*\.jsx?$/, + loader: 'jsx', + }, + optimizeDeps: { + esbuildOptions: { + plugins: [ + { + name: 'load-js-files-as-jsx', + + setup(build) { + build.onLoad({ filter: /(src|__tests__)\/.*\.js$/ }, async (args) => ({ + contents: await fs.readFile(args.path, 'utf8'), + loader: 'jsx', + })); + }, + }, + ], + }, + }, + plugins: [react()], + resolve: { + alias: { + '@tests': fileURLToPath(new URL('./__tests__', import.meta.url)), + }, + }, + test: { + coverage: { + all: true, + enabled: true, + }, + environment: 'happy-dom', + exclude: ['node_modules'], + globals: true, + include: ['**/*.test.js', '**/*.test.jsx'], + sequence: { + shuffle: true, + }, + setupFiles: ['./setupTest.js'], + }, +}); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 166ee53..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,98 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const webpack = require('webpack'); -const TerserPlugin = require('terser-webpack-plugin'); -const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); - -/** */ -const baseConfig = mode => ({ - entry: ['./src/index.js'], - module: { - rules: [ - { - include: path.resolve(fs.realpathSync(process.cwd()), '.'), // CRL - loader: require.resolve('babel-loader'), - options: { - // Save disk space when time isn't as important - cacheCompression: true, - cacheDirectory: true, - compact: true, - envName: mode, - }, - test: /\.(js|mjs|jsx)$/, - }, - { - test: /\.html$/, - loader: 'html-loader' - } - ], - }, - optimization: { - minimizer: [ - new TerserPlugin({ - extractComments: true, - }), - ], - }, - output: { - filename: 'mirador-image-tools.js', - hashFunction: 'md5', - library: 'MiradorImageTools', - libraryExport: 'default', - libraryTarget: 'umd', - path: path.join(__dirname, 'umd') - }, - plugins: [ - new webpack.IgnorePlugin({ - resourceRegExp: /@blueprintjs\/(core|icons)/, // ignore optional UI framework dependencies - }), - ], - resolve: { - fallback: { "url": false }, - extensions: ['.js'], - }, -}); - -module.exports = (env, options) => { - const isProduction = options.mode === 'production'; - const config = baseConfig(options.mode); - - if (isProduction) { - return { - ...config, - devtool: 'source-map', - mode: 'production', - plugins: [ - ...(config.plugins || []), - new webpack.optimize.LimitChunkCountPlugin({ - maxChunks: 1, - }), - ], - }; - } - - return { - ...config, - output: { - filename: 'demo.js', - path: path.join(__dirname, 'demo/dist'), - publicPath: '/' - }, - devServer: { - hot: true, - port: 4444, - static: [ - './demo/dist/', - ], - }, - devtool: 'eval-source-map', - mode: 'development', - entry: ['./demo/src/index.js'], - plugins: [ - ...(config.plugins || []), - new HtmlWebpackPlugin({ template: path.join(__dirname, 'demo/src/index.html') }), - new ReactRefreshWebpackPlugin(), - ], - }; -}; From db17706dac35ca356e1f4f24d1551dc9636ca800 Mon Sep 17 00:00:00 2001 From: Marlo Longley Date: Thu, 9 Jan 2025 13:20:48 -0500 Subject: [PATCH 3/3] Add Netlify config file --- .gitignore | 1 + netlify.toml | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 netlify.toml diff --git a/.gitignore b/.gitignore index 05ed3c1..64276a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /coverage /demo/dist +/dist /es /lib /node_modules diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..c869f0c --- /dev/null +++ b/netlify.toml @@ -0,0 +1,9 @@ +# This is the configuration file for the netlify preview deploys +# See https://www.netlify.com/docs/netlify-toml-reference/ for more + +[build] + publish = "dist/" + +[[redirects]] + from = "/" + to = "/demo/src/" \ No newline at end of file