1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge pull request #5970 from thornbill/material-react-table

This commit is contained in:
Bill Thornton 2024-08-30 08:40:54 -04:00 committed by GitHub
commit 7c3270725e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 547 additions and 279 deletions

394
package-lock.json generated
View file

@ -21,7 +21,7 @@
"@jellyfin/sdk": "0.0.0-unstable.202408290501",
"@mui/icons-material": "5.15.19",
"@mui/material": "5.15.19",
"@mui/x-data-grid": "7.6.1",
"@mui/x-date-pickers": "7.14.0",
"@react-hook/resize-observer": "2.0.2",
"@tanstack/react-query": "5.51.24",
"@tanstack/react-query-devtools": "5.51.24",
@ -48,6 +48,7 @@
"lodash-es": "4.17.21",
"markdown-it": "14.1.0",
"material-design-icons-iconfont": "6.7.0",
"material-react-table": "2.13.1",
"native-promise-only": "0.8.1",
"pdfjs-dist": "3.11.174",
"react": "18.3.1",
@ -2006,10 +2007,9 @@
"dev": true
},
"node_modules/@babel/runtime": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz",
"integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==",
"license": "MIT",
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz",
"integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@ -5418,12 +5418,12 @@
"license": "MIT"
},
"node_modules/@mui/private-theming": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz",
"integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==",
"version": "5.16.6",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz",
"integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==",
"dependencies": {
"@babel/runtime": "^7.23.9",
"@mui/utils": "^5.15.14",
"@mui/utils": "^5.16.6",
"prop-types": "^15.8.1"
},
"engines": {
@ -5444,9 +5444,9 @@
}
},
"node_modules/@mui/styled-engine": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz",
"integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==",
"version": "5.16.6",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz",
"integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==",
"dependencies": {
"@babel/runtime": "^7.23.9",
"@emotion/cache": "^11.11.0",
@ -5475,15 +5475,15 @@
}
},
"node_modules/@mui/system": {
"version": "5.15.15",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz",
"integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==",
"version": "5.16.7",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz",
"integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==",
"dependencies": {
"@babel/runtime": "^7.23.9",
"@mui/private-theming": "^5.15.14",
"@mui/styled-engine": "^5.15.14",
"@mui/types": "^7.2.14",
"@mui/utils": "^5.15.14",
"@mui/private-theming": "^5.16.6",
"@mui/styled-engine": "^5.16.6",
"@mui/types": "^7.2.15",
"@mui/utils": "^5.16.6",
"clsx": "^2.1.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
@ -5514,9 +5514,9 @@
}
},
"node_modules/@mui/types": {
"version": "7.2.14",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz",
"integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==",
"version": "7.2.15",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.15.tgz",
"integrity": "sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==",
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0"
},
@ -5527,14 +5527,16 @@
}
},
"node_modules/@mui/utils": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz",
"integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==",
"version": "5.16.6",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz",
"integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==",
"dependencies": {
"@babel/runtime": "^7.23.9",
"@types/prop-types": "^15.7.11",
"@mui/types": "^7.2.15",
"@types/prop-types": "^15.7.12",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-is": "^18.2.0"
"react-is": "^18.3.1"
},
"engines": {
"node": ">=12.0.0"
@ -5554,22 +5556,22 @@
}
},
"node_modules/@mui/utils/node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
},
"node_modules/@mui/x-data-grid": {
"version": "7.6.1",
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.6.1.tgz",
"integrity": "sha512-ZUQqSvmJgNQAgwLqVp/XUgNgKFb3zdsBQTbYCagjAK7Saq3iPDJkTb7FNSyT8UN0G6Kqogxgd9fKJW4L4ku1zQ==",
"license": "MIT",
"node_modules/@mui/x-date-pickers": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.14.0.tgz",
"integrity": "sha512-3xI3xYVxqPU4//KfE4FcR+Zs7UT4kkDPvA+IDOcQdRUyVwmcXCjBuJZgKgJMqSCNK/KIJZQQrpmy5XGHOKTbdA==",
"dependencies": {
"@babel/runtime": "^7.24.6",
"@mui/system": "^5.15.15",
"@mui/utils": "^5.15.14",
"@babel/runtime": "^7.25.0",
"@mui/system": "^5.16.7",
"@mui/utils": "^5.16.6",
"@types/react-transition-group": "^4.4.11",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"reselect": "^4.1.8"
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=14.0.0"
@ -5579,9 +5581,47 @@
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/material": "^5.15.14",
"date-fns": "^2.25.0 || ^3.2.0",
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0",
"dayjs": "^1.10.7",
"luxon": "^3.0.2",
"moment": "^2.29.4",
"moment-hijri": "^2.1.2",
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"date-fns": {
"optional": true
},
"date-fns-jalali": {
"optional": true
},
"dayjs": {
"optional": true
},
"luxon": {
"optional": true
},
"moment": {
"optional": true
},
"moment-hijri": {
"optional": true
},
"moment-jalaali": {
"optional": true
}
}
},
"node_modules/@nodelib/fs.scandir": {
@ -6254,6 +6294,21 @@
"node": ">=10"
}
},
"node_modules/@tanstack/match-sorter-utils": {
"version": "8.15.1",
"resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz",
"integrity": "sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==",
"dependencies": {
"remove-accents": "0.5.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.51.24",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.24.tgz",
@ -6307,6 +6362,62 @@
"react": "^18 || ^19"
}
},
"node_modules/@tanstack/react-table": {
"version": "8.19.3",
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz",
"integrity": "sha512-MtgPZc4y+cCRtU16y1vh1myuyZ2OdkWgMEBzyjYsoMWMicKZGZvcDnub3Zwb6XF2pj9iRMvm1SO1n57lS0vXLw==",
"dependencies": {
"@tanstack/table-core": "8.19.3"
},
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/@tanstack/react-virtual": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.8.3.tgz",
"integrity": "sha512-9ICwbDUUzN99CJIGc373i8NLoj6zFTKI2Hlcmo0+lCSAhPQ5mxq4dGOMKmLYoEFyHcGQ64Bd6ZVbnPpM6lNK5w==",
"dependencies": {
"@tanstack/virtual-core": "3.8.3"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@tanstack/table-core": {
"version": "8.19.3",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.19.3.tgz",
"integrity": "sha512-IqREj9ADoml9zCAouIG/5kCGoyIxPFdqdyoxis9FisXFi5vT+iYfEfLosq4xkU/iDbMcEuAj+X8dWRLvKYDNoQ==",
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/virtual-core": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.8.3.tgz",
"integrity": "sha512-vd2A2TnM5lbnWZnHi9B+L2gPtkSeOtJOAw358JqokIH1+v2J7vUAzFVPwB/wrye12RFOurffXu33plm4uQ+JBQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@ -6585,9 +6696,9 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
},
"node_modules/@types/prop-types": {
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
"node_modules/@types/qs": {
"version": "6.9.7",
@ -6631,9 +6742,9 @@
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
"integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
"version": "4.4.11",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz",
"integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==",
"dependencies": {
"@types/react": "*"
}
@ -13203,6 +13314,15 @@
"resolved": "https://registry.npmjs.org/headroom.js/-/headroom.js-0.12.0.tgz",
"integrity": "sha512-iXnAafUm3FdzfJ91uixLws2hkKI1jC8bAKK/pt7XYr8Ie1jO7xbK48Ycpl9tUPyBgkzuj1p/PhJS0fy4E/5anA=="
},
"node_modules/highlight-words": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/highlight-words/-/highlight-words-1.2.2.tgz",
"integrity": "sha512-Mf4xfPXYm8Ay1wTibCrHpNWeR2nUMynMVFkXCi4mbl+TEgmNOe+I4hV7W3OCZcSvzGL6kupaqpfHOemliMTGxQ==",
"engines": {
"node": ">= 16",
"npm": ">= 8"
}
},
"node_modules/history": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
@ -15472,6 +15592,33 @@
"resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.7.0.tgz",
"integrity": "sha512-lSj71DgVv20kO0kGbs42icDzbRot61gEDBLQACzkUuznRQBUYmbxzEkGU6dNBb5fRWHMaScYlAXX96HQ4/cJWA=="
},
"node_modules/material-react-table": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-2.13.1.tgz",
"integrity": "sha512-3iWwCa24ogxwllP4+W11euR/GV6f5wQE5FEilJ72/H3hDYHgsN+XehANytaG0G7/qy/OWYE7oXkcsRUU35I/iA==",
"dependencies": {
"@tanstack/match-sorter-utils": "8.15.1",
"@tanstack/react-table": "8.19.3",
"@tanstack/react-virtual": "3.8.3",
"highlight-words": "1.2.2"
},
"engines": {
"node": ">=16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kevinvandy"
},
"peerDependencies": {
"@emotion/react": ">=11.11",
"@emotion/styled": ">=11.11",
"@mui/icons-material": ">=5.11",
"@mui/material": ">=5.13",
"@mui/x-date-pickers": ">=6.15.0",
"react": ">=17.0",
"react-dom": ">=17.0"
}
},
"node_modules/mathml-tag-names": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@ -19949,6 +20096,11 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/remove-accents": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
"integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="
},
"node_modules/renderkid": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz",
@ -20004,11 +20156,6 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"node_modules/reselect": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
},
"node_modules/resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
@ -28244,9 +28391,9 @@
"dev": true
},
"@babel/runtime": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz",
"integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==",
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz",
"integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
@ -29786,19 +29933,19 @@
}
},
"@mui/private-theming": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz",
"integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==",
"version": "5.16.6",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz",
"integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==",
"requires": {
"@babel/runtime": "^7.23.9",
"@mui/utils": "^5.15.14",
"@mui/utils": "^5.16.6",
"prop-types": "^15.8.1"
}
},
"@mui/styled-engine": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz",
"integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==",
"version": "5.16.6",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz",
"integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==",
"requires": {
"@babel/runtime": "^7.23.9",
"@emotion/cache": "^11.11.0",
@ -29807,55 +29954,58 @@
}
},
"@mui/system": {
"version": "5.15.15",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz",
"integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==",
"version": "5.16.7",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz",
"integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==",
"requires": {
"@babel/runtime": "^7.23.9",
"@mui/private-theming": "^5.15.14",
"@mui/styled-engine": "^5.15.14",
"@mui/types": "^7.2.14",
"@mui/utils": "^5.15.14",
"@mui/private-theming": "^5.16.6",
"@mui/styled-engine": "^5.16.6",
"@mui/types": "^7.2.15",
"@mui/utils": "^5.16.6",
"clsx": "^2.1.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
}
},
"@mui/types": {
"version": "7.2.14",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz",
"integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==",
"version": "7.2.15",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.15.tgz",
"integrity": "sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==",
"requires": {}
},
"@mui/utils": {
"version": "5.15.14",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz",
"integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==",
"version": "5.16.6",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz",
"integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==",
"requires": {
"@babel/runtime": "^7.23.9",
"@types/prop-types": "^15.7.11",
"@mui/types": "^7.2.15",
"@types/prop-types": "^15.7.12",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-is": "^18.2.0"
"react-is": "^18.3.1"
},
"dependencies": {
"react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
}
}
},
"@mui/x-data-grid": {
"version": "7.6.1",
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.6.1.tgz",
"integrity": "sha512-ZUQqSvmJgNQAgwLqVp/XUgNgKFb3zdsBQTbYCagjAK7Saq3iPDJkTb7FNSyT8UN0G6Kqogxgd9fKJW4L4ku1zQ==",
"@mui/x-date-pickers": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.14.0.tgz",
"integrity": "sha512-3xI3xYVxqPU4//KfE4FcR+Zs7UT4kkDPvA+IDOcQdRUyVwmcXCjBuJZgKgJMqSCNK/KIJZQQrpmy5XGHOKTbdA==",
"requires": {
"@babel/runtime": "^7.24.6",
"@mui/system": "^5.15.15",
"@mui/utils": "^5.15.14",
"@babel/runtime": "^7.25.0",
"@mui/system": "^5.16.7",
"@mui/utils": "^5.16.6",
"@types/react-transition-group": "^4.4.11",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"reselect": "^4.1.8"
"react-transition-group": "^4.4.5"
}
},
"@nodelib/fs.scandir": {
@ -30267,6 +30417,14 @@
}
}
},
"@tanstack/match-sorter-utils": {
"version": "8.15.1",
"resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz",
"integrity": "sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==",
"requires": {
"remove-accents": "0.5.0"
}
},
"@tanstack/query-core": {
"version": "5.51.24",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.24.tgz",
@ -30293,6 +30451,32 @@
"@tanstack/query-devtools": "5.51.16"
}
},
"@tanstack/react-table": {
"version": "8.19.3",
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz",
"integrity": "sha512-MtgPZc4y+cCRtU16y1vh1myuyZ2OdkWgMEBzyjYsoMWMicKZGZvcDnub3Zwb6XF2pj9iRMvm1SO1n57lS0vXLw==",
"requires": {
"@tanstack/table-core": "8.19.3"
}
},
"@tanstack/react-virtual": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.8.3.tgz",
"integrity": "sha512-9ICwbDUUzN99CJIGc373i8NLoj6zFTKI2Hlcmo0+lCSAhPQ5mxq4dGOMKmLYoEFyHcGQ64Bd6ZVbnPpM6lNK5w==",
"requires": {
"@tanstack/virtual-core": "3.8.3"
}
},
"@tanstack/table-core": {
"version": "8.19.3",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.19.3.tgz",
"integrity": "sha512-IqREj9ADoml9zCAouIG/5kCGoyIxPFdqdyoxis9FisXFi5vT+iYfEfLosq4xkU/iDbMcEuAj+X8dWRLvKYDNoQ=="
},
"@tanstack/virtual-core": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.8.3.tgz",
"integrity": "sha512-vd2A2TnM5lbnWZnHi9B+L2gPtkSeOtJOAw358JqokIH1+v2J7vUAzFVPwB/wrye12RFOurffXu33plm4uQ+JBQ=="
},
"@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@ -30556,9 +30740,9 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
},
"@types/prop-types": {
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
"@types/qs": {
"version": "6.9.7",
@ -30600,9 +30784,9 @@
}
},
"@types/react-transition-group": {
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
"integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
"version": "4.4.11",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz",
"integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==",
"requires": {
"@types/react": "*"
}
@ -35412,6 +35596,11 @@
"resolved": "https://registry.npmjs.org/headroom.js/-/headroom.js-0.12.0.tgz",
"integrity": "sha512-iXnAafUm3FdzfJ91uixLws2hkKI1jC8bAKK/pt7XYr8Ie1jO7xbK48Ycpl9tUPyBgkzuj1p/PhJS0fy4E/5anA=="
},
"highlight-words": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/highlight-words/-/highlight-words-1.2.2.tgz",
"integrity": "sha512-Mf4xfPXYm8Ay1wTibCrHpNWeR2nUMynMVFkXCi4mbl+TEgmNOe+I4hV7W3OCZcSvzGL6kupaqpfHOemliMTGxQ=="
},
"history": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
@ -37051,6 +37240,17 @@
"resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.7.0.tgz",
"integrity": "sha512-lSj71DgVv20kO0kGbs42icDzbRot61gEDBLQACzkUuznRQBUYmbxzEkGU6dNBb5fRWHMaScYlAXX96HQ4/cJWA=="
},
"material-react-table": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-2.13.1.tgz",
"integrity": "sha512-3iWwCa24ogxwllP4+W11euR/GV6f5wQE5FEilJ72/H3hDYHgsN+XehANytaG0G7/qy/OWYE7oXkcsRUU35I/iA==",
"requires": {
"@tanstack/match-sorter-utils": "8.15.1",
"@tanstack/react-table": "8.19.3",
"@tanstack/react-virtual": "3.8.3",
"highlight-words": "1.2.2"
}
},
"mathml-tag-names": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@ -39887,6 +40087,11 @@
"mdast-util-to-markdown": "^0.6.0"
}
},
"remove-accents": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
"integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="
},
"renderkid": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz",
@ -39930,11 +40135,6 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"reselect": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",

