diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..d4523518 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,32 @@ +name: Lint & Format + +on: + pull_request: + push: + branches: [ main ] + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci || npm install + + - name: Run ESLint + run: npm run lint || true + + - name: Prettier Check + run: npm run format + + - name: Debug - Show that workflow is updated + run: echo "WORKFLOW VERSION 1.1" diff --git a/.gitignore b/.gitignore index f8debb6c..50192ee1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,30 @@ -nbproject/ -build.xml -*.properties \ No newline at end of file +# Node +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build artifacts +dist/ +build/ +coverage/ + +# IDE files +.vscode/ +.idea/ + +# OS files +.DS_Store +Thumbs.db + +# Environment variables +.env +.env.local +.env.*.local + +# Logs +*.log + +# Temporary files +*.tmp +*.swp diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..e5cfc514 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +node_modules +web/dist +web/vendor +web/**/*.min.js diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..1af700ca --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ + +{ + "printWidth": 100, + "tabWidth": 2, + "singleQuote": true, + "trailingComma": "es5", + "semi": true, + "arrowParens": "always" +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..a3a6391c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,35 @@ +const prettierPlugin = require("eslint-plugin-prettier"); +const prettierConfig = require("eslint-config-prettier"); + +module.exports = [ + { + files: ["web/**/*.js"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + globals: { + window: "readonly", + document: "readonly", + localStorage: "readonly", + fetch: "readonly", + console: "readonly", + alert: "readonly", + setTimeout: "readonly", + CustomEvent: "readonly", + module: "readonly" + } + }, + plugins: { + prettier: prettierPlugin + }, + rules: { + ...prettierConfig.rules, + "no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], + "no-undef": "error", + eqeqeq: "warn", + curly: ["warn", "all"], + "prettier/prettier": "warn" + }, + ignores: ["node_modules/", "web/dist/", "web/vendor/", "web/**/*.min.js", "web/**/*.test.js"] + } +]; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..df64d403 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1098 @@ +{ + "name": "rerum-playground", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rerum-playground", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "prettier": "^3.6.2" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..ade887b9 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "rerum-playground", + "version": "1.0.0", + "description": "This is a web space that uses the RERUM installation of the Research Computing Group at Saint Louis University to explore the possibilities of interoperable tools and standards. By using the Tiny Things sandbox, users can immediately create reusable JSON-LD objects that conform to IIIF and Web Annotation standards and find new ways to interact with them.", + "main": "index.js", + "directories": { + "doc": "docs" + }, + "scripts": { + "lint": "eslint \"web/**/*.js\"", + "lint:fix": "eslint \"web/**/*.js\" --fix", + "format": "prettier --check \"web/**/*.js\"", + "format:fix": "prettier --write \"web/**/*.js\"", + "qa": "npm run lint && npm run format", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "prettier": "^3.6.2" + } +} diff --git a/web/js/config.js b/web/js/config.js index 7a87a28a..9b941a5c 100644 --- a/web/js/config.js +++ b/web/js/config.js @@ -1,59 +1,55 @@ -/* +/* * The global config for the app. * There should be no functions or HTML template literals here. - * + * */ import ToolsCatalog from './toolsCatalog.js'; export default { - URLS: { - //TODO Bring in internal TT. Register as a new application for RERUM dev. - CREATE: "http://tinydev.rerum.io/app/create", - UPDATE: "http://tinydev.rerum.io/app/update", - PATCH: "http://tinydev.rerum.io/app/patch", - OVERWRITE: "http://tinydev.rerum.io/app/overwrite", - QUERY: "http://tinydev.rerum.io/app/query", - SINCE: "http://devstore.rerum.io/v1/since", - HISTORY: "http://devstore.rerum.io/v1/history" - }, - EVENTS: { - CREATED: "created", - UPDATED: "updated", - LOADED: "completely_loaded", - NEW_VIEW: "view_called", - VIEW_RENDERED : "view_loaded", - CLICKED: "clicked" - }, - APPAGENT : "", //TODO register a new app with RERUM. - /** - * 0: OFF - * 6: TRACE - * 5: DEBUG - * 4: INFO - * 3: WARNING - * 2: ERROR - * 1: FATAL - */ - LOGLEVEL: 3, - VERSION: "0.0.1", - //Of NOTE, it is possible for these to be in their own repository. We could gather these from those repos. - //Then just adding one of these things to the repos will make it show up in the playground. Just a thought for now. - TOOLS:{ - id : "tool_set", - catalog : ToolsCatalog // importing the tool catalog from toolsCatalog.js - }, + URLS: { + //TODO Bring in internal TT. Register as a new application for RERUM dev. + CREATE: 'http://tinydev.rerum.io/app/create', + UPDATE: 'http://tinydev.rerum.io/app/update', + PATCH: 'http://tinydev.rerum.io/app/patch', + OVERWRITE: 'http://tinydev.rerum.io/app/overwrite', + QUERY: 'http://tinydev.rerum.io/app/query', + SINCE: 'http://devstore.rerum.io/v1/since', + HISTORY: 'http://devstore.rerum.io/v1/history', + }, + EVENTS: { + CREATED: 'created', + UPDATED: 'updated', + LOADED: 'completely_loaded', + NEW_VIEW: 'view_called', + VIEW_RENDERED: 'view_loaded', + CLICKED: 'clicked', + }, + APPAGENT: '', //TODO register a new app with RERUM. + /** + * 0: OFF + * 6: TRACE + * 5: DEBUG + * 4: INFO + * 3: WARNING + * 2: ERROR + * 1: FATAL + */ + LOGLEVEL: 3, + VERSION: '0.0.1', + //Of NOTE, it is possible for these to be in their own repository. We could gather these from those repos. + //Then just adding one of these things to the repos will make it show up in the playground. Just a thought for now. + TOOLS: { + id: 'tool_set', + catalog: ToolsCatalog, // importing the tool catalog from toolsCatalog.js + }, - // What are these here for? Do the users know about these? - INTERFACES:{ - id : "interface_set", - catalog : [ - - ] - }, - TECHNOLOGIES:{ - id : "technology_set", - catalog : [ - - ] - } -} + // What are these here for? Do the users know about these? + INTERFACES: { + id: 'interface_set', + catalog: [], + }, + TECHNOLOGIES: { + id: 'technology_set', + catalog: [], + }, +}; diff --git a/web/js/json-utils.js b/web/js/json-utils.js index 52e092db..b2f186a8 100644 --- a/web/js/json-utils.js +++ b/web/js/json-utils.js @@ -1,25 +1,22 @@ - function prettifyJSON(input) { try { - const obj = typeof input === "string" ? JSON.parse(input) : input; + const obj = typeof input === 'string' ? JSON.parse(input) : input; return JSON.stringify(obj, null, 2); } catch (error) { return `Invalid JSON: ${error.message}`; } } - -function validateJSON(input){ - try { - JSON.parse(input); - return true; - } - catch (error) { - return false; - } +function validateJSON(input) { + try { + JSON.parse(input); + return true; + } catch (error) { + return false; + } } module.exports = { - prettifyJSON: prettifyJSON, - validateJSON: validateJSON -} + prettifyJSON: prettifyJSON, + validateJSON: validateJSON, +}; diff --git a/web/js/json-utils.test.js b/web/js/json-utils.test.js index 2162815d..4c59938a 100644 --- a/web/js/json-utils.test.js +++ b/web/js/json-utils.test.js @@ -1,9 +1,9 @@ -const {prettifyJSON, validateJSON} = require('./json-utils.js'); +const { prettifyJSON, validateJSON } = require('./json-utils.js'); const Student = { - name : "Brian", - year : "Junior", - major : "Computer Science" + name: 'Brian', + year: 'Junior', + major: 'Computer Science', }; test('JSON is formatted.', () => { @@ -11,7 +11,7 @@ test('JSON is formatted.', () => { }); test('JSON is validated.', () => { - expect(validateJSON(JSON.stringify(Student,null,2))).toBe(true); + expect(validateJSON(JSON.stringify(Student, null, 2))).toBe(true); }); test('Invalid JSON should return false', () => { @@ -22,4 +22,3 @@ test('Invalid JSON should show helpful message', () => { const result = prettifyJSON("{name: 'Brian'}"); expect(result).toMatch(/Invalid JSON/); }); - diff --git a/web/js/manifestStorage.js b/web/js/manifestStorage.js index ef0593e1..8625aef4 100644 --- a/web/js/manifestStorage.js +++ b/web/js/manifestStorage.js @@ -1,21 +1,21 @@ -const MANIFEST_LINKS_KEY ='storedManifestLinks'; +const MANIFEST_LINKS_KEY = 'storedManifestLinks'; /** * Save the given manifest link to local storage. */ export function storeManifestLink(manifestLink) { - let manifestLinks = getStoredManifestLinks(); + let manifestLinks = getStoredManifestLinks(); - if (!manifestLinks.includes(manifestLink)) { - manifestLinks.push(manifestLink); - } + if (!manifestLinks.includes(manifestLink)) { + manifestLinks.push(manifestLink); + } - // save updated links array to local storage - localStorage.setItem(MANIFEST_LINKS_KEY, JSON.stringify(manifestLinks)); + // save updated links array to local storage + localStorage.setItem(MANIFEST_LINKS_KEY, JSON.stringify(manifestLinks)); } export function getStoredManifestLinks() { - // get the stored links from local storage, or return an empty array if none are found - const manifestLinks = localStorage.getItem(MANIFEST_LINKS_KEY); - return manifestLinks ? JSON.parse(manifestLinks) : []; -} \ No newline at end of file + // get the stored links from local storage, or return an empty array if none are found + const manifestLinks = localStorage.getItem(MANIFEST_LINKS_KEY); + return manifestLinks ? JSON.parse(manifestLinks) : []; +} diff --git a/web/js/playground.js b/web/js/playground.js index f8bb2a6e..d2210032 100644 --- a/web/js/playground.js +++ b/web/js/playground.js @@ -1,33 +1,36 @@ -/* - * App specific functions. This is for unique functionality and application initialization. +/* + * App specific functions. This is for unique functionality and application initialization. */ - + // Playground scripting utilities. Will be available as github CDN. //fetch footer fetch('footer.html') - .then(response => response.text()) - .then(data => { - document.getElementById('footer-placeholder').innerHTML = data; - }) - .catch(error => console.error('Error loading footer:', error)); + .then((response) => response.text()) + .then((data) => { + document.getElementById('footer-placeholder').innerHTML = data; + }) + .catch((error) => console.error('Error loading footer:', error)); //fetch menu fetch('menu.html') - .then(response => response.text()) - .then(data => { - document.getElementById('menu-placeholder').innerHTML = data; - }) - .catch(error => console.error('Error loading menu:', error)); + .then((response) => response.text()) + .then((data) => { + document.getElementById('menu-placeholder').innerHTML = data; + }) + .catch((error) => console.error('Error loading menu:', error)); -//menubar js +//menubar js function openCloseMenu() { - var toolBar = document.getElementById("toolBar"); - var mainContent = document.querySelector(".content") || document.querySelector(".container") || document.querySelector("#tool_set"); - toolBar.classList.toggle("sidebar-open"); - if (mainContent) { - mainContent.classList.toggle("shift"); - } + var toolBar = document.getElementById('toolBar'); + var mainContent = + document.querySelector('.content') || + document.querySelector('.container') || + document.querySelector('#tool_set'); + toolBar.classList.toggle('sidebar-open'); + if (mainContent) { + mainContent.classList.toggle('shift'); + } } -window.openCloseMenu = openCloseMenu; \ No newline at end of file +window.openCloseMenu = openCloseMenu; diff --git a/web/js/sandbox.js b/web/js/sandbox.js index 7d29c187..972b9360 100644 --- a/web/js/sandbox.js +++ b/web/js/sandbox.js @@ -1,19 +1,17 @@ function showSection(id) { - document - .querySelectorAll(".sandbox-section") - .forEach((sec) => sec.classList.add("hidden")); - document.getElementById(id).classList.remove("hidden"); + document.querySelectorAll('.sandbox-section').forEach((sec) => sec.classList.add('hidden')); + document.getElementById(id).classList.remove('hidden'); } window.showSection = showSection; // Placeholder action handlers -document.addEventListener("DOMContentLoaded", () => { - document.querySelectorAll(".action-btn").forEach((btn) => { - btn.addEventListener("click", () => { +document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('.action-btn').forEach((btn) => { + btn.addEventListener('click', () => { const action = btn.textContent.trim(); console.log(`${action} action triggered (placeholder).`); alert(`${action} action clicked (placeholder).`); }); }); -}); \ No newline at end of file +}); diff --git a/web/js/tools.js b/web/js/tools.js index b874bbba..8f3cca7a 100644 --- a/web/js/tools.js +++ b/web/js/tools.js @@ -2,7 +2,7 @@ import { storeManifestLink, getStoredManifestLinks } from './manifestStorage.js'; // Playground scripting utilities. Will be available as github CDN. -import { default as UTILS } from 'https://centerfordigitalhumanities.github.io/rerum-playground/web/js/utilities.js' +import { default as UTILS } from 'https://centerfordigitalhumanities.github.io/rerum-playground/web/js/utilities.js'; import PLAYGROUND from './config.js'; import ToolsCatalog from './toolsCatalog.js'; @@ -13,100 +13,104 @@ const RECENTLY_USED_KEY = 'recentlyUsedTools'; * Retrieve recently used tools from local storage. */ function getRecentlyUsedTools() { - const recentTools = localStorage.getItem(RECENTLY_USED_KEY); - return recentTools ? JSON.parse(recentTools) : []; + const recentTools = localStorage.getItem(RECENTLY_USED_KEY); + return recentTools ? JSON.parse(recentTools) : []; } -/** +/** * Save recently used tools to local storage. */ function saveRecentlyUsedTools(recentTools) { - localStorage.setItem(RECENTLY_USED_KEY, JSON.stringify(recentTools)); + localStorage.setItem(RECENTLY_USED_KEY, JSON.stringify(recentTools)); } /** * Update recently used tools, move the clicked tool to the top. */ function updateRecentlyUsedTools(clickedTool) { - let allTools = getRecentlyUsedTools(); + let allTools = getRecentlyUsedTools(); - allTools = allTools.filter( - tool => tool.label.toLowerCase() !== clickedTool.label.toLowerCase() - ); + allTools = allTools.filter( + (tool) => tool.label.toLowerCase() !== clickedTool.label.toLowerCase() + ); - allTools.unshift(clickedTool); + allTools.unshift(clickedTool); - const updatedTools = [ - ...allTools, - ...ToolsCatalog.filter(tool => - !allTools.some(recentTool => recentTool.label === tool.label) - ), - ]; + const updatedTools = [ + ...allTools, + ...ToolsCatalog.filter( + (tool) => !allTools.some((recentTool) => recentTool.label === tool.label) + ), + ]; - saveRecentlyUsedTools(updatedTools); + saveRecentlyUsedTools(updatedTools); } /** -* Landing behavior for interfaces. This should populate the set of available interfaces to the DOM. -*/ + * Landing behavior for interfaces. This should populate the set of available interfaces to the DOM. + */ function initializeInterfaces(config) { - return new Promise((res) => { - let setContainer = document.getElementById(config.id) - Array.from(config.catalog).forEach(inter => { - setContainer.innerHTML += UTILS.thumbnailGenerator(inter) - }) - UTILS.broadcast(undefined, PLAYGROUND.EVENTS.LOADED, setContainer, {}) - /** - * Really each render should be a promise and we should return a Promise.all() here of some kind. - * That would only work if PlaygroundRender resulted in a Promise where we could return Promise.all(renderPromises). - */ - setTimeout(res, 200) //A small hack to ensure all the HTML generated by processing the views enters the DOM before this says it has resolved. - }) + return new Promise((res) => { + let setContainer = document.getElementById(config.id); + Array.from(config.catalog).forEach((inter) => { + setContainer.innerHTML += UTILS.thumbnailGenerator(inter); + }); + UTILS.broadcast(undefined, PLAYGROUND.EVENTS.LOADED, setContainer, {}); + /** + * Really each render should be a promise and we should return a Promise.all() here of some kind. + * That would only work if PlaygroundRender resulted in a Promise where we could return Promise.all(renderPromises). + */ + setTimeout(res, 200); //A small hack to ensure all the HTML generated by processing the views enters the DOM before this says it has resolved. + }); } /** -* Landing behavior for technologies. This should populate the technologies to the DOM. -*/ + * Landing behavior for technologies. This should populate the technologies to the DOM. + */ function initializeTechnologies(config) { - return new Promise((res) => { - let setContainer = document.getElementById(config.id) - Array.from(config.catalog).forEach(tech => { - setContainer.innerHTML += UTILS.thumbnailGenerator(tech) - }) - UTILS.broadcast(undefined, PLAYGROUND.EVENTS.LOADED, setContainer, {}) - /** - * Really each render should be a promise and we should return a Promise.all() here of some kind. - * That would only work if PlaygroundRender resulted in a Promise where we could return Promise.all(renderPromises). - */ - setTimeout(res, 200) //A small hack to ensure all the HTML generated by processing the views enters the DOM before this says it has resolved. - }) + return new Promise((res) => { + let setContainer = document.getElementById(config.id); + Array.from(config.catalog).forEach((tech) => { + setContainer.innerHTML += UTILS.thumbnailGenerator(tech); + }); + UTILS.broadcast(undefined, PLAYGROUND.EVENTS.LOADED, setContainer, {}); + /** + * Really each render should be a promise and we should return a Promise.all() here of some kind. + * That would only work if PlaygroundRender resulted in a Promise where we could return Promise.all(renderPromises). + */ + setTimeout(res, 200); //A small hack to ensure all the HTML generated by processing the views enters the DOM before this says it has resolved. + }); } /** * Render tools, higlighting recently used ones. */ function renderTools() { - const toolSetContainer = document.getElementById('tool_set'); - if (!toolSetContainer) { - console.error("Tool set container not found."); - return; - } - - toolSetContainer.innerHTML = ''; - - const recentTools = getRecentlyUsedTools(); - - const toolsToRender = recentTools.length > 0 - ? [...recentTools, ...ToolsCatalog.filter(tool => - !recentTools.some(recentTool => recentTool.label === tool.label) - )] - : [...ToolsCatalog]; - - const toolsWrapper = document.createElement('div'); - - toolsToRender.forEach((tool, index) => { - const isRecentlyUsed = index < 3 ? `Recently used` : ''; - const toolHTML = ` + const toolSetContainer = document.getElementById('tool_set'); + if (!toolSetContainer) { + console.error('Tool set container not found.'); + return; + } + + toolSetContainer.innerHTML = ''; + + const recentTools = getRecentlyUsedTools(); + + const toolsToRender = + recentTools.length > 0 + ? [ + ...recentTools, + ...ToolsCatalog.filter( + (tool) => !recentTools.some((recentTool) => recentTool.label === tool.label) + ), + ] + : [...ToolsCatalog]; + + const toolsWrapper = document.createElement('div'); + + toolsToRender.forEach((tool, index) => { + const isRecentlyUsed = index < 3 ? `Recently used` : ''; + const toolHTML = `
${isRecentlyUsed} @@ -116,175 +120,175 @@ function renderTools() {
`; - toolsWrapper.innerHTML += toolHTML; - }); - - toolSetContainer.appendChild(toolsWrapper); - - // Add event listeners after elements are created - const toolLinks = toolSetContainer.querySelectorAll('a.catalogEntry'); - toolLinks.forEach(link => { - link.addEventListener('click', function (e) { - e.preventDefault(); - const toolLabel = this.querySelector('label').innerText; - handleToolClick(toolLabel); - }); + toolsWrapper.innerHTML += toolHTML; + }); + + toolSetContainer.appendChild(toolsWrapper); + + // Add event listeners after elements are created + const toolLinks = toolSetContainer.querySelectorAll('a.catalogEntry'); + toolLinks.forEach((link) => { + link.addEventListener('click', function (e) { + e.preventDefault(); + const toolLabel = this.querySelector('label').innerText; + handleToolClick(toolLabel); }); + }); } /** * Render stored manifest links. */ function renderStoredManifests() { - const manifestContainer = document.getElementById('stored_manifest_links'); - const storedManifests = getStoredManifestLinks(); + const manifestContainer = document.getElementById('stored_manifest_links'); + const storedManifests = getStoredManifestLinks(); - if (!manifestContainer) { - console.error("Manifest set container not found."); - return; - } + if (!manifestContainer) { + console.error('Manifest set container not found.'); + return; + } - manifestContainer.innerHTML = ''; + manifestContainer.innerHTML = ''; - if (storedManifests.length === 0) { - manifestContainer.innerHTML = '

No stored manifest links.

'; - return; - } + if (storedManifests.length === 0) { + manifestContainer.innerHTML = '

No stored manifest links.

'; + return; + } - storedManifests.forEach(manifestLink => { - const manifestHTML = ` + storedManifests.forEach((manifestLink) => { + const manifestHTML = `

${manifestLink}

`; - manifestContainer.innerHTML += manifestHTML; - }); + manifestContainer.innerHTML += manifestHTML; + }); } -window.onload = function() { - renderStoredManifests(); +window.onload = function () { + renderStoredManifests(); }; /** * Handle tool click event to manage recently used logic and allow default navigation */ function handleToolClick(toolLabel) { - const clickedTool = ToolsCatalog.find( - tool => tool.label.toLowerCase() === toolLabel.toLowerCase() - ); - - if (clickedTool) { - updateRecentlyUsedTools(clickedTool); - renderTools(); - setTimeout(() => { - window.open(clickedTool.view, '_blank'); - }, 100); - } else { - console.error('Clicked tool not found:', toolLabel); - } + const clickedTool = ToolsCatalog.find( + (tool) => tool.label.toLowerCase() === toolLabel.toLowerCase() + ); + + if (clickedTool) { + updateRecentlyUsedTools(clickedTool); + renderTools(); + setTimeout(() => { + window.open(clickedTool.view, '_blank'); + }, 100); + } else { + console.error('Clicked tool not found:', toolLabel); + } } /** * Update tool order when a tool is clicked. */ -window.updateToolOrder = function(toolLabel) { - const clickedTool = ToolsCatalog.find(tool => tool.label === toolLabel); - if (clickedTool) { - updateRecentlyUsedTools(clickedTool); - renderTools(); - } -} +window.updateToolOrder = function (toolLabel) { + const clickedTool = ToolsCatalog.find((tool) => tool.label === toolLabel); + if (clickedTool) { + updateRecentlyUsedTools(clickedTool); + renderTools(); + } +}; document.addEventListener('DOMContentLoaded', () => { -/** -* These are promises so we can control the chaining how we like, if necessary. -*/ - try { - initializeInterfaces(PLAYGROUND.INTERFACES) - initializeTechnologies(PLAYGROUND.TECHNOLOGIES) - renderTools(); - renderStoredManifests(); - } catch (err) { - console.error("Error initializing the playground: ", err); - } -}); + /** + * These are promises so we can control the chaining how we like, if necessary. + */ + try { + initializeInterfaces(PLAYGROUND.INTERFACES); + initializeTechnologies(PLAYGROUND.TECHNOLOGIES); + renderTools(); + renderStoredManifests(); + } catch (err) { + console.error('Error initializing the playground: ', err); + } +}); // Wait until the DOM is fully loaded -document.addEventListener('DOMContentLoaded', function() { - const manifestUrl = document.getElementById('manifestUrl'); - const loadManifest = document.getElementById('loadManifest'); - const manifestMessage = document.getElementById('manifestMessage'); - const loadMessage = document.getElementById("loadMessage"); - const manifestLabelField = document.getElementById("manifestLabelField"); - const loadingOverlay = document.querySelector('.loading-overlay'); - - // Function to show loading overlay - function showLoading() { - loadingOverlay.style.display = 'flex'; - manifestMessage.textContent = ''; +document.addEventListener('DOMContentLoaded', function () { + const manifestUrl = document.getElementById('manifestUrl'); + const loadManifest = document.getElementById('loadManifest'); + const manifestMessage = document.getElementById('manifestMessage'); + const loadMessage = document.getElementById('loadMessage'); + const manifestLabelField = document.getElementById('manifestLabelField'); + const loadingOverlay = document.querySelector('.loading-overlay'); + + // Function to show loading overlay + function showLoading() { + loadingOverlay.style.display = 'flex'; + manifestMessage.textContent = ''; + } + + // Function to hide loading overlay + function hideLoading() { + loadingOverlay.style.display = 'none'; + } + + // Event listener for loading manifest + loadManifest.addEventListener('click', async function () { + const url = manifestUrl.value.trim(); + if (!url) { + manifestMessage.textContent = 'Please enter a URL.'; + manifestMessage.style.color = 'red'; + return; } - // Function to hide loading overlay - function hideLoading() { - loadingOverlay.style.display = 'none'; - } + showLoading(); - // Event listener for loading manifest - loadManifest.addEventListener('click', async function() { - const url = manifestUrl.value.trim(); - if (!url) { - manifestMessage.textContent = 'Please enter a URL.'; - manifestMessage.style.color = 'red'; - return; - } - - showLoading(); - - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - - hideLoading(); - manifestMessage.textContent = 'Manifest loaded successfully!'; - manifestMessage.style.color = 'green'; - - storeManifestLink(url); - - try { - let manifestLabel = `Name: ${data.label.en[0]}`; - let manifestType = `Type: ${data.type}`; - let manifestItemCount = `Number of Items: ${data.items.length}`; - - loadMessage.innerHTML = "Current Object:"; - manifestLabelField.innerHTML = `${manifestLabel}      ${manifestType}      ${manifestItemCount}`; - } catch (metadataError) { - loadMessage.innerHTML = "No metadata available!"; - manifestLabelField.innerHTML = ""; - } - } catch (error) { - hideLoading(); - manifestMessage.textContent = 'Failed to load manifest. Please check the URL and try again.'; - manifestMessage.style.color = 'red'; - console.error('Error:', error); - } - }); + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + hideLoading(); + manifestMessage.textContent = 'Manifest loaded successfully!'; + manifestMessage.style.color = 'green'; + + storeManifestLink(url); + + try { + let manifestLabel = `Name: ${data.label.en[0]}`; + let manifestType = `Type: ${data.type}`; + let manifestItemCount = `Number of Items: ${data.items.length}`; + + loadMessage.innerHTML = 'Current Object:'; + manifestLabelField.innerHTML = `${manifestLabel}      ${manifestType}      ${manifestItemCount}`; + } catch (metadataError) { + loadMessage.innerHTML = 'No metadata available!'; + manifestLabelField.innerHTML = ''; + } + } catch (error) { + hideLoading(); + manifestMessage.textContent = 'Failed to load manifest. Please check the URL and try again.'; + manifestMessage.style.color = 'red'; + console.error('Error:', error); + } + }); }); // Toggle dropdown function, defined outside of DOMContentLoaded to ensure it's globally accessible function toggleDropdown() { - const manifestContainer = document.getElementById('stored_manifest_links'); - const dropdownArrow = document.getElementById('dropdownArrow'); - - if (manifestContainer.style.display === 'none' || manifestContainer.style.display === '') { - manifestContainer.style.display = 'block'; - dropdownArrow.textContent = '▲'; - } else { - manifestContainer.style.display = 'none'; - dropdownArrow.textContent = '▼'; - } + const manifestContainer = document.getElementById('stored_manifest_links'); + const dropdownArrow = document.getElementById('dropdownArrow'); + + if (manifestContainer.style.display === 'none' || manifestContainer.style.display === '') { + manifestContainer.style.display = 'block'; + dropdownArrow.textContent = '▲'; + } else { + manifestContainer.style.display = 'none'; + dropdownArrow.textContent = '▼'; + } } document.getElementById('dropdownLabel').addEventListener('click', toggleDropdown); @@ -293,24 +297,24 @@ document.getElementById('dropdownArrow').addEventListener('click', toggleDropdow /** * Update tool order when a tool is clicked. */ -window.updateToolOrder = function(toolLabel) { - const clickedTool = ToolsCatalog.find(tool => tool.label === toolLabel); - if (clickedTool) { - updateRecentlyUsedTools(clickedTool); - renderTools(); - } -} +window.updateToolOrder = function (toolLabel) { + const clickedTool = ToolsCatalog.find((tool) => tool.label === toolLabel); + if (clickedTool) { + updateRecentlyUsedTools(clickedTool); + renderTools(); + } +}; document.addEventListener('DOMContentLoaded', () => { -/** -* These are promises so we can control the chaining how we like, if necessary. -*/ - try { - initializeInterfaces(PLAYGROUND.INTERFACES) - initializeTechnologies(PLAYGROUND.TECHNOLOGIES) - renderTools(); - renderStoredManifests(); - } catch (err) { - console.error("Error initializing the playground: ", err); - } -}); \ No newline at end of file + /** + * These are promises so we can control the chaining how we like, if necessary. + */ + try { + initializeInterfaces(PLAYGROUND.INTERFACES); + initializeTechnologies(PLAYGROUND.TECHNOLOGIES); + renderTools(); + renderStoredManifests(); + } catch (err) { + console.error('Error initializing the playground: ', err); + } +}); diff --git a/web/js/toolsCatalog.js b/web/js/toolsCatalog.js index c85ac20e..a903a600 100644 --- a/web/js/toolsCatalog.js +++ b/web/js/toolsCatalog.js @@ -2,43 +2,47 @@ // Each tool contains a label, icon, description, and a view (URL) for redirection. const ToolsCatalog = [ - { - label: "TinyNode", - icon: "./images/rerum_logo.png", - view: "https://tiny.rerum.io/", - description: "A flexible tool for interacting with objects from RERUM, allowing users to experiment with data." - }, - { - label: "Geolocating Web Annotation Tool", - icon: "./images/rerum_logo.png", - view: "https://geo.rerum.io/", - description: "Helps users annotate data with geolocation coordinates by selecting points on a map." - }, - { - label: "navPlace Object Tool", - icon: "./images/rerum_logo.png", - view: "https://geo.rerum.io/", - description: "Allows interaction with place-based objects in a spatial context." - }, - { - label: "TPEN", - icon: "./images/T-PEN_logo.png", - view: "https://t-pen.org/TPEN/", - description: "Allows users to transcribe manuscripts by aligning text with scanned images for research and accuracy." - }, - { - label: "Adno", - icon: "./images/adno-logo.png", - view: "https://w.adno.app/", - description: "A tool for viewing and editing IIIF and static images within archives and heritage collections." - }, - { - label: "Universal Viewer", - icon: "./images/uv-logo.png", - view: "https://universalviewer.io/", - description: "A viewer for web objects, allowing users to share their media with the world." - } + { + label: 'TinyNode', + icon: './images/rerum_logo.png', + view: 'https://tiny.rerum.io/', + description: + 'A flexible tool for interacting with objects from RERUM, allowing users to experiment with data.', + }, + { + label: 'Geolocating Web Annotation Tool', + icon: './images/rerum_logo.png', + view: 'https://geo.rerum.io/', + description: + 'Helps users annotate data with geolocation coordinates by selecting points on a map.', + }, + { + label: 'navPlace Object Tool', + icon: './images/rerum_logo.png', + view: 'https://geo.rerum.io/', + description: 'Allows interaction with place-based objects in a spatial context.', + }, + { + label: 'TPEN', + icon: './images/T-PEN_logo.png', + view: 'https://t-pen.org/TPEN/', + description: + 'Allows users to transcribe manuscripts by aligning text with scanned images for research and accuracy.', + }, + { + label: 'Adno', + icon: './images/adno-logo.png', + view: 'https://w.adno.app/', + description: + 'A tool for viewing and editing IIIF and static images within archives and heritage collections.', + }, + { + label: 'Universal Viewer', + icon: './images/uv-logo.png', + view: 'https://universalviewer.io/', + description: 'A viewer for web objects, allowing users to share their media with the world.', + }, ]; // export the tools catalog to be used in config.js -export default ToolsCatalog; \ No newline at end of file +export default ToolsCatalog; diff --git a/web/js/utilities.js b/web/js/utilities.js index 10b0473d..37add415 100644 --- a/web/js/utilities.js +++ b/web/js/utilities.js @@ -1,177 +1,203 @@ -/* - * Utility functions. These are functions used repeatedly by PLAYGROUND for various unit tasks. - * +/* + * Utility functions. These are functions used repeatedly by PLAYGROUND for various unit tasks. + * */ -import { default as PLAYGROUND } from 'https://centerfordigitalhumanities.github.io/rerum-playground/web/js/config.js' +import { default as PLAYGROUND } from 'https://centerfordigitalhumanities.github.io/rerum-playground/web/js/config.js'; const logger = { - fatal(msg) { - if (PLAYGROUND.LOGLEVEL > 0) console.error(`%c☠ ${msg}`, `color:crimson;font-weight:bold;font-size:2rem;`) - }, - error(msg) { - if (PLAYGROUND.LOGLEVEL > 1) console.error(`💣 ${msg}`) - }, - warn(msg) { - if (PLAYGROUND.LOGLEVEL > 2) console.warn(`⚠ ${msg}`) - }, - info(msg) { - if (PLAYGROUND.LOGLEVEL > 3) console.info(`ℹ %c${msg}`, `color:#061615;background:#3acabb;`) - }, - debug(msg) { - if (PLAYGROUND.LOGLEVEL > 4) console.debug(`🐞 ${msg}`) - }, - trace(msg) { - if (PLAYGROUND.LOGLEVEL > 5) console.trace(msg) + fatal(msg) { + if (PLAYGROUND.LOGLEVEL > 0) { + console.error(`%c☠ ${msg}`, `color:crimson;font-weight:bold;font-size:2rem;`); } -} + }, + error(msg) { + if (PLAYGROUND.LOGLEVEL > 1) { + console.error(`💣 ${msg}`); + } + }, + warn(msg) { + if (PLAYGROUND.LOGLEVEL > 2) { + console.warn(`⚠ ${msg}`); + } + }, + info(msg) { + if (PLAYGROUND.LOGLEVEL > 3) { + console.info(`ℹ %c${msg}`, `color:#061615;background:#3acabb;`); + } + }, + debug(msg) { + if (PLAYGROUND.LOGLEVEL > 4) { + console.debug(`🐞 ${msg}`); + } + }, + trace(msg) { + if (PLAYGROUND.LOGLEVEL > 5) { + console.trace(msg); + } + }, +}; /** * Logs any errors * @param {HTTPResponse} response from `fetch()` * @returns Promise(JSON) || Error */ -const handleHTTPError = (response, getAs = "json") => { - if (response.ok) return response[getAs]() - const errorMessages = { - 400: "Bad Request", - 401: "Request was unauthorized", - 403: "Forbidden to make request", - 404: "Not found", - 500: "Internal server error", - 503: "Server down time", - } - logger.warn(errorMessages[response.status] ?? `Unhandled HTTP Error ${response.status}`) - throw Error("HTTP Error: " + response.statusText) -} +const handleHTTPError = (response, getAs = 'json') => { + if (response.ok) { + return response[getAs](); + } + const errorMessages = { + 400: 'Bad Request', + 401: 'Request was unauthorized', + 403: 'Forbidden to make request', + 404: 'Not found', + 500: 'Internal server error', + 503: 'Server down time', + }; + logger.warn(errorMessages[response.status] ?? `Unhandled HTTP Error ${response.status}`); + throw Error('HTTP Error: ' + response.statusText); +}; const API = { - create: async (obj) => { - return fetch(PLAYGROUND.URLS.CREATE, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(obj) - }) - .then(handleHTTPError) - .catch(err => { return err }) - }, - update: async (obj) => { - return fetch(PLAYGROUND.URLS.UPDATE, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(obj) - }) - .then(handleHTTPError) - .catch(err => { return err }) - }, - overwrite: async (obj) => { - return fetch(PLAYGROUND.URLS.OVERWRITE, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(obj) - }) - .then(handleHTTPError) - .catch(err => { return err }) - }, - delete: async (uri) => { - return fetch(PLAYGROUND.URLS.DELETE, { - method: 'DELETE', - headers: { - 'Content-Type': 'text/plain' - }, - body: uri - }) - .then(handleHTTPError) - .catch(err => { return err }) - }, - query: async (obj) => { - return fetch(PLAYGROUND.URLS.QUERY, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(obj) - }) - .then(handleHTTPError) - .catch(err => { return err }) - }, - resolveJSON: async (uri) => { - return fetch(uri) - .then(handleHTTPError) - .catch(err => { return err }) - }, - resolveString: async (uri) => { - return fetch(uri) - .then(response => handleHTTPError(response, "text")) - .catch(err => { return err }) - } -} + create: async (obj) => { + return fetch(PLAYGROUND.URLS.CREATE, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(obj), + }) + .then(handleHTTPError) + .catch((err) => { + return err; + }); + }, + update: async (obj) => { + return fetch(PLAYGROUND.URLS.UPDATE, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(obj), + }) + .then(handleHTTPError) + .catch((err) => { + return err; + }); + }, + overwrite: async (obj) => { + return fetch(PLAYGROUND.URLS.OVERWRITE, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(obj), + }) + .then(handleHTTPError) + .catch((err) => { + return err; + }); + }, + delete: async (uri) => { + return fetch(PLAYGROUND.URLS.DELETE, { + method: 'DELETE', + headers: { + 'Content-Type': 'text/plain', + }, + body: uri, + }) + .then(handleHTTPError) + .catch((err) => { + return err; + }); + }, + query: async (obj) => { + return fetch(PLAYGROUND.URLS.QUERY, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(obj), + }) + .then(handleHTTPError) + .catch((err) => { + return err; + }); + }, + resolveJSON: async (uri) => { + return fetch(uri) + .then(handleHTTPError) + .catch((err) => { + return err; + }); + }, + resolveString: async (uri) => { + return fetch(uri) + .then((response) => handleHTTPError(response, 'text')) + .catch((err) => { + return err; + }); + }, +}; export default { - handleHTTPError, - logger, - API, - /** - * Broadcast a message about PLAYGROUND - */ - broadcast(event = {}, type = "message", element = document, obj = {}) { - - return element.dispatchEvent(new CustomEvent(type, { detail: Object.assign(obj, { target: event.target }), bubbles: true })) - }, + handleHTTPError, + logger, + API, + /** + * Broadcast a message about PLAYGROUND + */ + broadcast(event = {}, type = 'message', element = document, obj = {}) { + return element.dispatchEvent( + new CustomEvent(type, { detail: Object.assign(obj, { target: event.target }), bubbles: true }) + ); + }, - /** - * Behavior for when the user picks a tool. They may provide the data to take into that tool. - * When the user picks a tool/interface/technology, we need to load up the view for the user to interact with. - * We may want to handle internal views and external views separately. - * What if the user had done some stuff and would like to take that data to the view? It may not just be a URI. - */ - useTool: function (tool, data) { - return new Promise((res) => { - // Make this the active tool for the user to interact with. - document.location.href = tool.view - }) - }, + /** + * Behavior for when the user picks a tool. They may provide the data to take into that tool. + * When the user picks a tool/interface/technology, we need to load up the view for the user to interact with. + * We may want to handle internal views and external views separately. + * What if the user had done some stuff and would like to take that data to the view? It may not just be a URI. + */ + useTool: function (tool, data) { + return new Promise((res) => { + // Make this the active tool for the user to interact with. + document.location.href = tool.view; + }); + }, - /** - * Behavior for when the user picks an interface. They may provide the data to take into that interface. - */ - useInterface: function (inter, data) { - return new Promise((res) => { - // Make this the active interface for the user to interact with. - }) - }, + /** + * Behavior for when the user picks an interface. They may provide the data to take into that interface. + */ + useInterface: function (inter, data) { + return new Promise((res) => { + // Make this the active interface for the user to interact with. + }); + }, - /** - * Behavior for when the user picks a technology. They may provide the data to take into that technology. - */ - useTechnology: function (tech, data) { - return new Promise((res) => { - // Make this the active technology for the user to interact with. - }) - }, + /** + * Behavior for when the user picks a technology. They may provide the data to take into that technology. + */ + useTechnology: function (tech, data) { + return new Promise((res) => { + // Make this the active technology for the user to interact with. + }); + }, - /** - * Generate a thumbnail that represents an entry from the set of tools, interfaces, or technologies. - * - * @param {Object} entry - each tool/interface/technology object with properties like label, icon, view, and description - * @returns {String} HTML structure for the thumbnail - */ - thumbnailGenerator: (entry) => { - return ` + /** + * Generate a thumbnail that represents an entry from the set of tools, interfaces, or technologies. + * + * @param {Object} entry - each tool/interface/technology object with properties like label, icon, view, and description + * @returns {String} HTML structure for the thumbnail + */ + thumbnailGenerator: (entry) => { + return `
${entry.description}
`; - } - } - - - + }, +};