diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 2e5b82cc6..000000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-env node */ -require('@rushstack/eslint-patch/modern-module-resolution') - -module.exports = { - root: true, - plugins: [ - '@stylistic', - ], - extends: [ - 'eslint:recommended', - '@vue/eslint-config-typescript', - '@vue/eslint-config-prettier/skip-formatting', - 'plugin:vue/vue3-recommended', - ], - env: { - 'vue/setup-compiler-macros': true, - }, - rules: { - '@typescript-eslint/ban-ts-comment': 'off', - 'vue/script-setup-uses-vars': 'error', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-empty-interface': 'off', - '@stylistic/semi': ['error', 'never'], - '@stylistic/quotes': ['error', 'single', 'avoid-escape'], - 'vue/multi-word-component-names': [ - 'error', - { - ignores: ['Acl'], - }, - ], - 'vue/valid-v-slot': ['error', { 'allowModifiers': true }], - '@stylistic/object-curly-spacing': ['error', 'always'], - '@stylistic/no-multiple-empty-lines': ['error', { 'max': 1, 'maxEOF': 1 }], - '@stylistic/no-trailing-spaces': 'error', - '@stylistic/comma-dangle': ['error', 'only-multiline'], - '@stylistic/max-len': ['error', { - 'code': 120, - 'ignoreTrailingComments': true, - 'ignoreUrls': true, - 'ignoreRegExpLiterals': true, - 'ignorePattern': '^import .*', - }], - 'vue/no-template-target-blank': ['error'], - 'vue/block-order': ['error', { 'order': [['script', 'template'], 'style'] }], - 'vue/define-macros-order': ['error'], - 'vue/component-name-in-template-casing': ['error'], - 'vue/component-api-style': ['error'], - 'vue/prefer-define-options': ['error'], - 'vue/require-typed-ref': ['error'], - 'vue/no-setup-props-reactivity-loss': ['error'], - 'vue/no-ref-object-reactivity-loss': ['error'], - }, - parserOptions: { - ecmaVersion: 'latest', - }, - ignorePatterns: ['/docs/**/*'], -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08f1d2e40..1b7dc8082 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,12 +10,15 @@ on: permissions: contents: read +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: lint: strategy: matrix: node-version: - - '20' + - '24' platform: - ubuntu-latest @@ -44,3 +47,51 @@ jobs: - name: Run Security check run: yarn npm audit + + test: + strategy: + matrix: + node-version: + - '24' + platform: + - ubuntu-latest + + name: Test + runs-on: ${{ matrix.platform }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Enable corepack + run: corepack enable + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'yarn' + cache-dependency-path: 'yarn.lock' + + - name: Install dependencies + run: yarn install --immutable + + - name: Get Playwright version + id: playwright-version + run: echo "version=$(node -p "require('playwright/package.json').version")" >> $GITHUB_OUTPUT + + - name: Cache Playwright browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: playwright-chromium-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-${{ hashFiles('yarn.lock') }} + + - name: Install Playwright browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx playwright install chromium --with-deps + + - name: Run tests + run: yarn test:run + env: + CI: true diff --git a/.github/workflows/release-package.yml b/.github/workflows/release-package.yml index 48f20bcb2..c79802297 100644 --- a/.github/workflows/release-package.yml +++ b/.github/workflows/release-package.yml @@ -3,34 +3,67 @@ name: Node.js Package Release on: release: types: [created] + push: + branches: + - beta + +permissions: + contents: read + id-token: write + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: build-publish-pkg: - name: Build and push package on node 20 + name: Build and push package on node 24 runs-on: 'ubuntu-latest' + environment: npmjs-publish steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Check version change (beta only) + id: check + if: github.event_name == 'push' + uses: EndBug/version-check@v2 + with: + diff-search: true - name: Enable corepack + if: github.event_name == 'release' || (steps.check.outputs.changed == 'true' && contains(steps.check.outputs.version, 'beta')) run: corepack enable - name: Setup Node + if: github.event_name == 'release' || (steps.check.outputs.changed == 'true' && contains(steps.check.outputs.version, 'beta')) uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' cache: 'yarn' cache-dependency-path: 'yarn.lock' - name: Install dependencies + if: github.event_name == 'release' || (steps.check.outputs.changed == 'true' && contains(steps.check.outputs.version, 'beta')) run: yarn install --immutable + env: + SKIP_PLAYWRIGHT_INSTALL: true - name: Run Yarn Build + if: github.event_name == 'release' || (steps.check.outputs.changed == 'true' && contains(steps.check.outputs.version, 'beta')) run: yarn lib:build - name: Publish package to npmjs - env: - NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} + if: github.event_name == 'release' + run: | + if [ "${{ github.event.release.prerelease }}" = "true" ]; then + npm publish --access public --tag beta + else + npm publish --access public + fi + + - name: Publish beta package to npmjs + if: github.event_name == 'push' && steps.check.outputs.changed == 'true' && contains(steps.check.outputs.version, 'beta') run: | - npm config set //registry.npmjs.org/:_authToken $NODE_AUTH_TOKEN - npm publish --access public + npm publish --tag beta diff --git a/.gitignore b/.gitignore index 258857fa5..d3c5b15e9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ pnpm-debug.log* lerna-debug.log* node_modules +todo dist dist-ssr *.local @@ -25,3 +26,7 @@ dist-ssr *.njsproj *.sln *.sw? + +.playwright-cli +.vitest-attachments +todo \ No newline at end of file diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 000000000..62b2687b7 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,5 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "semi": false, + "singleQuote": true +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 000000000..99d107b9d --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,10 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["eslint", "typescript", "unicorn", "oxc", "vue"], + "env": { "browser": true }, + "categories": { "correctness": "error" }, + "rules": { + "no-unused-vars": ["error", { "caughtErrors": "none", "argsIgnorePattern": "^_" }], + "no-unused-expressions": "off" + } +} diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index f06235c46..000000000 --- a/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist diff --git a/.prettierrc.cjs b/.prettierrc.cjs deleted file mode 100644 index 10a50747d..000000000 --- a/.prettierrc.cjs +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - printWidth: 120, - semi: false, - singleQuote: true, - bracketSameLine: false, - trailingComma: 'es5', - singleAttributePerLine: true, -} diff --git a/.stylelintrc.cjs b/.stylelintrc.cjs index c802ac00a..676174d08 100644 --- a/.stylelintrc.cjs +++ b/.stylelintrc.cjs @@ -7,6 +7,8 @@ module.exports = { "selector-class-pattern": "^(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:\\[.+\\])?$", "no-descending-specificity": null, "no-duplicate-selectors": null, + "scss/double-slash-comment-whitespace-inside": null, "color-function-notation": ["modern", { "ignore": ["with-var-inside"] }], + "declaration-property-value-no-unknown": null, }, } diff --git a/.yarnrc.yml b/.yarnrc.yml index 91b1101f5..a1ceb033c 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1,5 +1,12 @@ +approvedGitRepositories: + - "**" + compressionLevel: mixed enableGlobalCache: false +enableScripts: true + nodeLinker: node-modules + +npmMinimalAgeGate: 0 diff --git a/copy.sh b/copy.sh index 9cc819d76..3c53eec7e 100755 --- a/copy.sh +++ b/copy.sh @@ -13,32 +13,85 @@ else exit 1 fi +# Target projects can be defined as: +# ADMIN_PROJECT=/path/to/admin-cms +# ADMIN_PROJECT=/path/to/admin-cms:/path/to/admin-ugc (colon separated) +# ADMIN_PROJECTS=("/path/to/admin-cms" "/path/to/admin-ugc") (bash array) +TARGETS=() + +if [[ -n "${ADMIN_PROJECTS+x}" ]]; then + TARGETS+=("${ADMIN_PROJECTS[@]}") +fi + +if [[ -n "${ADMIN_PROJECT+x}" ]]; then + if [[ "$(declare -p ADMIN_PROJECT 2>/dev/null)" == "declare -a"* ]]; then + TARGETS+=("${ADMIN_PROJECT[@]}") + else + IFS=':' read -r -a SPLIT_TARGETS <<< "${ADMIN_PROJECT}" + TARGETS+=("${SPLIT_TARGETS[@]}") + fi +fi + +# Drop empty entries (e.g. trailing colon) +FILTERED_TARGETS=() +for target in "${TARGETS[@]}"; do + [[ -n "${target}" ]] && FILTERED_TARGETS+=("${target}") +done +TARGETS=("${FILTERED_TARGETS[@]}") + # Ensure that paths are set in the sourced file -if [[ -z "$COMMON_ADMIN_PROJECT" || -z "$ADMIN_PROJECT" ]]; then +if [[ -z "${COMMON_ADMIN_PROJECT}" || ${#TARGETS[@]} -eq 0 ]]; then echo "Error: Paths not defined in the sourced file." exit 1 fi -# Check if directories exist -if [[ ! -d "${COMMON_ADMIN_PROJECT}" || ! -d "${ADMIN_PROJECT}" ]]; then - echo "Error: One or both of the specified directories do not exist." +# Check if source directory exists +if [[ ! -d "${COMMON_ADMIN_PROJECT}" ]]; then + echo "Error: Source directory ${COMMON_ADMIN_PROJECT} does not exist." exit 1 fi -# Create directories before copying files -mkdir -p "${ADMIN_PROJECT}/node_modules/@anzusystems/common-admin/" -mkdir -p "${ADMIN_PROJECT}/node_modules/@anzusystems/common-admin/dist" +if [[ ! -d "${COMMON_ADMIN_PROJECT}/dist" ]]; then + echo "Error: dist not found in ${COMMON_ADMIN_PROJECT}. Build it first." + exit 1 +fi -# Copy package.json from COMMON_ADMIN_PROJECT to ADMIN_PROJECT -if [[ -f "${COMMON_ADMIN_PROJECT}/package.json" ]]; then - cp "${COMMON_ADMIN_PROJECT}/package.json" "${ADMIN_PROJECT}/node_modules/@anzusystems/common-admin/" -else +if [[ ! -f "${COMMON_ADMIN_PROJECT}/package.json" ]]; then echo "Error: package.json not found in ${COMMON_ADMIN_PROJECT}." exit 1 fi -# Create dist directory before copying files -mkdir -p "${ADMIN_PROJECT}/node_modules/@anzusystems/common-admin/dist" +# Check if all target directories exist before copying anything +MISSING=0 +for target in "${TARGETS[@]}"; do + if [[ ! -d "${target}" ]]; then + echo "Error: Target directory ${target} does not exist." + MISSING=1 + fi +done +[[ ${MISSING} -eq 1 ]] && exit 1 + +for target in "${TARGETS[@]}"; do + PACKAGE_DIR="${target}/node_modules/@anzusystems/common-admin" + + # Wipe the previous dist first. `cp -r` only overwrites; it never removes, so chunks whose + # content hash changed piled up as orphans and the package ended up holding several builds at + # once. Scoped to this package's own dist directory, nothing else in node_modules is touched. + rm -rf "${PACKAGE_DIR}/dist" + + # Create directories before copying files + mkdir -p "${PACKAGE_DIR}/dist" + + # Copy package.json from COMMON_ADMIN_PROJECT to the target project + cp "${COMMON_ADMIN_PROJECT}/package.json" "${PACKAGE_DIR}/" + + cp -r "${COMMON_ADMIN_PROJECT}/dist/"* "${PACKAGE_DIR}/dist/" + + # Clear Vite's dependency pre-bundle cache so it picks up the new files + rm -rf "${target}/node_modules/.vite/deps/" + + # Touch trigger file so Vite plugin detects the update and does a full-reload + touch "${target}/.common-admin-updated" -# Use paths from sourced file -cp -r "${COMMON_ADMIN_PROJECT}/dist/"* "${ADMIN_PROJECT}/node_modules/@anzusystems/common-admin/dist/" + echo "Successfully copied release from ${COMMON_ADMIN_PROJECT}/dist to ${PACKAGE_DIR}/dist" +done diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 31e932b44..8e8be8476 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -39,7 +39,7 @@ export default { }) }, // @ts-ignore - enhanceApp({ app, router, siteData }) { + enhanceApp({ app, _router, _siteData }) { // ... app.use(i18n) app.use(vuetify) diff --git a/docs/styleguide/views/viewsDemoData.ts b/docs/styleguide/views/viewsDemoData.ts index 9dc105fdd..a0a881bcd 100644 --- a/docs/styleguide/views/viewsDemoData.ts +++ b/docs/styleguide/views/viewsDemoData.ts @@ -1,7 +1,9 @@ import { reactive, ref } from 'vue' // @ts-ignore import { makeFilterHelper } from '@/composables/filter/filterHelpers' +// @ts-ignore import type { Pagination } from '@/types/Pagination' +// @ts-ignore import type { FilterBag } from '@/types/Filter' // @ts-ignore import { useAlerts } from '@/composables/system/alerts' @@ -120,7 +122,7 @@ export function useLogSystem() { } } -export const fetchSiteList = (pagination: Pagination, filterBag: FilterBag) => { +export const fetchSiteList = (_pagination: Pagination, _filterBag: FilterBag) => { return Promise.resolve([]) } diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..f1fd32cdb --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,111 @@ +import stylistic from '@stylistic/eslint-plugin' +import pluginVue from 'eslint-plugin-vue' +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' +import vuetify from 'eslint-plugin-vuetify' +import oxlint from 'eslint-plugin-oxlint' +import { recommended as anzuRecommended } from './src/eslint/plugin.mjs' + +export default defineConfigWithVueTs( + { + name: 'app/files-to-lint', + files: ['**/*.{ts,mts,tsx,vue}'], + }, + { + name: 'app/files-to-ignore', + ignores: [ + '**/dist/**', + '**/dist-ssr/**', + '**/coverage/**', + '.stylelintrc.cjs', + '**/cypress/**', + '**/docs/**', + '.playwright-cli/**', + ], + }, + pluginVue.configs['flat/essential'], + pluginVue.configs['flat/strongly-recommended'], + pluginVue.configs['flat/recommended'], + vueTsConfigs.recommended, + anzuRecommended({ + deprecatedImports: { + mode: 'internal', + skipFiles: [ + 'src/lib.ts', + 'src/services/api/apiFetchByIds.ts', + 'src/components/form/AFormRemoteAutocompleteWithCached.vue', + 'src/components/filter/AFilterMixed.vue', + 'src/components/dam/assetSelect/composables/assetSelectListActions.ts', + ], + }, + }), + { + plugins: { + '@stylistic': stylistic, + }, + rules: { + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-empty-interface': 'off', + '@stylistic/semi': ['error', 'never'], + '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], + 'vue/multi-word-component-names': [ + 'error', + { + ignores: ['Acl'], + }, + ], + 'vue/valid-v-slot': ['error', { allowModifiers: true }], + '@stylistic/object-curly-spacing': ['error', 'always'], + '@stylistic/no-multiple-empty-lines': ['error', { max: 1, maxEOF: 1 }], + '@stylistic/no-trailing-spaces': 'error', + '@stylistic/comma-dangle': ['error', 'only-multiline'], + '@stylistic/max-len': [ + 'error', + { + code: 120, + ignoreTrailingComments: true, + ignoreUrls: true, + ignoreRegExpLiterals: true, + ignorePattern: '^import .*', + }, + ], + 'vue/no-template-target-blank': ['error'], + 'vue/block-order': ['error', { order: [['script', 'template'], 'style'] }], + 'vue/define-macros-order': ['error'], + 'vue/component-name-in-template-casing': ['error'], + 'vue/component-api-style': ['error'], + 'vue/prefer-define-options': ['error'], + 'vue/require-typed-ref': ['error'], + 'vue/no-setup-props-reactivity-loss': ['error'], + 'vue/no-ref-object-reactivity-loss': ['error'], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrors: 'none', + }, + ], + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + }, + }, + { + name: 'app/test-files', + files: ['**/*.test.{ts,js}', '**/*.spec.{ts,js}', '**/test/**/*.{ts,js}', '**/tests/**/*.{ts,js}'], + rules: { + // Relax rules for test files + '@stylistic/max-len': ['error', { code: 150 }], // Longer lines for test descriptions + '@typescript-eslint/no-explicit-any': 'off', // Allow any in tests for mocking + '@typescript-eslint/no-non-null-assertion': 'off', // Allow ! in tests + 'vue/one-component-per-file': 'off', // Allow multiple components in test files + '@stylistic/no-multiple-empty-lines': ['error', { max: 2 }], // More spacing in tests + // Keep important rules + '@stylistic/semi': ['error', 'never'], + '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], + '@stylistic/object-curly-spacing': ['error', 'always'], + }, + }, + ...vuetify.configs['flat/recommended-v4'], + oxlint.configs['flat/recommended'], +) diff --git a/package.json b/package.json index cbdba3658..b103a0f0e 100644 --- a/package.json +++ b/package.json @@ -1,112 +1,148 @@ { "name": "@anzusystems/common-admin", - "packageManager": "yarn@4.2.2", + "packageManager": "yarn@4.18.0", "files": [ - "dist" + "dist", + "src/eslint" ], "module": "./dist/common-admin.es.js", - "types": "./dist/common-admin.es.d.ts", + "types": "./dist/common-admin.d.ts", "exports": { ".": { - "import": "./dist/common-admin.es.js" + "import": "./dist/common-admin.js", + "types": "./dist/common-admin.d.ts" }, - "./styles": "./dist/style.css", + "./styles": "./dist/common-admin.css", + "./labs": { + "import": "./dist/labs.js", + "types": "./dist/labs.d.ts" + }, + "./eslint": "./src/eslint/plugin.mjs", "./*": "./*" }, - "version": "1.47.0-alpha59", - "type": "module", + "version": "1.47.0-beta.dev-1784059879-fix1", "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/anzusystems/common-admin.git" + }, "scripts": { + "prepare": "node -e \"if (process.env.SKIP_PLAYWRIGHT_INSTALL !== 'true') { require('child_process').execSync('playwright install chromium --with-deps', { stdio: 'inherit' }) }\"", "docs:dev": "vitepress dev docs", "docs:build-only": "vitepress build docs", "docs:build": "yarn build ; yarn docs:build-only", "docs:preview": "vitepress preview docs", "playground:dev": "vite", "playground:build": "vite build", + "playground:build:nm": "vite build --minify false", "build": "vite build --config vite.config.lib.ts", "build:dev": "vite build --minify false --sourcemap true --config vite.config.lib.ts", "lib:build": "vite build --config vite.config.lib.ts", - "ci": "run-s --print-name lint:tsc lint:eslint lint:stylelint", - "lint": "yarn ci", - "lint:tsc": "vue-tsc --noEmit -p tsconfig.app.json --composite false", - "lint:eslint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore", - "lint:eslint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "ci": "run-s --print-name lint:tsc lint:oxlint lint:eslint lint:stylelint", + "lint": "[ \"$*\" = \"--fix\" ] && yarn lint:fix || yarn ci", + "lint:fix": "run-s --print-name lint:tsc format lint:oxlint:fix lint:eslint:fix lint:stylelint:fix", + "lint:tsc": "NODE_OPTIONS=--max-old-space-size=4096 vue-tsc --build", + "lint:tsc:test": "NODE_OPTIONS=--max-old-space-size=4096 vue-tsc --build tsconfig.test.json", + "lint:eslint": "eslint", + "lint:eslint:fix": "eslint --fix", + "lint:oxlint": "oxlint .", + "lint:oxlint:fix": "oxlint . --fix", "lint:stylelint": "stylelint src/**/*.{scss,vue}", "lint:stylelint:fix": "stylelint src/**/*.{scss,vue} --fix", - "lint:prettier": "prettier -c \"src/**/*.{ts,vue}\"", - "lint:prettier:fix": "prettier -w \"src/**/*.{ts,vue}\"", - "test": "exit 0" + "format": "oxfmt src/", + "format:check": "oxfmt src/ --check", + "test": "vitest", + "test:run": "vitest run", + "test:ui": "vitest --ui" }, "devDependencies": { "@anzusystems/common-admin": "workspace:*", - "@intlify/unplugin-vue-i18n": "^4.0.0", - "@kyvg/vue3-notification": "^3.2.1", + "@intlify/unplugin-vue-i18n": "^11.2.5", + "@kyvg/vue3-notification": "^3.4.2", "@mdi/font": "^7.4.47", - "@rushstack/eslint-patch": "^1.10.2", - "@shikijs/vitepress-twoslash": "^1.5.1", - "@stylistic/eslint-plugin": "^2.0.0", - "@tsconfig/node18": "^18.2.4", - "@types/node": "^20.12.11", + "@microsoft/api-extractor": "^7.59.0", + "@sentry/vue": "^10.73.0", + "@shikijs/vitepress-twoslash": "^4.4.3", + "@stylistic/eslint-plugin": "^5.10.0", + "@tsconfig/node22": "^22.0.6", + "@types/node": "^24.13.3", "@types/rusha": "^0.8.3", - "@types/sortablejs": "^1.15.8", + "@types/sortablejs": "^1.15.9", "@types/webfontloader": "^1.6.38", - "@typescript-eslint/parser": "^7.8.0", - "@vitejs/plugin-vue": "^5.0.4", - "@vue/eslint-config-prettier": "^9.0.0", - "@vue/eslint-config-typescript": "^13.0.0", - "@vue/language-server": "^1.8.27", - "@vue/language-service": "^1.8.27", - "@vue/tsconfig": "^0.5.1", + "@vitejs/plugin-vue": "^6.0.8", + "@vitest/browser": "^4.1.11", + "@vitest/browser-playwright": "^4.1.11", + "@vitest/ui": "^4.1.11", + "@vue/compiler-dom": "3.5.42", + "@vue/devtools-api": "^8.2.1", + "@vue/eslint-config-typescript": "^14.9.0", + "@vue/language-server": "3.3.11", + "@vue/test-utils": "^2.5.0", + "@vue/tsconfig": "0.9.1", "@vuelidate/core": "^2.0.3", "@vuelidate/validators": "^2.0.4", - "@vueuse/core": "^10.9.0", - "@vueuse/integrations": "^10.9.0", - "axios": "1.6.8", - "cropperjs": "^1.6.2", - "dayjs": "1.11.11", - "eslint": "8.57.0", - "eslint-plugin-vue": "9.26.0", - "npm-run-all": "^4.1.5", - "pinia": "2.1.7", - "postcss": "^8.4.38", - "postcss-html": "^1.7.0", - "postcss-prefix-selector": "^1.16.1", - "prettier": "^3.2.5", + "@vueuse/core": "14.4.0", + "@vueuse/integrations": "14.4.0", + "axios": "1.20.0", + "cropperjs": "^1.6.3", + "dayjs": "1.11.23", + "eslint": "^10.9.1", + "eslint-plugin-oxlint": "1.80.0", + "eslint-plugin-vue": "10.10.0", + "eslint-plugin-vuetify": "^2.7.2", + "npm-run-all2": "^9.0.3", + "oxfmt": "^0.65.0", + "oxlint": "1.80.0", + "pinia": "4.0.3", + "playwright": "^1.62.1", + "postcss": "^8.5.26", + "postcss-html": "^2.0.0", + "postcss-prefix-selector": "^2.2.0", "rusha": "^0.8.14", - "sass": "^1.77.0", - "socket.io-client": "4.7.5", - "sortablejs": "^1.15.2", - "stylelint": "16.5.0", - "stylelint-config-recommended-vue": "^1.5.0", - "stylelint-config-standard-scss": "^13.1.0", - "typescript": "5.4.5", - "uuid": "^9.0.1", - "vite": "5.2.11", - "vite-plugin-dts": "^3.9.1", - "vite-plugin-vuetify": "^2.0.3", - "vitepress": "1.1.4", - "vue": "3.4.27", - "vue-i18n": "9.13.1", - "vue-router": "4.3.2", - "vue-tsc": "2.0.16", - "vuetify": "3.6.4", + "sass": "1.103.1", + "socket.io-client": "4.8.3", + "sortablejs": "^1.15.7", + "stylelint": "17.14.1", + "stylelint-config-html": "^2.0.0", + "stylelint-config-recommended": "^18.0.0", + "stylelint-config-recommended-vue": "^2.0.0", + "stylelint-config-standard-scss": "^17.0.0", + "typescript": "5.9.3", + "unplugin": "3.3.0", + "unplugin-dts": "^1.0.3", + "uuid": "^14.0.2", + "vite": "7.3.6", + "vite-plugin-vuetify": "^2.1.3", + "vitepress": "1.6.4", + "vitest": "^4.1.11", + "vue": "3.5.42", + "vue-eslint-parser": "^10.4.1", + "vue-i18n": "11.4.10", + "vue-router": "5.3.0", + "vue-tsc": "3.3.11", + "vuetify": "4.1.12", "webfontloader": "^1.6.28" }, "peerDependencies": { - "@vuelidate/core": "2.0.3", - "@vuelidate/validators": "2.0.4", - "@vueuse/core": "10.9.0", - "@vueuse/integrations": "10.9.0", - "axios": "1.6.8", - "dayjs": "1.11.11", - "pinia": "2.1.7", - "socket.io-client": "4.7.5", - "vue": "3.4.27", - "vue-i18n": "9.13.1", - "vue-router": "4.3.2", - "vuetify": "3.6.3" + "@sentry/vue": "^10.0.0", + "@vuelidate/core": "^2.0.3", + "@vuelidate/validators": "^2.0.4", + "@vueuse/core": "^14.1.0", + "@vueuse/integrations": "^14.1.0", + "axios": "^1.13.4", + "dayjs": "^1.11.19", + "pinia": "^4.0.2", + "socket.io-client": "^4.8.3", + "unplugin": "^3.0.0", + "vue": "^3.5.27", + "vue-i18n": "^11.2.8", + "vue-router": "^5.0.1", + "vuetify": "^4.0.0" }, "peerDependenciesMeta": { + "@sentry/vue": { + "optional": true + }, "@vueuse/core": { "optional": true }, diff --git a/src/AnzuSystemsCommonAdmin.ts b/src/AnzuSystemsCommonAdmin.ts index aab43a140..c1551578d 100644 --- a/src/AnzuSystemsCommonAdmin.ts +++ b/src/AnzuSystemsCommonAdmin.ts @@ -1,43 +1,50 @@ import type { App, DeepReadonly, Ref, UnwrapRef } from 'vue' import type { AnzuUser } from '@/types/AnzuUser' -import type { AclValue } from '@/types/Permission' import Acl from '@/components/permission/Acl.vue' import Notification from '@kyvg/vue3-notification' import type { LanguageCode } from '@/composables/languageSettings' -import { - AvailableLanguagesSymbol, - CurrentUserSymbol, - CustomAclResolverSymbol, - DefaultLanguageSymbol, -} from '@/components/injectionKeys' +import { AvailableLanguagesSymbol, DefaultLanguageSymbol } from '@/components/injectionKeys' import type { AxiosInstance } from 'axios' import { initCommonAdminImageOptions } from '@/components/damImage/composables/commonAdminImageOptions' import { initCommonAdminCoreDamOptions } from '@/components/dam/assetSelect/composables/commonAdminCoreDamOptions' import { initCommonAdminCollabOptions } from '@/components/collab/composables/commonAdminCollabOptions' +import type { IntegerId } from '@/types/common' +import type { ImageAware, ImageCreateUpdateAware } from '@/types/ImageAware' +import type { + UploadMetadataToImageMapFn, + AssetSelectMetadataToImageMapFn, +} from '@/components/damImage/uploadQueue/composables/metadataToImageMap' -export type PluginOptions = { - currentUser: CurrentUserType +export type PluginOptions = { languages: { available: LanguageCode[]; default: LanguageCode } - customAclResolver?: CustomAclResolver coreDam?: CommonAdminCoreDamOptions image?: CommonAdminImageOptions collab?: CommonAdminCollabOptions } -export type CustomAclResolver = - | undefined - | { - can?: (acl: T, subject?: object) => boolean - canOwner?: (subject: object) => boolean - } - export type CurrentUserType = DeepReadonly>> export interface CommonAdminImageConfig { imageClient: () => AxiosInstance previewDomain: string + previewDomainOriginal: string width: number height: number + imageApi?: { + fetchImage: (client: () => AxiosInstance, id: IntegerId) => Promise + createImage: (client: () => AxiosInstance, data: ImageCreateUpdateAware) => Promise + updateImage: ( + client: () => AxiosInstance, + id: IntegerId, + data: ImageCreateUpdateAware, + ) => Promise + deleteImage: (client: () => AxiosInstance, id: IntegerId) => Promise + fetchImageListByIds: (client: () => AxiosInstance, ids: IntegerId[]) => Promise + bulkUpdateImages: ( + client: () => AxiosInstance, + items: ImageCreateUpdateAware[], + ) => Promise + } } export type CommonAdminImageOptions = @@ -46,8 +53,30 @@ export type CommonAdminImageOptions = configs: { [key: string]: CommonAdminImageConfig } } +export interface ImageFieldValidationConfig { + required?: boolean + min?: number + max?: number +} + export interface CommonAdminCoreDamConfig { damClient: () => AxiosInstance + endPointAsset?: string + endPointImage?: string + endPointRoi?: string + mainFileSingleUseEnabled?: boolean + showSourceEnabled?: boolean + showFileInfoEnabled?: boolean + sourceLabel?: string + editAssetLabel?: string + addFromDamLabel?: string + replaceFromDamLabel?: string + descriptionValidation?: ImageFieldValidationConfig + sourceValidation?: ImageFieldValidationConfig + customUploadMetadataToImageMap?: UploadMetadataToImageMapFn + customAssetSelectMetadataToImageMap?: AssetSelectMetadataToImageMapFn + assetListEnabledFilters?: string[] + simpleAssetSidebar?: boolean } export type CommonAdminCoreDamOptions = @@ -56,23 +85,21 @@ export type CommonAdminCoreDamOptions = configs: { [key: string]: CommonAdminCoreDamConfig } apiTimeout: number uploadStatusFallback: boolean + adminDomain: string notification: { enabled: boolean webSocketUrl: string } } -export type CommonAdminCollabOptions = - | { - enabled: boolean - disableCollabFieldDataChangeListener: boolean - socketUrl: string - } +export type CommonAdminCollabOptions = { + enabled: boolean + socketUrl: string + beforeReconnect: () => Promise +} export default { - install(app: App, options: PluginOptions): void { - app.provide(CurrentUserSymbol, options.currentUser) - app.provide(CustomAclResolverSymbol, options.customAclResolver) + install(app: App, options: PluginOptions): void { app.provide(AvailableLanguagesSymbol, options.languages.available) app.provide(DefaultLanguageSymbol, options.languages.default) app.component('Acl', Acl) diff --git a/src/App.vue b/src/App.vue index 906e99f06..361158f87 100644 --- a/src/App.vue +++ b/src/App.vue @@ -14,7 +14,7 @@ import logoNoText from '@/assets/logo-ca-no-text.svg' import { initLanguageMessagesLoaded } from '@/playground/system/loadLanguageMessages' import { useDropzoneGlobalDragState } from '@/components/file/composables/dropzone' import { initDamNotifications } from '@/components/damImage/uploadQueue/composables/damNotifications' -import { updateDamCurrentUser } from '@/components/damImage/composables/damCurrentUser' +import { defineAuth } from '@/composables/auth/defineAuth' import { damClient } from '@/playground/mock/coreDamClient' const route = useRoute() @@ -44,7 +44,10 @@ const { openConnection } = initDamNotifications() onMounted(async () => { openConnection() initGlobalDragState() - await updateDamCurrentUser(damClient) + + const { useCurrentUser } = defineAuth('dam') + const { fetchCurrentUser } = useCurrentUser('dam') + await fetchCurrentUser(damClient, '/adm/users/current') ready.value = true }) diff --git a/src/components/AAdminSwitcher.vue b/src/components/AAdminSwitcher.vue index 80eeac186..d94bf1146 100644 --- a/src/components/AAdminSwitcher.vue +++ b/src/components/AAdminSwitcher.vue @@ -9,7 +9,7 @@ const props = withDefaults( }>(), { configUrl: null, - } + }, ) const { t } = useI18n() @@ -89,10 +89,10 @@ onMounted(async () => { justify-content: center; flex-direction: column; text-decoration: none; - color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity)); + color: rgb(var(--v-theme-on-surface), var(--v-hover-opacity)); &:hover { - background-color: rgba(var(--v-theme-on-surface), 10%); + background-color: rgb(var(--v-theme-on-surface), 10%); border-radius: 8px; } @@ -101,7 +101,7 @@ onMounted(async () => { font-weight: normal; font-size: 14px; line-height: 14px; - color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity)); + color: rgb(var(--v-theme-on-surface), var(--v-high-emphasis-opacity)); padding: 8px 0; } diff --git a/src/components/AAlerts.vue b/src/components/AAlerts.vue index 988a553e0..724dfa97e 100644 --- a/src/components/AAlerts.vue +++ b/src/components/AAlerts.vue @@ -14,7 +14,7 @@ withDefaults( position: 'top center', width: '50%', customClass: undefined, - } + }, ) @@ -24,19 +24,17 @@ withDefaults( :group="group" :position="position" :width="width" - :class="customClass" + :class="['a-alerts', customClass]" >