View file

@ -85,7 +85,7 @@
"@jellyfin/sdk": "0.0.0-unstable.202408290501",
"@mui/icons-material": "5.15.19",
"@mui/material": "5.15.19",
"@mui/x-data-grid": "7.6.1",
"@mui/x-date-pickers": "7.14.0",
"@react-hook/resize-observer": "2.0.2",
"@tanstack/react-query": "5.51.24",
"@tanstack/react-query-devtools": "5.51.24",
@ -112,6 +112,7 @@
"lodash-es": "4.17.21",
"markdown-it": "14.1.0",
"material-design-icons-iconfont": "6.7.0",
"material-react-table": "2.13.1",
"native-promise-only": "0.8.1",
"pdfjs-dist": "3.11.174",
"react": "18.3.1",

View file

@ -1,17 +0,0 @@
import React, { type RefAttributes } from 'react';
import { Link } from 'react-router-dom';
import { GridActionsCellItem, type GridActionsCellItemProps } from '@mui/x-data-grid';
type GridActionsCellLinkProps = { to: string } & GridActionsCellItemProps & RefAttributes<HTMLButtonElement>;
/**
* Link component to use in mui's data-grid action column due to a current bug with passing props to custom link components.
* @see https://github.com/mui/mui-x/issues/4654
*/
const GridActionsCellLink = ({ to, ...props }: GridActionsCellLinkProps) => (
<Link to={to}>
<GridActionsCellItem {...props} />
</Link>
);
export default GridActionsCellLink;

