Compare commits

..
18 changed files with 307 additions and 231 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
name: Bug Report
description: Found something you weren't expecting? Report it here!
labels: kind/bug
type: Bug
body:
- type: markdown
attributes:
@@ -49,4 +49,4 @@ body:
id: screenshots
attributes:
label: Screenshots
description: If this issue involves the Web Interface, please provide one or more screenshots
description: If this issue involves the Web Interface, please provide one or more screenshots
+1 -4
View File
@@ -1,4 +1 @@
declare module 'postcss-easing-gradients' {
import postcssEasingGradients from 'postcss-easing-gradients'
export default postcssEasingGradients
}
declare module 'postcss-easing-gradients';
+3 -3
View File
@@ -116,7 +116,7 @@
"@faker-js/faker": "9.4.0",
"@histoire/plugin-screenshot": "0.17.17",
"@histoire/plugin-vue": "0.17.17",
"@tsconfig/node22": "22.0.0",
"@tsconfig/node24": "24.0.0",
"@types/codemirror": "5.60.15",
"@types/flexsearch": "0.7.6",
"@types/is-touch-device": "1.0.3",
@@ -130,7 +130,7 @@
"@vue/tsconfig": "0.7.0",
"autoprefixer": "10.4.20",
"browserslist": "4.24.4",
"caniuse-lite": "1.0.30001692",
"caniuse-lite": "1.0.30001695",
"csstype": "3.1.3",
"cypress": "14.0.0",
"esbuild": "0.24.2",
@@ -152,7 +152,7 @@
"vite-plugin-sentry": "1.4.0",
"vite-plugin-vue-devtools": "7.7.0",
"vite-svg-loader": "5.1.0",
"vitest": "3.0.1",
"vitest": "3.0.2",
"vue-tsc": "2.2.0",
"wait-on": "8.0.2",
"workbox-cli": "7.3.0"
+94 -80
View File
@@ -212,9 +212,9 @@ importers:
'@histoire/plugin-vue':
specifier: 0.17.17
version: 0.17.17(histoire@0.17.17(@types/node@22.10.7)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)))(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0))(vue@3.5.13(typescript@5.7.3))
'@tsconfig/node22':
specifier: 22.0.0
version: 22.0.0
'@tsconfig/node24':
specifier: 24.0.0
version: 24.0.0
'@types/codemirror':
specifier: 5.60.15
version: 5.60.15
@@ -255,8 +255,8 @@ importers:
specifier: 4.24.4
version: 4.24.4
caniuse-lite:
specifier: 1.0.30001692
version: 1.0.30001692
specifier: 1.0.30001695
version: 1.0.30001695
csstype:
specifier: 3.1.3
version: 3.1.3
@@ -321,8 +321,8 @@ importers:
specifier: 5.1.0
version: 5.1.0(vue@3.5.13(typescript@5.7.3))
vitest:
specifier: 3.0.1
version: 3.0.1(@types/node@22.10.7)(happy-dom@16.6.0)(jiti@1.21.6)(jsdom@20.0.3)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
specifier: 3.0.2
version: 3.0.2(@types/node@22.10.7)(happy-dom@16.6.0)(jiti@1.21.6)(jsdom@20.0.3)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
vue-tsc:
specifier: 2.2.0
version: 2.2.0(typescript@5.7.3)
@@ -1535,6 +1535,7 @@ packages:
'@faker-js/faker@9.4.0':
resolution: {integrity: sha512-85+k0AxaZSTowL0gXp8zYWDIrWclTbRPg/pm/V0dSFZ6W6D4lhcG3uuZl4zLsEKfEvs69xDbLN2cHQudwp95JA==}
engines: {node: '>=18.0.0', npm: '>=9.0.0'}
deprecated: Please update to a newer version
'@floating-ui/core@1.6.7':
resolution: {integrity: sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==}
@@ -1643,22 +1644,26 @@ packages:
resolution: {integrity: sha512-NAmhw1l/llM0HZRpagR/ChJTNymW4ll6/4EDSJML5c8L5Hl/+k6UyF8EIgE6DeHpfheQujkSRngauViHqq6jJQ==}
engines: {node: '>= 16'}
'@intlify/message-compiler@11.0.0-rc.1':
resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==}
engines: {node: '>= 16'}
'@intlify/message-compiler@11.0.1':
resolution: {integrity: sha512-5RFH8x+Mn3mbjcHXnb6KCXGiczBdiQkWkv99iiA0JpKrNuTAQeW59Pjq/uObMB0eR0shnKYGTkIJxum+DbL3sw==}
engines: {node: '>= 16'}
'@intlify/shared@11.0.0-rc.1':
resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==}
engines: {node: '>= 16'}
'@intlify/message-compiler@12.0.0-alpha.4':
resolution: {integrity: sha512-F4yHuJzI2ZXtXTW2B1VhuNULAOjR5bniFSC/Z0rLWRYPO43pVgKLu+FPDoJq13OjTFq4X5hx2s855M4gqPU/pA==}
engines: {node: '>= 22'}
'@intlify/shared@11.0.1':
resolution: {integrity: sha512-lH164+aDDptHZ3dBDbIhRa1dOPQUp+83iugpc+1upTOWCnwyC1PVis6rSWNMMJ8VQxvtHQB9JMib48K55y0PvQ==}
engines: {node: '>= 16'}
'@intlify/shared@11.4.12':
resolution: {integrity: sha512-ojfehaXMFrGFcSnK3cJPaMxlrSXT8FwZUG9H4i+WKX4OFD4Yl1WKdTmfwoC0REnwIfTrnt1QhRtd8kwi/te6bQ==}
engines: {node: '>= 22'}
'@intlify/shared@12.0.0-alpha.4':
resolution: {integrity: sha512-MfunEN3/yQjD1Q9HnD4Qtvg/qLYnR+tK3mEz/EEvikJO+Hu8ICe7Rqh6uusLSjpWy6ECVt2p5HGXlYA12UBweA==}
engines: {node: '>= 22'}
'@intlify/unplugin-vue-i18n@6.0.3':
resolution: {integrity: sha512-9ZDjBlhUHtgjRl23TVcgfJttgu8cNepwVhWvOv3mUMRDAhjW0pur1mWKEUKr1I8PNwE4Gvv2IQ1xcl4RL0nG0g==}
engines: {node: '>= 18'}
@@ -2343,8 +2348,8 @@ packages:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
'@tsconfig/node22@22.0.0':
resolution: {integrity: sha512-twLQ77zevtxobBOD4ToAtVmuYrpeYUh3qh+TEp+08IWhpsrIflVHqQ1F1CiPxQGL7doCdBIOOCF+1Tm833faNg==}
'@tsconfig/node24@24.0.0':
resolution: {integrity: sha512-3/6Cr4dELEAucgPIr6ufY7yBYMi4ttZFOewABADNZ+bm4DUZl4dup2VBcBeP1ejj3QblrWVWux/1XcRv/ZVikA==}
'@types/chrome@0.0.270':
resolution: {integrity: sha512-ADvkowV7YnJfycZZxL2brluZ6STGW+9oKG37B422UePf2PCXuFA/XdERI0T18wtuWPx0tmFeZqq6MOXVk1IC+Q==}
@@ -2514,11 +2519,11 @@ packages:
vite: ^5.0.0 || ^6.0.0
vue: ^3.2.25
'@vitest/expect@3.0.1':
resolution: {integrity: sha512-oPrXe8dwvQdzUxQFWwibY97/smQ6k8iPVeSf09KEvU1yWzu40G6naHExY0lUgjnTPWMRGQOJnhMBb8lBu48feg==}
'@vitest/expect@3.0.2':
resolution: {integrity: sha512-dKSHLBcoZI+3pmP5hiZ7I5grNru2HRtEW8Z5Zp4IXog8QYcxhlox7JUPyIIFWfN53+3HW3KPLIl6nSzUGgKSuQ==}
'@vitest/mocker@3.0.1':
resolution: {integrity: sha512-5letLsVdFhReCPws/SNwyekBCyi4w2IusycV4T7eVdt2mfellS2yKDrEmnE5KPCHr0Ez5xCZVJbJws3ckuNNgQ==}
'@vitest/mocker@3.0.2':
resolution: {integrity: sha512-Hr09FoBf0jlwwSyzIF4Xw31OntpO3XtZjkccpcBf8FeVW3tpiyKlkeUzxS/txzHqpUCNIX157NaTySxedyZLvA==}
peerDependencies:
msw: ^2.4.9
vite: ^5.0.0 || ^6.0.0
@@ -2528,20 +2533,20 @@ packages:
vite:
optional: true
'@vitest/pretty-format@3.0.1':
resolution: {integrity: sha512-FnyGQ9eFJ/Dnqg3jCvq9O6noXtxbZhOlSvNLZsCGJxhsGiZ5LDepmsTCizRfyGJt4Q6pJmZtx7rO/qqr9R9gDA==}
'@vitest/pretty-format@3.0.2':
resolution: {integrity: sha512-yBohcBw/T/p0/JRgYD+IYcjCmuHzjC3WLAKsVE4/LwiubzZkE8N49/xIQ/KGQwDRA8PaviF8IRO8JMWMngdVVQ==}
'@vitest/runner@3.0.1':
resolution: {integrity: sha512-LfVbbYOduTVx8PnYFGH98jpgubHBefIppbPQJBSlgjnRRlaX/KR6J46htECUHpf+ElJZ4xxssAfEz/Cb2iIMYA==}
'@vitest/runner@3.0.2':
resolution: {integrity: sha512-GHEsWoncrGxWuW8s405fVoDfSLk6RF2LCXp6XhevbtDjdDme1WV/eNmUueDfpY1IX3MJaCRelVCEXsT9cArfEg==}
'@vitest/snapshot@3.0.1':
resolution: {integrity: sha512-ZYV+iw2lGyc4QY2xt61b7Y3NJhSAO7UWcYWMcV0UnMrkXa8hXtfZES6WAk4g7Jr3p4qJm1P0cgDcOFyY5me+Ug==}
'@vitest/snapshot@3.0.2':
resolution: {integrity: sha512-h9s67yD4+g+JoYG0zPCo/cLTabpDqzqNdzMawmNPzDStTiwxwkyYM1v5lWE8gmGv3SVJ2DcxA2NpQJZJv9ym3g==}
'@vitest/spy@3.0.1':
resolution: {integrity: sha512-HnGJB3JFflnlka4u7aD0CfqrEtX3FgNaZAar18/KIhfo0r/WADn9PhBfiqAmNw4R/xaRcLzLPFXDwEQV1vHlJA==}
'@vitest/spy@3.0.2':
resolution: {integrity: sha512-8mI2iUn+PJFMT44e3ISA1R+K6ALVs47W6eriDTfXe6lFqlflID05MB4+rIFhmDSLBj8iBsZkzBYlgSkinxLzSQ==}
'@vitest/utils@3.0.1':
resolution: {integrity: sha512-i+Gm61rfIeSitPUsu4ZcWqucfb18ShAanRpOG6KlXfd1j6JVK5XxO2Z6lEmfjMnAQRIvvLtJ3JByzDTv347e8w==}
'@vitest/utils@3.0.2':
resolution: {integrity: sha512-Qu01ZYZlgHvDP02JnMBRpX43nRaZtNpIzw3C1clDXmn8eakgX6iQVGzTQ/NjkIr64WD8ioqOjkaYRVvHQI5qiw==}
'@volar/language-core@2.4.11':
resolution: {integrity: sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==}
@@ -2947,8 +2952,8 @@ packages:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
caniuse-lite@1.0.30001692:
resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==}
caniuse-lite@1.0.30001695:
resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==}
capital-case@1.0.4:
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
@@ -3573,6 +3578,7 @@ packages:
eslint@9.18.0:
resolution: {integrity: sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true
peerDependencies:
jiti: '*'
@@ -3883,11 +3889,12 @@ packages:
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
global-dirs@2.1.0:
resolution: {integrity: sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==}
@@ -4691,6 +4698,7 @@ packages:
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
deprecated: Use your platform's native DOMException instead
node-fetch@2.6.7:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
@@ -5313,7 +5321,7 @@ packages:
puppeteer@13.7.0:
resolution: {integrity: sha512-U1uufzBjz3+PkpCxFrWzh4OrMIdIb2ztzCu0YEPfRHjHswcSwHZswnK+WdsOQJsRV8WeTg3jLhJR4D867+fjsA==}
engines: {node: '>=10.18.1'}
deprecated: < 22.8.2 is no longer supported
deprecated: < 24.15.0 is no longer supported
qs@6.13.0:
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
@@ -5753,6 +5761,7 @@ packages:
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
deprecated: The work that was done in this beta branch won't be included in future versions
sourcemap-codec@1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
@@ -6231,6 +6240,7 @@ packages:
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
hasBin: true
validate-npm-package-license@3.0.4:
@@ -6253,8 +6263,8 @@ packages:
engines: {node: '>=v14.18.0'}
hasBin: true
vite-node@3.0.1:
resolution: {integrity: sha512-PoH9mCNsSZQXl3gdymM5IE4WR0k0WbnFd89nAyyDvltF2jVGdFcI8vpB1PBdKTcjAR7kkYiHSlIO68X/UT8Q1A==}
vite-node@3.0.2:
resolution: {integrity: sha512-hsEQerBAHvVAbv40m3TFQe/lTEbOp7yDpyqMJqr2Tnd+W58+DEYOt+fluQgekOePcsNBmR77lpVAnIU2Xu4SvQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
@@ -6373,15 +6383,15 @@ packages:
yaml:
optional: true
vitest@3.0.1:
resolution: {integrity: sha512-SWKoSAkxtFHqt8biR3eN53dzmeWkigEpyipqfblcsoAghVvoFMpxQEj0gc7AajMi6Ra49fjcTN6v4AxklmS4aQ==}
vitest@3.0.2:
resolution: {integrity: sha512-5bzaHakQ0hmVVKLhfh/jXf6oETDBtgPo8tQCHYB+wftNgFJ+Hah67IsWc8ivx4vFL025Ow8UiuTf4W57z4izvQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
'@vitest/browser': 3.0.1
'@vitest/ui': 3.0.1
'@vitest/browser': 3.0.2
'@vitest/ui': 3.0.2
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
@@ -6436,6 +6446,7 @@ packages:
vue-i18n@11.0.1:
resolution: {integrity: sha512-pWAT8CusK8q9/EpN7V3oxwHwxWm6+Kp2PeTZmRGvdZTkUzMQDpbbmHp0TwQ8xw04XKm23cr6B4GL72y3W8Yekg==}
engines: {node: '>= 16'}
deprecated: This version is NOT deprecated. Previous deprecation was a mistake.
peerDependencies:
vue: ^3.0.0
@@ -6511,6 +6522,7 @@ packages:
whatwg-encoding@2.0.0:
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
engines: {node: '>=12'}
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-mimetype@3.0.0:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
@@ -8105,8 +8117,8 @@ snapshots:
'@intlify/bundle-utils@10.0.0(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))':
dependencies:
'@intlify/message-compiler': 11.0.0-rc.1
'@intlify/shared': 11.0.0-rc.1
'@intlify/message-compiler': 12.0.0-alpha.4
'@intlify/shared': 12.0.0-alpha.4
acorn: 8.14.0
escodegen: 2.1.0
estree-walker: 2.0.2
@@ -8122,26 +8134,28 @@ snapshots:
'@intlify/message-compiler': 11.0.1
'@intlify/shared': 11.0.1
'@intlify/message-compiler@11.0.0-rc.1':
dependencies:
'@intlify/shared': 11.0.0-rc.1
source-map-js: 1.2.1
'@intlify/message-compiler@11.0.1':
dependencies:
'@intlify/shared': 11.0.1
source-map-js: 1.2.1
'@intlify/shared@11.0.0-rc.1': {}
'@intlify/message-compiler@12.0.0-alpha.4':
dependencies:
'@intlify/shared': 12.0.0-alpha.4
source-map-js: 1.2.1
'@intlify/shared@11.0.1': {}
'@intlify/shared@11.4.12': {}
'@intlify/shared@12.0.0-alpha.4': {}
'@intlify/unplugin-vue-i18n@6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.18.0(jiti@1.21.6))(rollup@4.30.1)(typescript@5.7.3)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.18.0(jiti@1.21.6))
'@intlify/bundle-utils': 10.0.0(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))
'@intlify/shared': 11.0.1
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.0.1)(@vue/compiler-dom@3.5.13)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))
'@intlify/shared': 11.4.12
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.4.12)(@vue/compiler-dom@3.5.13)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))
'@rollup/pluginutils': 5.1.3(rollup@4.30.1)
'@typescript-eslint/scope-manager': 8.18.2
'@typescript-eslint/typescript-estree': 8.18.2(typescript@5.7.3)
@@ -8163,11 +8177,11 @@ snapshots:
- supports-color
- typescript
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.0.1)(@vue/compiler-dom@3.5.13)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))':
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.4.12)(@vue/compiler-dom@3.5.13)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@babel/parser': 7.26.1
optionalDependencies:
'@intlify/shared': 11.0.1
'@intlify/shared': 11.4.12
'@vue/compiler-dom': 3.5.13
vue: 3.5.13(typescript@5.7.3)
vue-i18n: 11.0.1(vue@3.5.13(typescript@5.7.3))
@@ -8776,7 +8790,7 @@ snapshots:
'@trysound/sax@0.2.0': {}
'@tsconfig/node22@22.0.0': {}
'@tsconfig/node24@24.0.0': {}
'@types/chrome@0.0.270':
dependencies:
@@ -8978,43 +8992,43 @@ snapshots:
vite: 6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
vue: 3.5.13(typescript@5.7.3)
'@vitest/expect@3.0.1':
'@vitest/expect@3.0.2':
dependencies:
'@vitest/spy': 3.0.1
'@vitest/utils': 3.0.1
'@vitest/spy': 3.0.2
'@vitest/utils': 3.0.2
chai: 5.1.2
tinyrainbow: 2.0.0
'@vitest/mocker@3.0.1(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0))':
'@vitest/mocker@3.0.2(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0))':
dependencies:
'@vitest/spy': 3.0.1
'@vitest/spy': 3.0.2
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
vite: 6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
'@vitest/pretty-format@3.0.1':
'@vitest/pretty-format@3.0.2':
dependencies:
tinyrainbow: 2.0.0
'@vitest/runner@3.0.1':
'@vitest/runner@3.0.2':
dependencies:
'@vitest/utils': 3.0.1
'@vitest/utils': 3.0.2
pathe: 2.0.1
'@vitest/snapshot@3.0.1':
'@vitest/snapshot@3.0.2':
dependencies:
'@vitest/pretty-format': 3.0.1
'@vitest/pretty-format': 3.0.2
magic-string: 0.30.17
pathe: 2.0.1
'@vitest/spy@3.0.1':
'@vitest/spy@3.0.2':
dependencies:
tinyspy: 3.0.2
'@vitest/utils@3.0.1':
'@vitest/utils@3.0.2':
dependencies:
'@vitest/pretty-format': 3.0.1
'@vitest/pretty-format': 3.0.2
loupe: 3.1.2
tinyrainbow: 2.0.0
@@ -9081,7 +9095,7 @@ snapshots:
'@vue/compiler-ssr': 3.5.13
'@vue/shared': 3.5.13
estree-walker: 2.0.2
magic-string: 0.30.14
magic-string: 0.30.17
postcss: 8.5.1
source-map-js: 1.2.1
@@ -9349,7 +9363,7 @@ snapshots:
autoprefixer@10.4.20(postcss@8.5.1):
dependencies:
browserslist: 4.24.4
caniuse-lite: 1.0.30001692
caniuse-lite: 1.0.30001695
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.0.1
@@ -9450,7 +9464,7 @@ snapshots:
browserslist@4.24.4:
dependencies:
caniuse-lite: 1.0.30001692
caniuse-lite: 1.0.30001695
electron-to-chromium: 1.5.73
node-releases: 2.0.19
update-browserslist-db: 1.1.1(browserslist@4.24.4)
@@ -9513,7 +9527,7 @@ snapshots:
camelcase@5.3.1: {}
caniuse-lite@1.0.30001692: {}
caniuse-lite@1.0.30001695: {}
capital-case@1.0.4:
dependencies:
@@ -13148,7 +13162,7 @@ snapshots:
- supports-color
- terser
vite-node@3.0.1(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0):
vite-node@3.0.2(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0):
dependencies:
cac: 6.7.14
debug: 4.4.0(supports-color@8.1.1)
@@ -13266,15 +13280,15 @@ snapshots:
terser: 5.31.6
yaml: 2.5.0
vitest@3.0.1(@types/node@22.10.7)(happy-dom@16.6.0)(jiti@1.21.6)(jsdom@20.0.3)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0):
vitest@3.0.2(@types/node@22.10.7)(happy-dom@16.6.0)(jiti@1.21.6)(jsdom@20.0.3)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0):
dependencies:
'@vitest/expect': 3.0.1
'@vitest/mocker': 3.0.1(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0))
'@vitest/pretty-format': 3.0.1
'@vitest/runner': 3.0.1
'@vitest/snapshot': 3.0.1
'@vitest/spy': 3.0.1
'@vitest/utils': 3.0.1
'@vitest/expect': 3.0.2
'@vitest/mocker': 3.0.2(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0))
'@vitest/pretty-format': 3.0.2
'@vitest/runner': 3.0.2
'@vitest/snapshot': 3.0.2
'@vitest/spy': 3.0.2
'@vitest/utils': 3.0.2
chai: 5.1.2
debug: 4.4.0(supports-color@8.1.1)
expect-type: 1.1.0
@@ -13286,7 +13300,7 @@ snapshots:
tinypool: 1.0.2
tinyrainbow: 2.0.0
vite: 6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
vite-node: 3.0.1(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
vite-node: 3.0.2(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.10.7
@@ -63,7 +63,7 @@ const motd = computed(() => configStore.motd)
const route = useRoute()
const {t} = useI18n({useScope: 'global'})
const title = computed(() => t(route.meta?.title as string || ''))
const title = computed(() => route.meta?.title ? t(route.meta.title as string) : '')
useTitle(() => title.value)
</script>
@@ -1,7 +1,10 @@
<template>
<div
:class="{ 'is-loading': projectService.loading, 'is-archived': currentProject?.isArchived}"
class="loader-container"
:class="{
'is-loading': isLoadingProject,
'is-archived': currentProject?.isArchived,
}"
>
<h1 class="project-title-print">
{{ getProjectTitle(currentProject) }}
@@ -37,43 +40,37 @@
</Message>
</CustomTransition>
<slot v-if="loadedProjectId" />
<slot v-if="!isLoadingProject" />
</div>
</template>
<script setup lang="ts">
import {computed, ref, watch} from 'vue'
import {useRoute} from 'vue-router'
import {computed} from 'vue'
import {useI18n} from 'vue-i18n'
import BaseButton from '@/components/base/BaseButton.vue'
import Message from '@/components/misc/Message.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import ProjectModel from '@/models/project'
import ProjectService from '@/services/project'
import {getProjectTitle} from '@/helpers/getProjectTitle'
import {saveProjectToHistory} from '@/modules/projectHistory'
import {useTitle} from '@/composables/useTitle'
import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects'
import type {IProject} from '@/modelTypes/IProject'
import type {IProjectView} from '@/modelTypes/IProjectView'
import {useI18n} from 'vue-i18n'
const props = defineProps<{
isLoadingProject: boolean,
projectId: IProject['id'],
viewId: IProjectView['id'],
}>()
const route = useRoute()
const {t} = useI18n()
const baseStore = useBaseStore()
const projectStore = useProjectStore()
const projectService = ref(new ProjectService())
const loadedProjectId = ref(0)
const currentProject = computed<IProject>(() => {
return typeof baseStore.currentProject === 'undefined' ? {
@@ -87,61 +84,6 @@ useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value)
const views = computed(() => projectStore.projects[props.projectId]?.views)
// watchEffect would be called every time the prop would get a value assigned, even if that value was the same as before.
// This resulted in loading and setting the project multiple times, even when navigating away from it.
// This caused wired bugs where the project background would be set on the home page but only right after setting a new
// project background and then navigating to home. It also highlighted the project in the menu and didn't allow changing any
// of it, most likely due to the rights not being properly populated.
watch(
() => props.projectId,
// loadProject
async (projectIdToLoad: number) => {
const projectData = {id: projectIdToLoad}
saveProjectToHistory(projectData)
// Don't load the project if we either already loaded it or aren't dealing with a project at all currently and
// the currently loaded project has the right set.
if (
(
projectIdToLoad === loadedProjectId.value ||
typeof projectIdToLoad === 'undefined' ||
projectIdToLoad === currentProject.value?.id
)
&& typeof currentProject.value !== 'undefined' && currentProject.value.maxRight !== null
) {
loadedProjectId.value = projectIdToLoad
return
}
console.debug('Loading project, $route.params =', route.params, `, loadedProjectId = ${loadedProjectId.value}, currentProject = `, currentProject.value)
// Set the current project to the one we're about to load so that the title is already shown at the top
loadedProjectId.value = 0
const projectFromStore = projectStore.projects[projectData.id]
if (projectFromStore) {
baseStore.handleSetCurrentProject({project: projectFromStore, currentProjectViewId: props.viewId})
}
// We create an extra project object instead of creating it in project.value because that would trigger a ui update which would result in bad ux.
const project = new ProjectModel(projectData)
try {
const loadedProject = await projectService.value.get(project)
baseStore.handleSetCurrentProject({project: loadedProject, currentProjectViewId: props.viewId})
} finally {
loadedProjectId.value = projectIdToLoad
}
},
{immediate: true},
)
watch(
() => props.viewId,
() => {
baseStore.setCurrentProjectViewId(props.viewId)
},
{immediate: true},
)
function getViewTitle(view: IProjectView) {
switch (view.title) {
case 'List':
@@ -1,6 +1,7 @@
<template>
<ProjectWrapper
class="project-gantt"
:is-loading-project="isLoadingProject"
:project-id="filters.projectId"
:view-id
>
@@ -95,6 +96,7 @@ import type {IProjectView} from '@/modelTypes/IProjectView'
type Options = Flatpickr.Options.Options
const props = defineProps<{
isLoadingProject: boolean,
route: RouteLocationNormalized
viewId: IProjectView['id']
}>()
@@ -1,6 +1,7 @@
<template>
<ProjectWrapper
class="project-kanban"
:is-loading-project="isLoadingProject"
:project-id="projectId"
:view-id
>
@@ -315,6 +316,7 @@ import TaskBucketService from '@/services/taskBucket'
import TaskBucketModel from '@/models/taskBucket'
const props = defineProps<{
isLoadingProject: boolean,
projectId: number,
viewId: IProjectView['id'],
}>()
@@ -1,6 +1,7 @@
<template>
<ProjectWrapper
class="project-list"
:is-loading-project="isLoadingProject"
:project-id="projectId"
:view-id
>
@@ -123,6 +124,7 @@ import TaskPositionService from '@/services/taskPosition'
import TaskPositionModel from '@/models/taskPosition'
const props = defineProps<{
isLoadingProject: boolean,
projectId: IProject['id'],
viewId: IProjectView['id'],
}>()
@@ -1,6 +1,7 @@
<template>
<ProjectWrapper
class="project-table"
:is-loading-project="isLoadingProject"
:project-id="projectId"
:view-id
>
@@ -298,6 +299,7 @@ import { camelCase } from 'change-case'
import {isSavedFilter} from '@/services/savedFilter'
const props = defineProps<{
isLoadingProject: boolean,
projectId: IProject['id'],
viewId: IProjectView['id'],
}>()
+2 -2
View File
@@ -1,6 +1,6 @@
import {computed} from 'vue'
import {computed, toValue} from 'vue'
import {useTitle as useTitleVueUse, toValue, type UseTitleOptions, type ReadonlyRefOrGetter, type MaybeRef, type MaybeRefOrGetter} from '@vueuse/core'
import {useTitle as useTitleVueUse, type UseTitleOptions, type ReadonlyRefOrGetter, type MaybeRef, type MaybeRefOrGetter} from '@vueuse/core'
export function useTitle(
newTitle:
+1 -1
View File
@@ -1124,7 +1124,7 @@
}
},
"date": {
"locale": "nb_NO",
"locale": "nn",
"altFormatLong": "d.m.y H:i",
"altFormatShort": "d.m.y"
},
+102 -51
View File
@@ -10,10 +10,65 @@ import {LINK_SHARE_HASH_PREFIX} from '@/constants/linkShareHash'
import {useAuthStore} from '@/stores/auth'
import {useBaseStore} from '@/stores/base'
import Register from '@/views/user/Register.vue'
import LinkSharingAuth from '@/views/sharing/LinkSharingAuth.vue'
import HomeComponent from '@/views/Home.vue'
import NotFoundComponent from '@/views/404.vue'
const About = () => import('@/views/About.vue')
// User Handling
import LoginComponent from '@/views/user/Login.vue'
import RegisterComponent from '@/views/user/Register.vue'
import OpenIdAuth from '@/views/user/OpenIdAuth.vue'
const DataExportDownload = () => import('@/views/user/DataExportDownload.vue')
// Tasks
import UpcomingTasksComponent from '@/views/tasks/ShowTasks.vue'
import LinkShareAuthComponent from '@/views/sharing/LinkSharingAuth.vue'
const TaskDetailView = () => import('@/views/tasks/TaskDetailView.vue')
// Team Handling
const ListTeamsComponent = () => import('@/views/teams/ListTeams.vue')
// Label Handling
const ListLabelsComponent = () => import('@/views/labels/ListLabels.vue')
const NewLabelComponent = () => import('@/views/labels/NewLabel.vue')
// Migration
const MigrationComponent = () => import('@/views/migrate/Migration.vue')
const MigrationHandlerComponent = () => import('@/views/migrate/MigrationHandler.vue')
// Project View
import ProjectView from '@/views/project/ProjectView.vue'
const ProjectInfo = () => import('@/views/project/ProjectInfo.vue')
// Project Settings
const ListProjects = () => import('@/views/project/ListProjects.vue')
const ProjectSettingEdit = () => import('@/views/project/settings/ProjectSettingsEdit.vue')
const ProjectSettingBackground = () => import('@/views/project/settings/ProjectSettingsBackground.vue')
const ProjectSettingDuplicate = () => import('@/views/project/settings/ProjectSettingsDuplicate.vue')
const ProjectSettingShare = () => import('@/views/project/settings/ProjectSettingsShare.vue')
const ProjectSettingWebhooks = () => import('@/views/project/settings/ProjectSettingsWebhooks.vue')
const ProjectSettingDelete = () => import('@/views/project/settings/ProjectSettingsDelete.vue')
const ProjectSettingArchive = () => import('@/views/project/settings/ProjectSettingsArchive.vue')
const ProjectSettingViews = () => import('@/views/project/settings/ProjectSettingsViews.vue')
// Saved Filters
const FilterNew = () => import('@/views/filters/FilterNew.vue')
const FilterEdit = () => import('@/views/filters/FilterEdit.vue')
const FilterDelete = () => import('@/views/filters/FilterDelete.vue')
const PasswordResetComponent = () => import('@/views/user/PasswordReset.vue')
const GetPasswordResetComponent = () => import('@/views/user/RequestPasswordReset.vue')
const UserSettingsComponent = () => import('@/views/user/Settings.vue')
const UserSettingsAvatarComponent = () => import('@/views/user/settings/Avatar.vue')
const UserSettingsCaldavComponent = () => import('@/views/user/settings/Caldav.vue')
const UserSettingsDataExportComponent = () => import('@/views/user/settings/DataExport.vue')
const UserSettingsDeletionComponent = () => import('@/views/user/settings/Deletion.vue')
const UserSettingsEmailUpdateComponent = () => import('@/views/user/settings/EmailUpdate.vue')
const UserSettingsGeneralComponent = () => import('@/views/user/settings/General.vue')
const UserSettingsPasswordUpdateComponent = () => import('@/views/user/settings/PasswordUpdate.vue')
const UserSettingsTOTPComponent = () => import('@/views/user/settings/TOTP.vue')
const UserSettingsApiTokensComponent = () => import('@/views/user/settings/ApiTokens.vue')
// Project Handling
const NewProjectComponent = () => import('@/views/project/NewProject.vue')
const EditTeamComponent = () => import('@/views/teams/EditTeam.vue')
const NewTeamComponent = () => import('@/views/teams/NewTeam.vue')
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@@ -35,7 +90,7 @@ const router = createRouter({
{
path: '/',
name: 'home',
component: () => import('@/views/Home.vue'),
component: HomeComponent,
},
{
path: '/:pathMatch(.*)*',
@@ -51,7 +106,7 @@ const router = createRouter({
{
path: '/login',
name: 'user.login',
component: () => import('@/views/user/Login.vue'),
component: LoginComponent,
meta: {
title: 'user.auth.login',
},
@@ -59,7 +114,7 @@ const router = createRouter({
{
path: '/get-password-reset',
name: 'user.password-reset.request',
component: () => import('@/views/user/RequestPasswordReset.vue'),
component: GetPasswordResetComponent,
meta: {
title: 'user.auth.resetPassword',
},
@@ -67,7 +122,7 @@ const router = createRouter({
{
path: '/password-reset',
name: 'user.password-reset.reset',
component: () => import('@/views/user/PasswordReset.vue'),
component: PasswordResetComponent,
meta: {
title: 'user.auth.resetPassword',
},
@@ -75,9 +130,7 @@ const router = createRouter({
{
path: '/register',
name: 'user.register',
// FIXME: use dynamic imports
// component: () => import('@/views/user/Register.vue'),
component: Register,
component: RegisterComponent,
meta: {
title: 'user.auth.createAccount',
},
@@ -85,63 +138,63 @@ const router = createRouter({
{
path: '/user/settings',
name: 'user.settings',
component: () => import('@/views/user/Settings.vue'),
component: UserSettingsComponent,
redirect: {name: 'user.settings.general'},
children: [
{
path: '/user/settings/avatar',
name: 'user.settings.avatar',
component: () => import('@/views/user/settings/Avatar.vue'),
component: UserSettingsAvatarComponent,
},
{
path: '/user/settings/caldav',
name: 'user.settings.caldav',
component: () => import('@/views/user/settings/Caldav.vue'),
component: UserSettingsCaldavComponent,
},
{
path: '/user/settings/data-export',
name: 'user.settings.data-export',
component: () => import('@/views/user/settings/DataExport.vue'),
component: UserSettingsDataExportComponent,
},
{
path: '/user/settings/deletion',
name: 'user.settings.deletion',
component: () => import('@/views/user/settings/Deletion.vue'),
component: UserSettingsDeletionComponent,
},
{
path: '/user/settings/email-update',
name: 'user.settings.email-update',
component: () => import('@/views/user/settings/EmailUpdate.vue'),
component: UserSettingsEmailUpdateComponent,
},
{
path: '/user/settings/general',
name: 'user.settings.general',
component: () => import('@/views/user/settings/General.vue'),
component: UserSettingsGeneralComponent,
},
{
path: '/user/settings/password-update',
name: 'user.settings.password-update',
component: () => import('@/views/user/settings/PasswordUpdate.vue'),
component: UserSettingsPasswordUpdateComponent,
},
{
path: '/user/settings/totp',
name: 'user.settings.totp',
component: () => import('@/views/user/settings/TOTP.vue'),
component: UserSettingsTOTPComponent,
},
{
path: '/user/settings/api-tokens',
name: 'user.settings.apiTokens',
component: () => import('@/views/user/settings/ApiTokens.vue'),
component: UserSettingsApiTokensComponent,
},
{
path: '/user/settings/migrate',
name: 'migrate.start',
component: () => import('@/views/migrate/Migration.vue'),
component: MigrationComponent,
},
{
path: '/migrate/:service',
name: 'migrate.service',
component: () => import('@/views/migrate/MigrationHandler.vue'),
component: MigrationHandlerComponent,
props: route => ({
service: route.params.service as string,
code: route.query.code as string,
@@ -152,25 +205,23 @@ const router = createRouter({
{
path: '/user/export/download',
name: 'user.export.download',
component: () => import('@/views/user/DataExportDownload.vue'),
component: DataExportDownload,
},
{
path: '/share/:share/auth',
name: 'link-share.auth',
// FIXME: use dynamic imports
// component: () => import('@/views/sharing/LinkSharingAuth.vue'),
component: LinkSharingAuth,
component: LinkShareAuthComponent,
},
{
path: '/tasks/:id',
name: 'task.detail',
component: () => import('@/views/tasks/TaskDetailView.vue'),
component: TaskDetailView,
props: route => ({ taskId: Number(route.params.id as string) }),
},
{
path: '/tasks/by/upcoming',
name: 'tasks.range',
component: () => import('@/views/tasks/ShowTasks.vue'),
component: UpcomingTasksComponent,
props: route => ({
dateFrom: parseDateOrString(route.query.from as string, new Date()),
dateTo: parseDateOrString(route.query.to as string, getNextWeekDate()),
@@ -194,12 +245,12 @@ const router = createRouter({
{
path: '/projects',
name: 'projects.index',
component: () => import('@/views/project/ListProjects.vue'),
component: ListProjects,
},
{
path: '/projects/new',
name: 'project.create',
component: () => import('@/views/project/NewProject.vue'),
component: NewProjectComponent,
meta: {
showAsModal: true,
},
@@ -207,7 +258,7 @@ const router = createRouter({
{
path: '/projects/:parentProjectId/new',
name: 'project.createFromParent',
component: () => import('@/views/project/NewProject.vue'),
component: NewProjectComponent,
props: route => ({ parentProjectId: Number(route.params.parentProjectId as string) }),
meta: {
showAsModal: true,
@@ -216,7 +267,7 @@ const router = createRouter({
{
path: '/projects/:projectId/settings/edit',
name: 'project.settings.edit',
component: () => import('@/views/project/settings/ProjectSettingsEdit.vue'),
component: ProjectSettingEdit,
props: route => ({ projectId: Number(route.params.projectId as string) }),
meta: {
showAsModal: true,
@@ -225,7 +276,7 @@ const router = createRouter({
{
path: '/projects/:projectId/settings/background',
name: 'project.settings.background',
component: () => import('@/views/project/settings/ProjectSettingsBackground.vue'),
component: ProjectSettingBackground,
meta: {
showAsModal: true,
},
@@ -233,7 +284,7 @@ const router = createRouter({
{
path: '/projects/:projectId/settings/duplicate',
name: 'project.settings.duplicate',
component: () => import('@/views/project/settings/ProjectSettingsDuplicate.vue'),
component: ProjectSettingDuplicate,
meta: {
showAsModal: true,
},
@@ -241,7 +292,7 @@ const router = createRouter({
{
path: '/projects/:projectId/settings/share',
name: 'project.settings.share',
component: () => import('@/views/project/settings/ProjectSettingsShare.vue'),
component: ProjectSettingShare,
meta: {
showAsModal: true,
},
@@ -249,7 +300,7 @@ const router = createRouter({
{
path: '/projects/:projectId/settings/webhooks',
name: 'project.settings.webhooks',
component: () => import('@/views/project/settings/ProjectSettingsWebhooks.vue'),
component: ProjectSettingWebhooks,
meta: {
showAsModal: true,
},
@@ -257,7 +308,7 @@ const router = createRouter({
{
path: '/projects/:projectId/settings/delete',
name: 'project.settings.delete',
component: () => import('@/views/project/settings/ProjectSettingsDelete.vue'),
component: ProjectSettingDelete,
meta: {
showAsModal: true,
},
@@ -265,7 +316,7 @@ const router = createRouter({
{
path: '/projects/:projectId/settings/archive',
name: 'project.settings.archive',
component: () => import('@/views/project/settings/ProjectSettingsArchive.vue'),
component: ProjectSettingArchive,
meta: {
showAsModal: true,
},
@@ -273,7 +324,7 @@ const router = createRouter({
{
path: '/projects/:projectId/settings/views',
name: 'project.settings.views',
component: () => import('@/views/project/settings/ProjectSettingsViews.vue'),
component: ProjectSettingViews,
meta: {
showAsModal: true,
},
@@ -282,7 +333,7 @@ const router = createRouter({
{
path: '/projects/:projectId/settings/edit',
name: 'filter.settings.edit',
component: () => import('@/views/filters/FilterEdit.vue'),
component: FilterEdit,
meta: {
showAsModal: true,
},
@@ -291,7 +342,7 @@ const router = createRouter({
{
path: '/projects/:projectId/settings/delete',
name: 'filter.settings.delete',
component: () => import('@/views/filters/FilterDelete.vue'),
component: FilterDelete,
meta: {
showAsModal: true,
},
@@ -300,7 +351,7 @@ const router = createRouter({
{
path: '/projects/:projectId/info',
name: 'project.info',
component: () => import('@/views/project/ProjectInfo.vue') ,
component: ProjectInfo,
meta: {
showAsModal: true,
},
@@ -328,7 +379,7 @@ const router = createRouter({
{
path: '/projects/:projectId/:viewId',
name: 'project.view',
component: () => import('@/views/project/ProjectView.vue'),
component: ProjectView,
props: route => ({
projectId: parseInt(route.params.projectId as string),
viewId: route.params.viewId ? parseInt(route.params.viewId as string): undefined,
@@ -337,12 +388,12 @@ const router = createRouter({
{
path: '/teams',
name: 'teams.index',
component: () => import('@/views/teams/ListTeams.vue'),
component: ListTeamsComponent,
},
{
path: '/teams/new',
name: 'teams.create',
component: () => import('@/views/teams/NewTeam.vue'),
component: NewTeamComponent,
meta: {
showAsModal: true,
},
@@ -350,17 +401,17 @@ const router = createRouter({
{
path: '/teams/:id/edit',
name: 'teams.edit',
component: () => import('@/views/teams/EditTeam.vue'),
component: EditTeamComponent,
},
{
path: '/labels',
name: 'labels.index',
component: () => import('@/views/labels/ListLabels.vue'),
component: ListLabelsComponent,
},
{
path: '/labels/new',
name: 'labels.create',
component: () => import('@/views/labels/NewLabel.vue'),
component: NewLabelComponent,
meta: {
showAsModal: true,
},
@@ -368,7 +419,7 @@ const router = createRouter({
{
path: '/filters/new',
name: 'filters.create',
component: () => import('@/views/filters/FilterNew.vue'),
component: FilterNew,
meta: {
showAsModal: true,
},
@@ -376,12 +427,12 @@ const router = createRouter({
{
path: '/auth/openid/:provider',
name: 'openid.auth',
component: () => import('@/views/user/OpenIdAuth.vue'),
component: OpenIdAuth,
},
{
path: '/about',
name: 'about',
component: () => import('@/views/About.vue'),
component: About,
},
],
})
+73 -16
View File
@@ -1,15 +1,21 @@
<script setup lang="ts">
import {computed, watch} from 'vue'
import {useProjectStore} from '@/stores/projects'
import {computed, ref, shallowReactive, watch, watchEffect} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects'
import {useAuthStore} from '@/stores/auth'
import {saveProjectView} from '@/helpers/projectView'
import ProjectService from '@/services/project'
import ProjectList from '@/components/project/views/ProjectList.vue'
import ProjectGantt from '@/components/project/views/ProjectGantt.vue'
import ProjectTable from '@/components/project/views/ProjectTable.vue'
import ProjectKanban from '@/components/project/views/ProjectKanban.vue'
import {useAuthStore} from '@/stores/auth'
import {DEFAULT_PROJECT_VIEW_SETTINGS} from '@/modelTypes/IProjectView'
import {saveProjectToHistory} from '@/modules/projectHistory'
const props = defineProps<{
projectId: number,
@@ -17,8 +23,10 @@ const props = defineProps<{
}>()
const router = useRouter()
const baseStore = useBaseStore()
const projectStore = useProjectStore()
const authStore = useAuthStore()
const route = useRoute()
const currentProject = computed(() => projectStore.projects[props.projectId])
@@ -26,19 +34,68 @@ const currentView = computed(() => {
return currentProject.value?.views.find(v => v.id === props.viewId)
})
const projectService = shallowReactive(new ProjectService())
const isLoadingProject = computed(() => projectService.loading)
const loadedProjectId = ref(0)
watch(
() => props.projectId,
// loadProject
async (projectIdToLoad, oldProjectIdToLoad) => {
console.debug('Loading project, $route.params =', route.params, `, loadedProjectId = ${loadedProjectId.value}, currentProject = `, currentProject.value)
if (projectIdToLoad !== oldProjectIdToLoad) {
loadedProjectId.value = 0
}
try {
const loadedProject = await projectService.get({id: projectIdToLoad})
// Here, we only set the new project in the projectStore.
// Setting that projet as the current one in the baseStore is handled by the watcher below.
projectStore.setProject(loadedProject)
} finally {
loadedProjectId.value = projectIdToLoad
}
},
{immediate: true},
)
watch(
() => [currentProject.value, props.viewId],
([newCurrentProject, newViewId]) => {
if (!newCurrentProject) {
baseStore.handleSetCurrentProject({project: null})
return
}
baseStore.handleSetCurrentProject({
project: newCurrentProject,
currentProjectViewId: newViewId,
})
}, {
deep: true,
immediate: true,
},
)
function redirectToDefaultViewIfNecessary() {
if (props.viewId === 0 || !projectStore.projects[props.projectId]?.views.find(v => v.id === props.viewId)) {
if (props.viewId === 0 || !currentView.value) {
// Ideally, we would do that in the router redirect, but the projects (and therefore, the views)
// are not always loaded then.
const defaultView = authStore.settings.frontendSettings.defaultView
let view
if (authStore.settings.frontendSettings.defaultView !== DEFAULT_PROJECT_VIEW_SETTINGS.FIRST) {
view = projectStore.projects[props.projectId]?.views.find(v => v.viewKind === authStore.settings.frontendSettings.defaultView)
if (defaultView !== DEFAULT_PROJECT_VIEW_SETTINGS.FIRST) {
view = currentProject.value?.views.find(v => v.viewKind === defaultView)
}
// Use the first view as fallback if the default view is not available
if (view === undefined && projectStore.projects[props.projectId]?.views?.length > 0) {
view = projectStore.projects[props.projectId]?.views[0]
if (view === undefined && currentProject.value?.views?.length > 0) {
view = currentProject.value?.views[0]
}
if (view) {
@@ -60,39 +117,39 @@ watch(
)
watch(
() => projectStore.projects[props.projectId],
currentProject,
redirectToDefaultViewIfNecessary,
)
// using a watcher instead of beforeEnter because beforeEnter is not called when only the viewId changes
watch(
() => [props.projectId, props.viewId],
() => saveProjectView(props.projectId, props.viewId),
{immediate: true},
)
watchEffect(() => saveProjectToHistory({id: props.projectId}))
watchEffect(() => saveProjectView(props.projectId, props.viewId))
const route = useRoute()
watchEffect(() => baseStore.setCurrentProjectViewId(props.viewId))
</script>
<template>
<ProjectList
v-if="currentView?.viewKind === 'list'"
:project-id="projectId"
:is-loading-project="isLoadingProject"
:view-id
/>
<ProjectGantt
v-if="currentView?.viewKind === 'gantt'"
:route
:is-loading-project="isLoadingProject"
:view-id
/>
<ProjectTable
v-if="currentView?.viewKind === 'table'"
:project-id="projectId"
:is-loading-project="isLoadingProject"
:view-id
/>
<ProjectKanban
v-if="currentView?.viewKind === 'kanban'"
:project-id="projectId"
:is-loading-project="isLoadingProject"
:view-id
/>
</template>
+7 -2
View File
@@ -1,9 +1,14 @@
{
"extends": [
"@tsconfig/node22/tsconfig.json",
"@tsconfig/node24/tsconfig.json",
"@vue/tsconfig/tsconfig.json"
],
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
"include": [
"env.config.d.ts",
"vite.config.*",
"vitest.config.*",
"cypress.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
+1 -1
View File
@@ -184,7 +184,7 @@ export default defineConfig(({mode}) => {
},
}),
vueDevTools({
launchEditor: env.VUE_DEVTOOLS_LAUNCH_EDITOR || 'code'
launchEditor: env.VUE_DEVTOOLS_LAUNCH_EDITOR || 'code',
}),
viteSentry(getSentryConfig(env)),
],
+1 -1
View File
@@ -67,7 +67,7 @@ require (
github.com/tkuchiki/go-timezone v0.2.3
github.com/typesense/typesense-go/v2 v2.0.0
github.com/ulule/limiter/v3 v3.11.2
github.com/wneessen/go-mail v0.6.0
github.com/wneessen/go-mail v0.6.1
github.com/yuin/goldmark v1.7.8
golang.org/x/crypto v0.32.0
golang.org/x/image v0.23.0
+2
View File
@@ -1377,6 +1377,8 @@ github.com/wneessen/go-mail v0.5.2 h1:MZKwgHJoRboLJ+EHMLuHpZc95wo+u1xViL/4XSswDT
github.com/wneessen/go-mail v0.5.2/go.mod h1:kRroJvEq2hOSEPFRiKjN7Csrz0G1w+RpiGR3b6yo+Ck=
github.com/wneessen/go-mail v0.6.0 h1:wO7EeJ8RL6DD+aycFGntil6b11g3FNQpQQQC1gkm97Y=
github.com/wneessen/go-mail v0.6.0/go.mod h1:G702XlFhzHV0Z4w9j2VsH5K9dJDvj0hx+yOOp1oX9vc=
github.com/wneessen/go-mail v0.6.1 h1:cDGqlGuEEhdILRe53VFzmM9WBk8Xh/QMvbO0oxrNJB4=
github.com/wneessen/go-mail v0.6.1/go.mod h1:G702XlFhzHV0Z4w9j2VsH5K9dJDvj0hx+yOOp1oX9vc=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=