View file

@ -1,18 +1,20 @@
import type { ActivityLogApiGetLogEntriesRequest } from '@jellyfin/sdk/lib/generated-client';
import type { AxiosRequestConfig } from 'axios';
import type { Api } from '@jellyfin/sdk';
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
import { useQuery } from '@tanstack/react-query';
import { JellyfinApiContext, useApi } from './useApi';
import { useApi } from 'hooks/useApi';
const fetchGetLogEntries = async (
currentApi: JellyfinApiContext,
requestParams: ActivityLogApiGetLogEntriesRequest,
const fetchLogEntries = async (
api?: Api,
requestParams?: ActivityLogApiGetLogEntriesRequest,
options?: AxiosRequestConfig
) => {
const { api } = currentApi;
if (!api) return;
if (!api) {
console.warn('[fetchLogEntries] No API instance available');
return;
}
const response = await getActivityLogApi(api).getLogEntries(requestParams, {
signal: options?.signal
@ -24,10 +26,11 @@ const fetchGetLogEntries = async (
export const useLogEntires = (
requestParams: ActivityLogApiGetLogEntriesRequest
) => {
const currentApi = useApi();
const { api } = useApi();
return useQuery({
queryKey: ['LogEntries', requestParams],
queryFn: ({ signal }) =>
fetchGetLogEntries(currentApi, requestParams, { signal })
fetchLogEntries(api, requestParams, { signal }),
enabled: !!api
});
};

View file

@ -0,0 +1,22 @@
import IconButton from '@mui/material/IconButton/IconButton';
import PermMedia from '@mui/icons-material/PermMedia';
import React, { type FC } from 'react';
import { Link } from 'react-router-dom';
import type { ActivityLogEntryCell } from 'apps/dashboard/features/activity/types/ActivityLogEntryCell';
import globalize from 'lib/globalize';
const ActionsCell: FC<ActivityLogEntryCell> = ({ row }) => (
row.original.ItemId ? (
<IconButton
size='large'
title={globalize.translate('LabelMediaDetails')}
component={Link}
to={`/details?id=${row.original.ItemId}`}
>
<PermMedia fontSize='inherit' />
</IconButton>
) : undefined
);
export default ActionsCell;

View file

@ -0,0 +1,14 @@
import type { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
import React, { type FC } from 'react';
import { ActivityLogEntryCell } from '../types/ActivityLogEntryCell';
import LogLevelChip from './LogLevelChip';
const LogLevelCell: FC<ActivityLogEntryCell> = ({ cell }) => {
const level = cell.getValue<LogLevel | undefined>();
return level ? (
<LogLevelChip level={level} />
) : undefined;
};
export default LogLevelCell;

View file

@ -1,12 +1,14 @@
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
import Info from '@mui/icons-material/Info';
import Box from '@mui/material/Box';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import React, { FC, useCallback, useState } from 'react';
import React, { type FC, useCallback, useState } from 'react';
const OverviewCell: FC<ActivityLogEntry> = ({ Overview, ShortOverview }) => {
import type { ActivityLogEntryCell } from '../types/ActivityLogEntryCell';
const OverviewCell: FC<ActivityLogEntryCell> = ({ row }) => {
const { ShortOverview, Overview } = row.original;
const displayValue = ShortOverview ?? Overview;
const [ open, setOpen ] = useState(false);

View file

@ -0,0 +1,27 @@
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
import IconButton from '@mui/material/IconButton/IconButton';
import React, { type FC } from 'react';
import { Link } from 'react-router-dom';
import UserAvatar from 'components/UserAvatar';
interface UserAvatarButtonProps {
user?: UserDto
}
const UserAvatarButton: FC<UserAvatarButtonProps> = ({ user }) => (
user?.Id ? (
<IconButton
size='large'
color='inherit'
sx={{ padding: 0 }}
title={user.Name || undefined}
component={Link}
to={`/dashboard/users/profile?userId=${user.Id}`}
>
<UserAvatar user={user} />
</IconButton>
) : undefined
);
export default UserAvatarButton;

View file

@ -0,0 +1,7 @@
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
import type { MRT_Cell, MRT_Row } from 'material-react-table';
export interface ActivityLogEntryCell {
cell: MRT_Cell<ActivityLogEntry>
row: MRT_Row<ActivityLogEntry>
}

View file

@ -1,34 +1,34 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
import PermMedia from '@mui/icons-material/PermMedia';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import Typography from '@mui/material/Typography';
import { DataGrid, type GridColDef } from '@mui/x-data-grid';
import { Link, useSearchParams } from 'react-router-dom';
import { type MRT_ColumnDef, MaterialReactTable, useMaterialReactTable } from 'material-react-table';
import { useSearchParams } from 'react-router-dom';
import { useLogEntires } from 'apps/dashboard/features/activity/api/useLogEntries';
import ActionsCell from 'apps/dashboard/features/activity/components/ActionsCell';
import LogLevelCell from 'apps/dashboard/features/activity/components/LogLevelCell';
import OverviewCell from 'apps/dashboard/features/activity/components/OverviewCell';
import UserAvatarButton from 'apps/dashboard/features/activity/components/UserAvatarButton';
import type { ActivityLogEntryCell } from 'apps/dashboard/features/activity/types/ActivityLogEntryCell';
import Page from 'components/Page';
import UserAvatar from 'components/UserAvatar';
import { useLogEntires } from 'hooks/useLogEntries';
import { useUsers } from 'hooks/useUsers';
import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } from 'scripts/datetime';
import { parseISO8601Date, toLocaleString } from 'scripts/datetime';
import globalize from 'lib/globalize';
import { toBoolean } from 'utils/string';
import LogLevelChip from '../components/activityTable/LogLevelChip';
import OverviewCell from '../components/activityTable/OverviewCell';
import GridActionsCellLink from '../components/dataGrid/GridActionsCellLink';
type UsersRecords = Record<string, UserDto>;
const DEFAULT_PAGE_SIZE = 25;
const VIEW_PARAM = 'useractivity';
const enum ActivityView {
All,
User,
System
All = 'All',
User = 'User',
System = 'System'
}
const getActivityView = (param: string | null) => {
@ -37,7 +37,11 @@ const getActivityView = (param: string | null) => {
return ActivityView.System;
};
const getRowId = (row: ActivityLogEntry) => row.Id ?? -1;
const getUserCell = (users: UsersRecords) => function UserCell({ row }: ActivityLogEntryCell) {
return (
<UserAvatarButton user={row.original.UserId && users[row.original.UserId] || undefined} />
);
};
const Activity = () => {
const [ searchParams, setSearchParams ] = useSearchParams();
@ -45,14 +49,13 @@ const Activity = () => {
const [ activityView, setActivityView ] = useState(
getActivityView(searchParams.get(VIEW_PARAM)));
const [ paginationModel, setPaginationModel ] = useState({
page: 0,
const [ pagination, setPagination ] = useState({
pageIndex: 0,
pageSize: DEFAULT_PAGE_SIZE
});
const { data: usersData, isLoading: isUsersLoading } = useUsers();
type UsersRecords = Record<string, UserDto>;
const users: UsersRecords = useMemo(() => {
if (!usersData) return {};
@ -67,107 +70,79 @@ const Activity = () => {
}, {});
}, [usersData]);
const UserCell = getUserCell(users);
const activityParams = useMemo(() => ({
startIndex: paginationModel.page * paginationModel.pageSize,
limit: paginationModel.pageSize,
startIndex: pagination.pageIndex * pagination.pageSize,
limit: pagination.pageSize,
hasUserId: activityView !== ActivityView.All ? activityView === ActivityView.User : undefined
}), [activityView, paginationModel.page, paginationModel.pageSize]);
}), [activityView, pagination.pageIndex, pagination.pageSize]);
const { data: logEntries, isLoading: isLogEntriesLoading } = useLogEntires(activityParams);
const isLoading = isUsersLoading || isLogEntriesLoading;
const userColDef: GridColDef[] = activityView !== ActivityView.System ? [
{
field: 'User',
headerName: globalize.translate('LabelUser'),
width: 60,
valueGetter: ( value, row ) => users[row.UserId]?.Name,
renderCell: ({ row }) => (
<IconButton
size='large'
color='inherit'
sx={{ padding: 0 }}
title={users[row.UserId]?.Name ?? undefined}
component={Link}
to={`/dashboard/users/profile?userId=${row.UserId}`}
>
<UserAvatar user={users[row.UserId]} />
</IconButton>
)
}
] : [];
const columns: GridColDef[] = [
{
field: 'Date',
headerName: globalize.translate('LabelDate'),
width: 90,
type: 'date',
valueGetter: ( value ) => parseISO8601Date(value),
valueFormatter: ( value ) => toLocaleDateString(value)
},
{
field: 'Time',
headerName: globalize.translate('LabelTime'),
width: 100,
type: 'dateTime',
valueGetter: ( value, row ) => parseISO8601Date(row.Date),
valueFormatter: ( value ) => toLocaleTimeString(value)
},
{
field: 'Severity',
headerName: globalize.translate('LabelLevel'),
width: 110,
renderCell: ({ value }) => (
value ? (
<LogLevelChip level={value} />
) : undefined
)
},
...userColDef,
{
field: 'Name',
headerName: globalize.translate('LabelName'),
width: 300
},
{
field: 'Overview',
headerName: globalize.translate('LabelOverview'),
width: 200,
valueGetter: ( value, row ) => row.ShortOverview ?? row.Overview,
renderCell: ({ row }) => (
<OverviewCell {...row} />
)
},
{
field: 'Type',
headerName: globalize.translate('LabelType'),
width: 180
},
{
field: 'actions',
type: 'actions',
width: 50,
getActions: ({ row }) => {
const actions = [];
if (row.ItemId) {
actions.push(
<GridActionsCellLink
size='large'
icon={<PermMedia />}
label={globalize.translate('LabelMediaDetails')}
title={globalize.translate('LabelMediaDetails')}
to={`/details?id=${row.ItemId}`}
/>
);
}
return actions;
const userColumn: MRT_ColumnDef<ActivityLogEntry>[] = useMemo(() =>
(activityView === ActivityView.System) ? [] : [{
id: 'User',
accessorFn: row => row.UserId && users[row.UserId]?.Name,
header: globalize.translate('LabelUser'),
size: 75,
Cell: UserCell,
enableResizing: false,
muiTableBodyCellProps: {
align: 'center'
}
}], [ activityView, users, UserCell ]);
const columns = useMemo<MRT_ColumnDef<ActivityLogEntry>[]>(() => [
{
id: 'Date',
accessorFn: row => parseISO8601Date(row.Date),
header: globalize.translate('LabelTime'),
size: 160,
Cell: ({ cell }) => toLocaleString(cell.getValue<Date>())
},
{
accessorKey: 'Severity',
header: globalize.translate('LabelLevel'),
size: 90,
Cell: LogLevelCell,
enableResizing: false,
muiTableBodyCellProps: {
align: 'center'
}
},
...userColumn,
{
accessorKey: 'Name',
header: globalize.translate('LabelName'),
size: 270
},
{
id: 'Overview',
accessorFn: row => row.ShortOverview || row.Overview,
header: globalize.translate('LabelOverview'),
size: 170,
Cell: OverviewCell
},
{
accessorKey: 'Type',
header: globalize.translate('LabelType'),
size: 150
},
{
id: 'Actions',
accessorFn: row => row.ItemId,
header: '',
size: 60,
Cell: ActionsCell,
enableColumnActions: false,
enableColumnFilter: false,
enableResizing: false,
enableSorting: false
}
];
], [ userColumn ]);
const onViewChange = useCallback((_e: React.MouseEvent<HTMLElement, MouseEvent>, newView: ActivityView | null) => {
if (newView !== null) {
@ -187,6 +162,58 @@ const Activity = () => {
}
}, [ activityView, searchParams, setSearchParams ]);
const table = useMaterialReactTable({
columns,
data: logEntries?.Items || [],
// Enable custom features
enableColumnPinning: true,
enableColumnResizing: true,
// Sticky header/footer
enableStickyFooter: true,
enableStickyHeader: true,
muiTableContainerProps: {
sx: {
maxHeight: 'calc(100% - 7rem)' // 2 x 3.5rem for header and footer
}
},
// State
initialState: {
density: 'compact'
},
state: {
isLoading,
pagination
},
// Server pagination
manualPagination: true,
onPaginationChange: setPagination,
rowCount: logEntries?.TotalRecordCount || 0,
// Custom toolbar contents
renderTopToolbarCustomActions: () => (
<ToggleButtonGroup
size='small'
value={activityView}
onChange={onViewChange}
exclusive
>
<ToggleButton value={ActivityView.All}>
{globalize.translate('All')}
</ToggleButton>
<ToggleButton value={ActivityView.User}>
{globalize.translate('LabelUser')}
</ToggleButton>
<ToggleButton value={ActivityView.System}>
{globalize.translate('LabelSystem')}
</ToggleButton>
</ToggleButtonGroup>
)
});
return (
<Page
id='serverActivityPage'
@ -203,43 +230,14 @@ const Activity = () => {
>
<Box
sx={{
display: 'flex',
alignItems: 'baseline',
marginY: 2
marginBottom: 1
}}
>
<Box sx={{ flexGrow: 1 }}>
<Typography variant='h2'>
{globalize.translate('HeaderActivity')}
</Typography>
</Box>
<ToggleButtonGroup
value={activityView}
onChange={onViewChange}
exclusive
>
<ToggleButton value={ActivityView.All}>
{globalize.translate('All')}
</ToggleButton>
<ToggleButton value={ActivityView.User}>
{globalize.translate('LabelUser')}
</ToggleButton>
<ToggleButton value={ActivityView.System}>
{globalize.translate('LabelSystem')}
</ToggleButton>
</ToggleButtonGroup>
<Typography variant='h2'>
{globalize.translate('HeaderActivity')}
</Typography>
</Box>
<DataGrid
columns={columns}
rows={logEntries?.Items || []}
pageSizeOptions={[ 10, 25, 50, 100 ]}
paginationMode='server'
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
rowCount={logEntries?.TotalRecordCount || 0}
getRowId={getRowId}
loading={isLoading}
/>
<MaterialReactTable table={table} />
</Box>
</Page>
);

View file

@ -1,18 +1,20 @@
import type { AxiosRequestConfig } from 'axios';
import type { Api } from '@jellyfin/sdk';
import type { UserApiGetUsersRequest } from '@jellyfin/sdk/lib/generated-client';
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
import { useQuery } from '@tanstack/react-query';
import { type JellyfinApiContext, useApi } from './useApi';
import { useApi } from './useApi';
export const fetchGetUsers = async (
currentApi: JellyfinApiContext,
const fetchUsers = async (
api?: Api,
requestParams?: UserApiGetUsersRequest,
options?: AxiosRequestConfig
) => {
const { api } = currentApi;
if (!api) return;
if (!api) {
console.warn('[fetchUsers] No API instance available');
return;
}
const response = await getUserApi(api).getUsers(requestParams, {
signal: options?.signal
@ -22,10 +24,11 @@ export const fetchGetUsers = async (
};
export const useUsers = (requestParams?: UserApiGetUsersRequest) => {
const currentApi = useApi();
const { api } = useApi();
return useQuery({
queryKey: ['Users'],
queryFn: ({ signal }) =>
fetchGetUsers(currentApi, requestParams, { signal })
fetchUsers(api, requestParams, { signal }),
enabled: !!api
});
};

View file

@ -185,13 +185,18 @@ const config = {
include: [
path.resolve(__dirname, 'node_modules/@jellyfin/libass-wasm'),
path.resolve(__dirname, 'node_modules/@jellyfin/sdk'),
path.resolve(__dirname, 'node_modules/@mui/x-data-grid'),
path.resolve(__dirname, 'node_modules/@mui/x-date-pickers'),
path.resolve(__dirname, 'node_modules/@react-hook/latest'),
path.resolve(__dirname, 'node_modules/@react-hook/passive-layout-effect'),
path.resolve(__dirname, 'node_modules/@react-hook/resize-observer'),
path.resolve(__dirname, 'node_modules/@remix-run/router'),
path.resolve(__dirname, 'node_modules/@tanstack/match-sorter-utils'),
path.resolve(__dirname, 'node_modules/@tanstack/query-core'),
path.resolve(__dirname, 'node_modules/@tanstack/react-query'),
path.resolve(__dirname, 'node_modules/@tanstack/react-table'),
path.resolve(__dirname, 'node_modules/@tanstack/react-virtual'),
path.resolve(__dirname, 'node_modules/@tanstack/table-core'),
path.resolve(__dirname, 'node_modules/@tanstack/virtual-core'),
path.resolve(__dirname, 'node_modules/@uupaa/dynamic-import-polyfill'),
path.resolve(__dirname, 'node_modules/axios'),
path.resolve(__dirname, 'node_modules/blurhash'),
@ -200,14 +205,17 @@ const config = {
path.resolve(__dirname, 'node_modules/dom7'),
path.resolve(__dirname, 'node_modules/epubjs'),
path.resolve(__dirname, 'node_modules/flv.js'),
path.resolve(__dirname, 'node_modules/highlight-words'),
path.resolve(__dirname, 'node_modules/libarchive.js'),
path.resolve(__dirname, 'node_modules/linkify-it'),
path.resolve(__dirname, 'node_modules/markdown-it'),
path.resolve(__dirname, 'node_modules/material-react-table'),
path.resolve(__dirname, 'node_modules/mdurl'),
path.resolve(__dirname, 'node_modules/punycode'),
path.resolve(__dirname, 'node_modules/react-blurhash'),
path.resolve(__dirname, 'node_modules/react-lazy-load-image-component'),
path.resolve(__dirname, 'node_modules/react-router'),
path.resolve(__dirname, 'node_modules/remove-accents'),
path.resolve(__dirname, 'node_modules/screenfull'),
path.resolve(__dirname, 'node_modules/ssr-window'),
path.resolve(__dirname, 'node_modules/swiper'),