Compare commits

..

125 Commits

Author SHA1 Message Date
Joshua M. Boniface
be195b0e24 Bump version to 10.7.7 2021-09-05 22:33:01 -04:00
dkanada
32330e3126 Merge pull request #2759 from thornbill/fix-serviceworker-paths
Fix serviceworker paths
2021-09-05 19:49:19 +09:00
Bill Thornton
af706726ef Upgrade workbox-webpack-plugin backport 2021-06-30 13:51:20 -04:00
Bill Thornton
393bfd2559 Update lockfile 2021-06-30 13:43:00 -04:00
Bill Thornton
575766b8e8 Merge pull request #2678 from grafixeyehero/window.global
Access Loading globally 

(cherry picked from commit c8fcb9e664)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-06-13 19:31:47 -04:00
Bill Thornton
f2cbfbb549 Merge pull request #2672 from nyanmisaka/patch-1
Remove OPUS from supported HLS audio formats

(cherry picked from commit 95256a87b5)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-27 02:46:37 -04:00
Bill Thornton
b589363d31 Merge pull request #2665 from dmitrylyzo/fix-server-hash-change
Add connection response handling

(cherry picked from commit eb79a8e045)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-27 02:46:37 -04:00
Anthony Lavado
4c1a301bdb Merge pull request #2676 from thornbill/fix-displaymessage-xss
(cherry picked from commit 70b41ff005)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-21 13:50:36 -04:00
Anthony Lavado
dfcfaad39c Merge pull request #2675 from thornbill/fix-share-url
(cherry picked from commit 8e465fb1fd)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-21 13:50:36 -04:00
Joshua M. Boniface
f9109a8194 Bump version to 10.7.6 2021-05-20 22:07:12 -04:00
Bill Thornton
2192388b78 Merge pull request #2673 from jellyfin/fix-serviceworker
Fix ServiceWorker URL

(cherry picked from commit 21b88e5efa)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-20 22:05:29 -04:00
Bill Thornton
f6d50b0721 Merge pull request #2475 from dmitrylyzo/fix-multiserver-wizard
Fix wizard in multi-server app

(cherry picked from commit 62d5c81120)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-20 22:05:29 -04:00
Joshua M. Boniface
05816d5562 Bump version to 10.7.5 2021-05-04 22:08:32 -04:00
Joshua M. Boniface
65f4e29d36 Bump version to 10.7.4 2021-05-04 21:16:31 -04:00
Joshua M. Boniface
0f0593f260 Bump version to 10.7.3 2021-05-04 20:00:47 -04:00
Bill Thornton
7d920beff0 Merge pull request #2657 from thornbill/apiclient-1.8
Bump jellyfin-apiclient to 1.8.0

(cherry picked from commit 4bfb59a00a)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-04 19:34:27 -04:00
Joshua M. Boniface
2dc95beb8f Merge pull request #2648 from thornbill/fix-docker-build
Fix alpine python package

(cherry picked from commit 79b36cd6b9)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-04 19:34:10 -04:00
Anthony Lavado
8449b94d16 Merge pull request #2647 from thornbill/safari-mov
(cherry picked from commit 4f13c10e8c)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-04 19:34:10 -04:00
Bill Thornton
4a17964e67 Merge pull request #2519 from dmitrylyzo/fix-hevc-in-ts
Add HEVC in TS for Tizen and webOS

(cherry picked from commit 59053ab6ae)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-04 19:34:10 -04:00
Joshua M. Boniface
c2106ab86c Remove erroneous package-lock.json 2021-05-03 18:06:37 -04:00
Joshua M. Boniface
591ee288bf Merge pull request #2645 from joshuaboniface/remove-image-proxy
(cherry picked from commit 9e845fb917)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-02 17:02:34 -04:00
Bill Thornton
87cae4336c Merge pull request #2624 from dmitrylyzo/fix-webos-customelements
webOS: fix Favorites tab
(cherry picked from commit 0a7b829c2c)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-01 13:25:37 -04:00
Bill Thornton
7cf812df37 Merge pull request #2617 from iwalton3/tv-scroll-styling-chrome
Set scrollbar width in TV mode.

(cherry picked from commit c1b847a309)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-01 13:25:37 -04:00
Anthony Lavado
b5d9ff9279 Merge pull request #2620 from dmitrylyzo/hide-search-alphapicker-on-tv
(cherry picked from commit 9f1bfe1631)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 15:00:36 -04:00
Bill Thornton
b053cb9d79 Merge pull request #2619 from jellyfin/fix-filter-hiding
Fix video filters hiding

(cherry picked from commit a8831cea3e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:57:55 -04:00
Bill Thornton
ba0815e1c3 Merge pull request #2616 from jellyfin/fix-es6-2
Fix view style selection dialog (ES6 migration)

(cherry picked from commit d7bf7ae58b)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:57:55 -04:00
Bill Thornton
94ac2b22a2 Merge pull request #2604 from Artiume/patch-5
Update Audiobook Resume Help

(cherry picked from commit cdf1bf72de)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:57:55 -04:00
Bill Thornton
7af38bb60c Merge pull request #2593 from oddstr13/pr-imagefix-master
Ensure that fillHeight gets set on image requests

(cherry picked from commit 5c09077a2f)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:27:31 -04:00
Joshua M. Boniface
21fd1a96fe Merge pull request #2585 from nielsvanvelzen/fix-lockfile-10.7.2
Fix 10.7.2 lockfile
2021-04-11 16:43:34 -04:00
Joshua M. Boniface
b50eb4fa2e Merge pull request #2586 from joshuaboniface/fix-fedora-docker
Fix 10.7.2 Fedora docker
2021-04-11 16:43:15 -04:00
Joshua M. Boniface
458fad3cb0 Add nodejs dependency to Fedora Dockerfile 2021-04-11 16:37:06 -04:00
Niels van Velzen
b467e581b0 Fix 10.7.2 lockfile 2021-04-11 22:32:23 +02:00
Joshua M. Boniface
2d381bbdc6 Bump version to 10.7.2 2021-04-11 14:20:17 -04:00
Bill Thornton
bdfc09739e Merge pull request #2576 from jellyfin/fix-es6-1
Fix ES6 migration

(cherry picked from commit 98814a5b66)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 13:38:51 -04:00
Bill Thornton
ff0a46a004 Merge pull request #2575 from jellyfin/revert-2536-fix-invalid-credentials
Revert "fix: redirect to login if stored credentials are invalid"

(cherry picked from commit 70ac9e0f2b)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 13:38:51 -04:00
Bill Thornton
32d152150b Merge pull request #2572 from Ullmie02/music_video_fix
Fix music videos on artist and album page

(cherry picked from commit e813fa7b64)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 13:38:51 -04:00
Bill Thornton
b9e0dd4938 Merge pull request #2552 from jellyfin/add-unknown-command
add 'unknown' as an input command

(cherry picked from commit a20322e7c2)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 13:38:51 -04:00
Bill Thornton
79d0ed3ee4 Merge pull request #2536 from cvium/fix-invalid-credentials
fix: redirect to login if stored credentials are invalid
(cherry picked from commit 070671f206)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 13:38:51 -04:00
Bill Thornton
d73d00a344 Merge pull request #2530 from ssenart/feature/2529-album_shuffle_broken
[2529] [RegressionFix] [Dlna] Album shuffle button does not shuffle any more since 10.6.4.

(cherry picked from commit 9ee48ee0ca)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 13:38:51 -04:00
Joshua M. Boniface
d91d3120e9 Merge pull request #2524 from crobibero/fedora-33
Build from fedora-33

(cherry picked from commit de2bebc089)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 13:38:51 -04:00
Bill Thornton
74de218ff6 Merge pull request #2514 from oddstr13/image-fill-resize
Add support for fillWidth and fillHeight

(cherry picked from commit 2c85b7806b)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 13:38:50 -04:00
Bill Thornton
eb3f1a3ba3 Merge pull request #2509 from jellyfin/disable-first-episode
Disable first episodes in Next Up home section

(cherry picked from commit a025771410)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 13:38:50 -04:00
Joshua M. Boniface
8e63b38a08 Add missing bump version 2021-03-21 19:27:16 -04:00
Joshua M. Boniface
def3532019 Bump version to 10.7.1 2021-03-21 19:24:02 -04:00
Bill Thornton
5ec953e344 Merge pull request #2503 from thornbill/fix-invalid-configs
Fix default values for invalid config.json files

(cherry picked from commit 7650c885d6)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-03-21 19:18:36 -04:00
Bill Thornton
85013363e8 Merge pull request #2498 from thornbill/add-cbz-close-button
Add close button to comics player

(cherry picked from commit 312136c531)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-03-21 19:18:36 -04:00
Bill Thornton
79c8495a1b Merge pull request #2496 from thornbill/logos-without-backdrops
Allow logos without backdrops enabled

(cherry picked from commit 6407128575)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-03-21 19:18:36 -04:00
dkanada
add2ec132f Merge pull request #2502 from brianjmurrell/patch-4
Add BR: nodejs for Fedora 33 and up
2021-03-20 01:06:43 +09:00
Brian J. Murrell
bcba45fc7d Bump the Release:
So that it successfully upgrades any broken ones.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2021-03-11 08:46:53 -05:00
Brian J. Murrell
e404106f9c Make directory permissions 755
Files can/should be 644 but directories need to be 755.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2021-03-11 08:33:38 -05:00
Brian J. Murrell
31b52f2c32 Reduce permissions
Giving everything a blanket execute permission is overzealous and dangerous.

Change permissions to 644.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2021-03-10 10:25:29 -05:00
Brian J. Murrell
a6d9897d8f Escape macro in comment
Not really allowed to use macros in comments and rpmbuild on F33 is starting
to enforce this.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2021-03-10 09:59:31 -05:00
Brian J. Murrell
925c2c926d Add BR: nodejs for Fedora 33 and up
nodejs doesn't seem to be implicitly installed as a BR: on Fedora 33, so set
an explicit BR: for it.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2021-03-10 09:37:44 -05:00
Joshua M. Boniface
a1dddf4524 Bump version to 10.7.0 2021-03-08 17:03:07 -05:00
dkanada
bad273ee3f Merge pull request #2482 from cvium/fix-addlibrary
don't use Locations as an indicator for AddLibrary

(cherry picked from commit ce95dced1d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-03-06 14:35:06 -05:00
Bill Thornton
e6f59a761b Merge pull request #2473 from thornbill/cache-busting
Add hash to bundle urls for cache busting

(cherry picked from commit 68bf09de16)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-03-06 14:34:57 -05:00
dkanada
afdaf29dc6 Merge pull request #2470 from pgeorgi/fix-cros
browser.js: Avoid misdetecting Chrome OS as OS X

(cherry picked from commit 8d01ed530e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-03-06 14:32:44 -05:00
dkanada
48201581d6 Merge pull request #2442 from jellyfin/plugin-tweaks
minor improvements to plugin pages

(cherry picked from commit d149430f65)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-03-06 14:32:41 -05:00
Joshua M. Boniface
8bb34c4266 Fix bad spacer in changelog line 2021-02-28 22:29:59 -05:00
Joshua M. Boniface
3ad0bb9118 Merge pull request #2461 from thornbill/remove-ios-limit
Remove iOS bandwidth limit

(cherry picked from commit 8f2437ab97)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-28 22:21:27 -05:00
Bill Thornton
7724b5fedc Merge pull request #2443 from dmitrylyzo/fix-tizen-subtitles
Fix attachment delivery urls

(cherry picked from commit 0a342ce095)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-27 22:36:16 -05:00
Bill Thornton
2a7d708944 Merge pull request #2378 from jellyfin/session-style
update style for active sessions

(cherry picked from commit bc557b1970)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-27 22:36:16 -05:00
dkanada
97461dabf2 Merge pull request #2343 from jellyfin/plugin-icon
fix image alignment on plugin cards

(cherry picked from commit 536797a22f)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-27 22:36:10 -05:00
Joshua M. Boniface
0ccbae44d1 Bump version to 10.7.0~rc4 2021-02-21 13:42:10 -05:00
dkanada
d85c91ce2e Merge pull request #2417 from MrLemur/source-type-change
Change babel.config.js sourceType to unamiguous

(cherry picked from commit fee5731038)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-21 13:27:22 -05:00
dkanada
93042157f8 Merge pull request #2375 from jellyfin/fix-no-repository-message
fix: message appearing after adding repositories
(cherry picked from commit bd297efac9)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-21 13:27:22 -05:00
dkanada
5e8c92a7a7 Merge pull request #2374 from cvium/fix_playaccess_validation
reject play access validation promise

(cherry picked from commit f6d0380486)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-21 13:27:22 -05:00
dkanada
3a57471b69 Merge pull request #2358 from Alcatraz077/master
Allows Search On Tizen

(cherry picked from commit caca17adba)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-21 13:27:22 -05:00
dkanada
fb17afad60 Merge pull request #2357 from thornbill/fix-epub-height
Fix epub player height

(cherry picked from commit 37bb21e347)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-21 13:27:22 -05:00
dkanada
4e38caba0a Merge pull request #2356 from jellyfin/fix-notch
fix: notched devices area not covered
(cherry picked from commit 9ee06bfa7e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-21 13:27:22 -05:00
Bill Thornton
1f97acc46e Merge pull request #2353 from thornbill/comics-player-height
Fix scaling in comics player

(cherry picked from commit 441d7a4236)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-21 13:27:22 -05:00
Joshua M. Boniface
998df179a8 Merge pull request #2350 from nyanmisaka/nvdec-vpp
(cherry picked from commit f7f0d688e8)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-21 13:27:19 -05:00
Bill Thornton
ff9fab5af1 Merge pull request #2344 from jellyfin/remove-unused-imports
refactor: remove unused imports
(cherry picked from commit 8df5febb3a)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-21 13:26:24 -05:00
Bill Thornton
84c5df8ea4 Merge pull request #2327 from MrChip53/library-menu-edit
Edit admin dashboard menu for plugins

(cherry picked from commit 0ff9615b47)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-02-21 13:26:24 -05:00
Joshua M. Boniface
d4c8cceb7f Bump version to 10.7.0-rc3 2021-01-23 16:02:48 -05:00
Bill Thornton
d588fc42c6 Merge pull request #2323 from jarnedemeulemeester/fix-play-icon-replace-resume-icon
Fix replay icon not getting replaced with play_arrow icon

(cherry picked from commit dade850ccf)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-01-23 15:20:16 -05:00
dkanada
0fa42cb7e6 Merge pull request #2318 from thornbill/buttondelete
Fix removed ButtonDelete key

(cherry picked from commit 7c9703e93a)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-01-23 15:20:16 -05:00
Bill Thornton
ef4f5d1f08 Merge pull request #2313 from dmitrylyzo/fix-safari-tizen
Fix browser detection: Safari vs Tizen

(cherry picked from commit 41cfd7f412)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-01-23 15:20:15 -05:00
Bill Thornton
d27f661b46 Merge pull request #2312 from jarnedemeulemeester/use-local-noto-sans
Use local version of Noto Sans if available

(cherry picked from commit 798df9b050)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-01-23 15:20:15 -05:00
Bill Thornton
0f247fd9f4 Merge pull request #2311 from jellyfin/nielsvanvelzen-disable-multi-download
Disable multi download option

(cherry picked from commit d08c4e4274)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-01-23 15:20:15 -05:00
Bill Thornton
ac34647e85 Merge pull request #2309 from jellyfin/white-flashing-images
fix(card): white flashing images

(cherry picked from commit 945946b96d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-01-23 15:20:15 -05:00
Bill Thornton
e8028aa1a1 Merge pull request #2306 from thornbill/fix-tiny-icons
Fix tiny card icons

(cherry picked from commit e2c4418485)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-01-23 15:20:15 -05:00
Bill Thornton
42b0b01a4e Merge pull request #2293 from thornbill/wrong-latest-tab
Fix latest tab links for tv and music

(cherry picked from commit e5531c363b)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-01-23 15:20:15 -05:00
Bill Thornton
cde21b3ff2 Merge pull request #2290 from MrTimscampi/chromecast-messages
Add Chromecast error messages to the locales

(cherry picked from commit a548bef8c2)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-01-23 15:20:15 -05:00
Joshua M. Boniface
58dc73cf66 Bump version to 10.7.0-rc2 2020-12-31 19:24:43 -05:00
Joshua M. Boniface
136983e026 Merge pull request #2288 from joshuaboniface/bump-apiclient
(cherry picked from commit 0a60f165a8)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-31 19:07:19 -05:00
Joshua M. Boniface
f38fb6eb46 Merge pull request #2280 from Artiume/patch-5
(cherry picked from commit b7eeebdd11)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-31 19:07:03 -05:00
Joshua M. Boniface
aba1d8655b Merge pull request #2225 from BaronGreenback/NewPluginController
(cherry picked from commit 9f175ee483)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-31 19:07:02 -05:00
Joshua M. Boniface
7582206eb3 Merge pull request #2286 from Artiume/patch-6
(cherry picked from commit b744fe4d74)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:13:21 -05:00
Bill Thornton
68edd48a0d Merge pull request #2283 from thornbill/allow-decimal-bitrate
Allow decimal entry for bitrate on mobile

(cherry picked from commit 9008a42cc9)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:02 -05:00
dkanada
e8d044fbb7 Merge pull request #2269 from jellyfin/dkanada-patch-1
Fix issue with double click fullscreen

(cherry picked from commit b64f50307d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:02 -05:00
Bill Thornton
7379822c72 Merge pull request #2265 from MrTimscampi/sort-by-premiere-date
Sort items by premiere date on the details page

(cherry picked from commit 58aa865af0)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:02 -05:00
Bill Thornton
2c230388be Merge pull request #2263 from MrTimscampi/osd-pointer-events
Fix OSD gradients not letting pointer events through

(cherry picked from commit 6a8173718d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:02 -05:00
dkanada
32514307eb Merge pull request #2260 from thornbill/moar-centering-issues
Fix chevron centering on home section titles

(cherry picked from commit af11671485)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:02 -05:00
Joshua M. Boniface
0f12bd64aa Merge pull request #2258 from thornbill/return-of-the-dashboard-theme
(cherry picked from commit 699a3d2046)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:01 -05:00
Bill Thornton
6ff1bdcaca Merge pull request #2247 from thornbill/cant-stop-wont-stop
Always allow stopping via the action menu

(cherry picked from commit 60cce55204)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:01 -05:00
Bill Thornton
88dc049a2f Merge pull request #2246 from thornbill/fix-item-details-mobile
Fix layout issues on mobile item details

(cherry picked from commit e1672db560)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:01 -05:00
Bill Thornton
5d42ac19c7 Merge pull request #2244 from Artiume/patch-2
Fix Continue Listening

(cherry picked from commit e752116209)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:01 -05:00
Bill Thornton
efe4374153 Merge pull request #2242 from thornbill/fix-prepare-script-windows
Replace bash prepare script with node version

(cherry picked from commit 898704d9b0)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:01 -05:00
Bill Thornton
cd256f7989 Merge pull request #2240 from thornbill/fix-sonarqube-bug
Remove duplicate try/catch

(cherry picked from commit 9f0b5bf673)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:01 -05:00
Bill Thornton
49fa4e0b65 Merge pull request #2239 from thornbill/disable-browser-hack-sass
Disable browser hack rule for sass files

(cherry picked from commit 86b8b55b1a)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:08:01 -05:00
Bill Thornton
3a4023ca40 Merge pull request #2238 from thornbill/revert-restart
Fix restart button being shown when unsupported

(cherry picked from commit 0a50c4ddc1)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:07:53 -05:00
Bill Thornton
982bbee9ea Merge pull request #2237 from thornbill/fix-plugin-cards
Fix layout of plugin cards

(cherry picked from commit be57362f21)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:06:54 -05:00
Joshua M. Boniface
142c56bf6a Merge pull request #2236 from thornbill/fix-epub-touch
Fix touch support in epub reader

(cherry picked from commit b60407abcc)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:06:54 -05:00
Bill Thornton
1207ac98be Merge pull request #2234 from thornbill/no-no-noto
Use Noto Sans from Fontsource

(cherry picked from commit d66d26b4f6)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:06:54 -05:00
Bill Thornton
c757c53e5c Merge pull request #2224 from Delgan/patch-1
Fix possible HLSError (BufferFullError) on Firefox

(cherry picked from commit ebb4b05081)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:06:54 -05:00
Bill Thornton
cd2d30eefc Merge pull request #2222 from nyanmisaka/finetune-tonemap
Modify some tone mapping related strings

(cherry picked from commit bb47abc2a4)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:06:54 -05:00
Joshua M. Boniface
07a33779d8 Merge pull request #2218 from thornbill/style-fixes
(cherry picked from commit 68fb95bf7d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-30 19:06:50 -05:00
dkanada
4dedb8ae45 Merge pull request #2220 from jellyfin/dependabot/npm_and_yarn/ini-1.3.7
Bump ini from 1.3.5 to 1.3.7

(cherry picked from commit c1a675053c)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:23 -05:00
Bill Thornton
87d459f827 Merge pull request #2219 from crobibero/create-playlist
Set Content-Type header when creating a playlist

(cherry picked from commit 1c03e4c830)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:23 -05:00
dkanada
eeded17cbd Merge pull request #2217 from nyanmisaka/landingScreen-cleanup
Landing screen options clean up

(cherry picked from commit f94cbfed7c)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:23 -05:00
Bill Thornton
95a995327d Merge pull request #2216 from dmitrylyzo/fix-livetv-canplay
Fix canPlay for Live TV

(cherry picked from commit fdcf74d498)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:23 -05:00
dkanada
27896bcc84 Merge pull request #2215 from dmitrylyzo/fix-livetv-pages
Fix multiplication of event listeners on Live TV pages

(cherry picked from commit 568968f654)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
dkanada
4aeecfa043 Merge pull request #2214 from dmitrylyzo/fix-livetv-route
Fix LiveTV group anchors

(cherry picked from commit 5da9d93423)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
Bill Thornton
88bb7adaba Merge pull request #2213 from thornbill/fix-sonar-bugs
Fix sonarqube bugs

(cherry picked from commit 52543f8a51)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
dkanada
d768cf7970 Merge pull request #2211 from thornbill/fix-dlna-profile-link
Fix invalid dlna profile path

(cherry picked from commit 3995143690)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
dkanada
22df1eb3a6 Merge pull request #2210 from nyanmisaka/offset-step
Set the step of subtitle offset slider to 0.1

(cherry picked from commit c7a1c19d9e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
dkanada
ac6ba3228f Merge pull request #2202 from thornbill/fix-user-edit
Remove reference to sharing help element

(cherry picked from commit a8005f2ec3)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
Bill Thornton
7e5fcd8374 Merge pull request #2195 from OancaAndrei/syncplay-fix-next-item
Fix SyncPlay switching to next item in queue

(cherry picked from commit 60e8fc4d8e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
dkanada
ccef18ee5d Merge pull request #2188 from dmitrylyzo/fix-back
Fix anchor click action and plugin configuration page URL

(cherry picked from commit 62a09b7a4e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
Bill Thornton
5f63743ed0 Merge pull request #2186 from Maxr1998/fix-plugin-loader
Fix plugin loader for function definitions in window

(cherry picked from commit 3992265189)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
dkanada
972ecc4106 Merge pull request #2183 from dmitrylyzo/fix-multiserver-syncplay
SyncPlay, don't use bad ApiClient

(cherry picked from commit 9815c64cdc)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
Bill Thornton
c4fd94b147 Merge pull request #2181 from MrTimscampi/no-userdata-field
Remove non-existing UserData field from requests

(cherry picked from commit 7cbaa99784)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-13 20:59:22 -05:00
Anthony Lavado
a0ef8a405c Merge pull request #2177 from anthonylavado/update-apiclient
Update the API Client version

(cherry picked from commit 46716fc25a)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-12-05 01:10:16 -05:00
Joshua M. Boniface
4b9ffb7b22 Bump version to 10.7.0~rc1 2020-12-04 21:00:31 -05:00
998 changed files with 77665 additions and 180866 deletions

View File

@@ -0,0 +1,57 @@
jobs:
- job: Build
displayName: 'Build'
strategy:
matrix:
Development:
BuildConfiguration: development
Production:
BuildConfiguration: production
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Install Node'
inputs:
versionSpec: '12.x'
- task: Cache@2
displayName: 'Cache node_modules'
inputs:
key: 'yarn | yarn.lock'
path: 'node_modules'
- script: 'yarn install --frozen-lockfile'
displayName: 'Install Dependencies'
env:
SKIP_PREPARE: 'true'
- script: 'yarn build:development'
displayName: 'Build Development'
condition: eq(variables['BuildConfiguration'], 'development')
- script: 'yarn build:production'
displayName: 'Build Production'
condition: eq(variables['BuildConfiguration'], 'production')
- script: 'test -d dist'
displayName: 'Check Build'
- script: 'mv dist jellyfin-web'
displayName: 'Rename Directory'
- task: ArchiveFiles@2
displayName: 'Archive Directory'
inputs:
rootFolderOrFile: 'jellyfin-web'
includeRootFolder: true
archiveFile: 'jellyfin-web-$(BuildConfiguration)'
- task: PublishPipelineArtifact@1
displayName: 'Publish Release'
inputs:
targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip'
artifactName: 'jellyfin-web-$(BuildConfiguration)'

View File

@@ -0,0 +1,122 @@
jobs:
- job: BuildPackage
displayName: 'Build Packages'
strategy:
matrix:
CentOS:
BuildConfiguration: centos
Debian:
BuildConfiguration: debian
Fedora:
BuildConfiguration: fedora
Portable:
BuildConfiguration: portable
pool:
vmImage: 'ubuntu-latest'
steps:
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-web-$(BuildConfiguration) deployment'
displayName: 'Build Dockerfile'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-web-$(BuildConfiguration)'
displayName: 'Run Dockerfile (unstable)'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-web-$(BuildConfiguration)'
displayName: 'Run Dockerfile (stable)'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
- task: PublishPipelineArtifact@1
displayName: 'Publish Release'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
targetPath: '$(Build.SourcesDirectory)/deployment/dist'
artifactName: 'jellyfin-web-$(BuildConfiguration)'
- task: SSH@0
displayName: 'Create target directory on repository server'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- task: CopyFilesOverSSH@0
displayName: 'Upload artifacts to repository server'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
sshEndpoint: repository
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
contents: '**'
targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- job: BuildDocker
displayName: 'Build Docker'
pool:
vmImage: 'ubuntu-latest'
variables:
- name: JellyfinVersion
value: 0.0.0
steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
- task: Docker@2
displayName: 'Push Unstable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
repository: 'jellyfin/jellyfin-web'
command: buildAndPush
buildContext: '.'
Dockerfile: 'deployment/Dockerfile.docker'
containerRegistry: Docker Hub
tags: |
unstable-$(Build.BuildNumber)
unstable
- task: Docker@2
displayName: 'Push Stable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
inputs:
repository: 'jellyfin/jellyfin-web'
command: buildAndPush
buildContext: '.'
Dockerfile: 'deployment/Dockerfile.docker'
containerRegistry: Docker Hub
tags: |
stable-$(Build.BuildNumber)
$(JellyfinVersion)
- job: CollectArtifacts
displayName: 'Collect Artifacts'
dependsOn:
- BuildPackage
- BuildDocker
condition: and(succeeded('BuildPackage'), succeeded('BuildDocker'))
pool:
vmImage: 'ubuntu-latest'
steps:
- task: SSH@0
displayName: 'Update Unstable Repository'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable'
- task: SSH@0
displayName: 'Update Stable Repository'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)'

16
.ci/azure-pipelines.yml Normal file
View File

@@ -0,0 +1,16 @@
trigger:
batch: true
branches:
include:
- '*'
tags:
include:
- '*'
pr:
branches:
include:
- '*'
jobs:
- template: azure-pipelines-build.yml
- template: azure-pipelines-package.yml

1
.copr/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../fedora/Makefile

View File

@@ -1,23 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{
"name": "Node.js",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
//https://github.com/microsoft/vscode-dev-containers/issues/559
"postCreateCommand": "source $NVM_DIR/nvm.sh && nvm install 20"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@@ -8,5 +8,5 @@ trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
[*.{json,yaml,yml}]
[*.json]
indent_size = 2

View File

@@ -1,11 +0,0 @@
{
"ecmaVersion": "es5",
"modules": "false",
"files": "./dist/**/*.js",
"not": [
"./dist/libraries/pdf.worker.js",
"./dist/libraries/worker-bundle.js",
"./dist/libraries/wasm-gen/libarchive.js",
"./dist/serviceworker.js"
]
}

View File

@@ -2,13 +2,11 @@ const restrictedGlobals = require('confusing-browser-globals');
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
'react',
'@babel',
'promise',
'import',
'eslint-comments',
'sonarjs'
'eslint-comments'
],
env: {
node: true,
@@ -16,216 +14,51 @@ module.exports = {
es2017: true,
es2020: true
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
impliedStrict: true
}
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
// 'plugin:promise/recommended',
'plugin:import/errors',
'plugin:eslint-comments/recommended',
'plugin:compat/recommended',
'plugin:sonarjs/recommended'
'plugin:compat/recommended'
],
rules: {
'array-callback-return': ['error', { 'checkForEach': true }],
'block-spacing': ['error'],
'brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
'comma-dangle': ['error', 'never'],
'comma-spacing': ['error'],
'curly': ['error', 'multi-line', 'consistent'],
'default-case-last': ['error'],
'eol-last': ['error'],
'indent': ['error', 4, { 'SwitchCase': 1 }],
'jsx-quotes': ['error', 'prefer-single'],
'keyword-spacing': ['error'],
'max-statements-per-line': ['error'],
'max-params': ['error', 7],
'new-cap': [
'error',
{
'capIsNewExceptions': ['jQuery.Deferred'],
'newIsCapExceptionPattern': '\\.default$'
}
],
'no-duplicate-imports': ['error'],
'no-empty-function': ['error'],
'no-extend-native': ['error'],
'no-floating-decimal': ['error'],
'no-lonely-if': ['error'],
'no-multi-spaces': ['error'],
'no-multiple-empty-lines': ['error', { 'max': 1 }],
'no-nested-ternary': ['error'],
'no-redeclare': ['off'],
'@typescript-eslint/no-redeclare': ['error', { builtinGlobals: false }],
'no-restricted-globals': ['error'].concat(restrictedGlobals),
'no-return-assign': ['error'],
'no-return-await': ['error'],
'no-sequences': ['error', { 'allowInParentheses': false }],
'no-shadow': ['off'],
'@typescript-eslint/no-shadow': ['error'],
'no-throw-literal': ['error'],
'no-trailing-spaces': ['error'],
'no-undef-init': ['error'],
'no-unneeded-ternary': ['error'],
'no-unused-expressions': ['off'],
'@typescript-eslint/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }],
'no-unused-private-class-members': ['error'],
'no-useless-rename': ['error'],
'no-useless-constructor': ['off'],
'@typescript-eslint/no-useless-constructor': ['error'],
'no-var': ['error'],
'no-void': ['error', { 'allowAsStatement': true }],
'no-warning-comments': ['warn', { 'terms': ['fixme', 'hack', 'xxx'] }],
'object-curly-spacing': ['error', 'always'],
'@babel/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }],
'one-var': ['error', 'never'],
'operator-linebreak': ['error', 'before', { overrides: { '?': 'after', ':': 'after', '=': 'after' } }],
'padded-blocks': ['error', 'never'],
'prefer-const': ['error', { 'destructuring': 'all' }],
'prefer-promise-reject-errors': ['warn', { 'allowEmptyReject': true }],
'@typescript-eslint/prefer-for-of': ['error'],
'@typescript-eslint/prefer-optional-chain': ['error'],
'prefer-const': ['error', {'destructuring': 'all'}],
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
'radix': ['error'],
'@typescript-eslint/semi': ['error'],
'@babel/semi': ['error'],
'no-var': ['error'],
'space-before-blocks': ['error'],
'space-infix-ops': 'error',
'yoda': 'error',
'react/jsx-filename-extension': ['error', { 'extensions': ['.jsx', '.tsx'] }],
'react/jsx-no-bind': ['error'],
'react/jsx-no-useless-fragment': ['error'],
'react/jsx-no-constructed-context-values': ['error'],
'react/no-array-index-key': ['error'],
'sonarjs/no-inverted-boolean-check': ['error'],
// TODO: Enable the following rules and fix issues
'sonarjs/cognitive-complexity': ['off'],
'sonarjs/no-duplicate-string': ['off']
},
settings: {
react: {
version: 'detect'
},
'import/parsers': {
'@typescript-eslint/parser': [ '.ts', '.tsx' ]
},
'import/resolver': {
node: {
extensions: [
'.js',
'.ts',
'.jsx',
'.tsx'
],
moduleDirectory: [
'node_modules',
'src'
]
}
},
polyfills: [
// Native Promises Only
'Promise',
// whatwg-fetch
'fetch',
// document-register-element
'document.registerElement',
// resize-observer-polyfill
'ResizeObserver',
// fast-text-encoding
'TextEncoder',
// intersection-observer
'IntersectionObserver',
// Core-js
'Object.assign',
'Object.is',
'Object.setPrototypeOf',
'Object.toString',
'Object.freeze',
'Object.seal',
'Object.preventExtensions',
'Object.isFrozen',
'Object.isSealed',
'Object.isExtensible',
'Object.getOwnPropertyDescriptor',
'Object.getPrototypeOf',
'Object.keys',
'Object.entries',
'Object.getOwnPropertyNames',
'Function.name',
'Function.hasInstance',
'Array.from',
'Array.arrayOf',
'Array.copyWithin',
'Array.fill',
'Array.find',
'Array.findIndex',
'Array.iterator',
'String.fromCodePoint',
'String.raw',
'String.iterator',
'String.codePointAt',
'String.endsWith',
'String.includes',
'String.repeat',
'String.startsWith',
'String.trim',
'String.anchor',
'String.big',
'String.blink',
'String.bold',
'String.fixed',
'String.fontcolor',
'String.fontsize',
'String.italics',
'String.link',
'String.small',
'String.strike',
'String.sub',
'String.sup',
'RegExp',
'Number',
'Math',
'Date',
'async',
'Symbol',
'Map',
'Set',
'WeakMap',
'WeakSet',
'ArrayBuffer',
'DataView',
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
'Reflect',
// Temporary while eslint-compat-plugin is buggy
'document.querySelector'
]
'yoda': 'error'
},
overrides: [
// Config files and development scripts
{
files: [
'./babel.config.js',
'./.eslintrc.js',
'./postcss.config.js',
'./webpack.*.js',
'./scripts/**/*.js'
]
},
// JavaScript source files
{
files: [
'./src/**/*.{js,jsx,ts,tsx}'
'./src/**/*.js'
],
parserOptions: {
project: ['./tsconfig.json']
},
parser: '@babel/eslint-parser',
env: {
node: false,
amd: true,
@@ -247,53 +80,116 @@ module.exports = {
'jQuery': 'readonly',
// Jellyfin globals
'ApiClient': 'writable',
'Events': 'writable',
'chrome': 'writable',
'DlnaProfilePage': 'writable',
'DashboardPage': 'writable',
'Emby': 'readonly',
'getParameterByName': 'writable',
'getWindowLocationSearch': 'writable',
'Globalize': 'writable',
'Hls': 'writable',
'dfnshelper': 'writable',
'LibraryMenu': 'writable',
'LinkParser': 'writable',
'LiveTvHelpers': 'writable',
'Loading': 'writable',
'MetadataEditor': 'writable',
'ServerNotifications': 'writable',
'TaskButton': 'writable',
'PlaylistViewer': 'writable',
'UserParentalControlPage': 'writable',
'Windows': 'readonly',
// Build time definitions
__COMMIT_SHA__: 'readonly',
__JF_BUILD_VERSION__: 'readonly',
__PACKAGE_JSON_NAME__: 'readonly',
__PACKAGE_JSON_VERSION__: 'readonly',
__USE_SYSTEM_FONTS__: 'readonly',
__WEBPACK_SERVE__: 'readonly'
'Windows': 'readonly'
},
rules: {
'@typescript-eslint/prefer-string-starts-ends-with': ['error']
}
},
// TypeScript source files
{
files: [
'./src/**/*.{ts,tsx}'
],
extends: [
'eslint:recommended',
'plugin:import/typescript',
'plugin:@typescript-eslint/recommended',
'plugin:eslint-comments/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended'
],
rules: {
'@typescript-eslint/no-floating-promises': ['error'],
'@typescript-eslint/no-unused-vars': ['error'],
'sonarjs/cognitive-complexity': ['error']
// TODO: Fix warnings and remove these rules
'no-redeclare': ['warn'],
'no-useless-escape': ['warn'],
'no-unused-vars': ['warn']
},
settings: {
polyfills: [
// Native Promises Only
'Promise',
// whatwg-fetch
'fetch',
// document-register-element
'document.registerElement',
// resize-observer-polyfill
'ResizeObserver',
// fast-text-encoding
'TextEncoder',
// intersection-observer
'IntersectionObserver',
// Core-js
'Object.assign',
'Object.is',
'Object.setPrototypeOf',
'Object.toString',
'Object.freeze',
'Object.seal',
'Object.preventExtensions',
'Object.isFrozen',
'Object.isSealed',
'Object.isExtensible',
'Object.getOwnPropertyDescriptor',
'Object.getPrototypeOf',
'Object.keys',
'Object.entries',
'Object.getOwnPropertyNames',
'Function.name',
'Function.hasInstance',
'Array.from',
'Array.arrayOf',
'Array.copyWithin',
'Array.fill',
'Array.find',
'Array.findIndex',
'Array.iterator',
'String.fromCodePoint',
'String.raw',
'String.iterator',
'String.codePointAt',
'String.endsWith',
'String.includes',
'String.repeat',
'String.startsWith',
'String.trim',
'String.anchor',
'String.big',
'String.blink',
'String.bold',
'String.fixed',
'String.fontcolor',
'String.fontsize',
'String.italics',
'String.link',
'String.small',
'String.strike',
'String.sub',
'String.sup',
'RegExp',
'Number',
'Math',
'Date',
'async',
'Symbol',
'Map',
'Set',
'WeakMap',
'WeakSet',
'ArrayBuffer',
'DataView',
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
'Reflect',
// Temporary while eslint-compat-plugin is buggy
'document.querySelector'
]
}
}
]

7
.github/CODEOWNERS vendored
View File

@@ -1 +1,6 @@
* @jellyfin/web
.ci @dkanada @EraYaN
.github @jellyfin/core
fedora @joshuaboniface
debian @joshuaboniface
.copr @joshuaboniface
deployment @joshuaboniface

6
.github/SUPPORT.md vendored
View File

@@ -7,14 +7,14 @@ When looking for support or information, please first search for your
question in these venues:
* [Jellyfin Forum](https://forum.jellyfin.org)
* [Jellyfin Documentation](https://jellyfin.org/docs/)
* [Jellyfin Documentation](https://docs.jellyfin.org)
* [Open or **closed** issues in the organization](https://github.com/issues?q=sort%3Aupdated-desc+org%3Ajellyfin+is%3Aissue+)
If you didn't find an answer in the resources above, contributors and other
users are reachable through the following channels:
* #jellyfin on [Matrix](https://matrix.to/#/#jellyfin:matrix.org%22) or [IRC](ircs://irc.libera.chat:6697/#jellyfin)
* #jellyfin-troubleshooting on [Matrix](https://matrix.to/#/#jellyfin-troubleshooting:matrix.org) or [IRC](ircs://irc.libera.chat:6697/#jellyfin-troubleshooting)
* #jellyfin on [Matrix](https://matrix.to/#/#jellyfin:matrix.org%22) or [IRC](https://webchat.freenode.net/#jellyfin)
* #jellyfin-troubleshooting on [Matrix](https://matrix.to/#/#jellyfin-troubleshooting:matrix.org) or [IRC](https://webchat.freenode.net/#jellyfin-troubleshooting)
* [/r/jellyfin on Reddit](https://www.reddit.com/r/jellyfin)
GitHub issues are for tracking enhancements and bugs, not general support.

7
.github/dependabot.yaml vendored Normal file
View File

@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10

View File

@@ -1,6 +1,6 @@
<!--
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.org/docs/general/contributing/issues page.
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://docs.jellyfin.org/general/contributing/issues.html page.
-->
**Changes**

View File

@@ -1,8 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>jellyfin/.github//renovate-presets/nodejs",
":semanticCommitsDisabled",
":dependencyDashboard"
]
}

20
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 90
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 14
# Issues with these labels will never be considered stale
exemptLabels:
- regression
- future
- feature
- enhancement
- confirmed
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Issues go stale after 90d of inactivity. Mark the issue as fresh by adding a comment or commit. Stale issues close after an additional 14d of inactivity.
If this issue is safe to close now please do so.
If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@@ -1,23 +0,0 @@
name: Automation
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
on:
push:
branches:
- master
pull_request_target:
jobs:
conflicts:
name: Merge conflict labeling
runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin-web' }}
steps:
- uses: eps1lon/actions-label-merge-conflict@1b1b1fcde06a9b3d089f3464c96417961dde1168 # v3.0.2
with:
dirtyLabel: 'merge conflict'
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'
repoToken: ${{ secrets.JF_BOT_TOKEN }}

View File

@@ -1,129 +0,0 @@
name: Build
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
on:
push:
branches: [ master, release* ]
pull_request_target:
branches: [ master, release* ]
workflow_dispatch:
jobs:
run-build-prod:
name: Run production build
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node environment
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 20
check-latest: true
cache: npm
- name: Install Node.js dependencies
run: npm ci --no-audit
- name: Run a production build
env:
JELLYFIN_VERSION: ${{ github.event.pull_request.head.sha || github.sha }}
run: npm run build:production
- name: Update config.json for testing
run: |
jq '.multiserver=true | .servers=["https://demo.jellyfin.org/unstable"]' dist/config.json > dist/config.tmp.json
mv dist/config.tmp.json dist/config.json
- name: Upload artifact
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: jellyfin-web__prod
path: dist
publish:
name: Deploy to Cloudflare Pages
runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin-web' }}
needs:
- run-build-prod
permissions:
contents: read
deployments: write
steps:
- name: Add comment
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
if: ${{ github.event_name == 'pull_request_target' }}
with:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
message: |
## Cloudflare Pages deployment
| **Latest commit** | <code>${{ github.event.pull_request.head.sha || github.sha }}</code> |
|-------------------|:-:|
| **Status** | 🔄 Deploying... |
| **Preview URL** | Not available |
| **Type** | 🔀 Preview |
pr_number: ${{ github.event.pull_request.number }}
comment_tag: CFPages-deployment
mode: recreate
- name: Download workflow artifact
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
with:
name: jellyfin-web__prod
path: dist
- name: Publish to Cloudflare
id: cf
uses: cloudflare/wrangler-action@f84a562284fc78278ff9052435d9526f9c718361 # v3.7.0
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=jellyfin-web --branch=${{
(github.event_name != 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository)
&& (github.event.pull_request.head.ref || github.ref_name)
|| format('{0}/{1}', github.event.pull_request.head.repo.full_name, github.event.pull_request.head.ref)
}} --commit-hash=${{ github.event.pull_request.head.sha || github.sha }}
- name: Update status comment (Success)
if: ${{ github.event_name == 'pull_request_target' && success() }}
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
with:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
message: |
## Cloudflare Pages deployment
| **Latest commit** | <code>${{ github.event.pull_request.head.sha || github.sha }}</code> |
|-------------------|:-:|
| **Status** | ✅ Deployed! |
| **Preview URL** | ${{ steps.cf.outputs.deployment-url != '' && steps.cf.outputs.deployment-url || 'Not available' }} |
| **Type** | 🔀 Preview |
pr_number: ${{ github.event.pull_request.number }}
comment_tag: CFPages-deployment
mode: recreate
- name: Update status comment (Failure)
if: ${{ github.event_name == 'pull_request_target' && failure() }}
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
with:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
message: |
## Cloudflare Pages deployment
| **Latest commit** | <code>${{ github.event.pull_request.head.sha || github.sha }}</code> |
|-------------------|:-:|
| **Status** | ❌ Failure. Check workflow logs for details |
| **Preview URL** | Not available |
| **Type** | 🔀 Preview |
pr_number: ${{ github.event.pull_request.number }}
comment_tag: CFPages-deployment
mode: recreate

31
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
schedule:
- cron: '30 7 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -1,34 +0,0 @@
name: CodeQL
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
on:
push:
branches: [ master, release* ]
pull_request:
branches: [ master, release* ]
schedule:
- cron: '30 7 * * 6'
jobs:
codeql:
name: Run CodeQL
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Initialize CodeQL
uses: github/codeql-action/init@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0
with:
languages: javascript
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0

View File

@@ -1,36 +0,0 @@
name: Commands
on:
issue_comment:
types:
- created
- edited
jobs:
rebase:
name: Rebase
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '@jellyfin-bot rebase') && github.event.comment.author_association == 'MEMBER'
runs-on: ubuntu-latest
steps:
- name: Notify as seen
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
with:
token: ${{ secrets.JF_BOT_TOKEN }}
comment-id: ${{ github.event.comment.id }}
reactions: '+1'
- name: Checkout the latest code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
- name: Automatic Rebase
uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691 # 1.8
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
- name: Comment on failure
if: failure()
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
with:
token: ${{ secrets.JF_BOT_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
I'm sorry @${{ github.event.comment.user.login }}, I'm afraid I can't do that.

95
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: Lint
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
run-eslint:
name: Run eslint
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 12
- name: Cache dependencies
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install Node.js dependencies
run: yarn install --frozen-lockfile
env:
SKIP_PREPARE: true
- name: Run eslint
run: yarn lint
run-stylelint-css:
name: Run stylelint (css)
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 12
- name: Set up stylelint matcher
uses: xt0rted/stylelint-problem-matcher@v1
- name: Cache dependencies
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install Node.js dependencies
run: yarn install --frozen-lockfile
env:
SKIP_PREPARE: true
- name: Run stylelint
run: yarn stylelint:css
run-stylelint-scss:
name: Run stylelint (scss)
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 12
- name: Set up stylelint matcher
uses: xt0rted/stylelint-problem-matcher@v1
- name: Cache dependencies
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install Node.js dependencies
run: yarn install --frozen-lockfile
env:
SKIP_PREPARE: true
- name: Run stylelint
run: yarn stylelint:scss

15
.github/workflows/merge-conflicts.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: "Merge Conflicts"
on:
push:
branches:
- master
jobs:
triage:
runs-on: ubuntu-latest
if: github.repository == 'jellyfin/jellyfin-web'
steps:
- uses: mschilde/auto-label-merge-conflicts@master
with:
CONFLICT_LABEL_NAME: "merge conflict"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,36 +0,0 @@
name: PR suggestions
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.id || github.run_id }}
cancel-in-progress: true
on:
pull_request_target:
branches: [ master, release* ]
jobs:
run-eslint:
name: Run eslint suggestions
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Setup node environment
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 20
check-latest: true
cache: npm
- name: Install Node.js dependencies
run: npm ci --no-audit
- name: Run eslint
if: ${{ github.repository == 'jellyfin/jellyfin-web' }}
uses: CatChen/eslint-suggestion-action@bc82950fa97bb3e46d9cca16a8bf2ad3e3c010fc # v4.1.5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,123 +0,0 @@
name: Quality checks
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
on:
push:
branches: [ master, release* ]
pull_request:
branches: [ master, release* ]
jobs:
run-escheck:
name: Run es-check
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup node environment
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 20
check-latest: true
cache: npm
- name: Install Node.js dependencies
run: npm ci --no-audit
- name: Run a production build
run: npm run build:production
- name: Run es-check
run: npm run escheck
run-eslint:
name: Run eslint
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup node environment
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 20
check-latest: true
cache: npm
- name: Install Node.js dependencies
run: npm ci --no-audit
- name: Run eslint
run: npx eslint --quiet "."
run-stylelint:
name: Run stylelint
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup node environment
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 20
check-latest: true
cache: npm
- name: Set up stylelint matcher
uses: xt0rted/stylelint-problem-matcher@34db1b874c0452909f0696aedef70b723870a583 # tag=v1
- name: Install Node.js dependencies
run: npm ci --no-audit
- name: Run stylelint
run: npm run stylelint
run-tsc:
name: Run TypeScript build check
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup node environment
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 20
check-latest: true
cache: npm
- name: Install Node.js dependencies
run: npm ci --no-audit
- name: Run tsc
run: npm run build:check
run-test:
name: Run tests
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup node environment
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 20
check-latest: true
cache: npm
- name: Install Node.js dependencies
run: npm ci --no-audit
- name: Run test suite
run: npm run test

View File

@@ -1,51 +0,0 @@
name: Stale Check
on:
schedule:
- cron: '30 1 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
issues:
name: Check issues
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
operations-per-run: 75
days-before-stale: 120
days-before-pr-stale: -1
days-before-close: 21
days-before-pr-close: -1
exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed
stale-issue-label: stale
stale-issue-message: |-
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://jellyfin.org/contact).
prs-conflicts:
name: Check PRs with merge conflicts
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
operations-per-run: 75
# The merge conflict action will remove the label when updated
remove-stale-when-updated: false
days-before-stale: -1
days-before-close: 90
days-before-issue-close: -1
stale-pr-label: merge conflict
close-pr-message: |-
This PR has been closed due to having unresolved merge conflicts.

View File

@@ -1,52 +0,0 @@
name: Update the Jellyfin SDK
on:
schedule:
- cron: '0 7 * * *'
workflow_dispatch:
concurrency:
group: unstable-sdk-pr
cancel-in-progress: true
jobs:
update:
runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin-web' }}
steps:
- name: Check out Git repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: master
token: ${{ secrets.JF_BOT_TOKEN }}
- name: Set up Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 20
check-latest: true
cache: npm
- name: Install latest unstable SDK
run: |
npm i --save @jellyfin/sdk@unstable
VERSION=$(jq -r '.dependencies["@jellyfin/sdk"]' package.json)
echo "JF_SDK_VERSION=${VERSION}" >> $GITHUB_ENV
- name: Open a pull request
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
with:
token: ${{ secrets.JF_BOT_TOKEN }}
commit-message: Update @jellyfin/sdk to ${{env.JF_SDK_VERSION}}
committer: jellyfin-bot <team@jellyfin.org>
author: jellyfin-bot <team@jellyfin.org>
branch: update-jf-sdk
delete-branch: true
title: Update @jellyfin/sdk to ${{env.JF_SDK_VERSION}}
body: |
**Changes**
Updates to the latest unstable @jellyfin/sdk build
labels: |
dependencies
npm

10
.gitignore vendored
View File

@@ -3,16 +3,12 @@ dist
web
node_modules
# test coverage
coverage
# config
config.json
# ide
.idea
.vs
# vim
*.sw?
.vscode
# log
yarn-error.log

3
.npmrc
View File

@@ -1,3 +0,0 @@
engine-strict=true
fund=false
save-exact=true

View File

@@ -1,3 +0,0 @@
# Exclude test files from Sonar sources
# See: https://docs.sonarcloud.io/advanced-setup/analysis-scope/#file-exclusion-and-inclusion
sonar.exclusions=src/**/*.test.js,src/**/*.test.ts

View File

@@ -1,7 +1,7 @@
{
"plugins": [
"stylelint-no-browser-hacks/lib"
],
"stylelint-no-browser-hacks/lib"
],
"rules": {
"at-rule-empty-line-before": [ "always", {
"except": [
@@ -13,7 +13,6 @@
"at-rule-name-case": "lower",
"at-rule-name-space-after": "always-single-line",
"at-rule-no-unknown": true,
"at-rule-no-vendor-prefix": true,
"at-rule-semicolon-newline-after": "always",
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-after": "always",
@@ -60,6 +59,7 @@
"declaration-colon-space-after": "always-single-line",
"declaration-colon-space-before": "never",
"font-family-no-duplicate-names": true,
"function-calc-no-invalid": true,
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "always-multi-line",
"function-comma-space-after": "always-single-line",
@@ -78,7 +78,6 @@
"media-feature-colon-space-before": "never",
"media-feature-name-case": "lower",
"media-feature-name-no-unknown": true,
"media-feature-name-no-vendor-prefix": true,
"media-feature-parentheses-space-inside": "never",
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
@@ -105,7 +104,6 @@
]
}
],
"property-no-vendor-prefix": true,
"rule-empty-line-before": [ "always-multi-line", {
"except": ["first-nested"],
"ignore": ["after-comment"]
@@ -119,7 +117,6 @@
"selector-list-comma-newline-after": "always",
"selector-list-comma-space-before": "never",
"selector-max-empty-lines": 0,
"selector-no-vendor-prefix": true,
"selector-pseudo-class-case": "lower",
"selector-pseudo-class-no-unknown": true,
"selector-pseudo-class-parentheses-space-inside": "never",
@@ -138,25 +135,9 @@
"string-no-newline": true,
"unit-case": "lower",
"unit-no-unknown": true,
"value-no-vendor-prefix": true,
"value-list-comma-newline-after": "always-multi-line",
"value-list-comma-space-after": "always-single-line",
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0
},
"overrides": [
{
"files": [
"*.scss",
"**/*.scss"
],
"customSyntax": "postcss-scss",
"plugins": [ "stylelint-scss" ],
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"plugin/no-browser-hacks": null
}
}
]
}
}

9
.stylelintrc.scss.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": [ "./.stylelintrc.json" ],
"plugins": [ "stylelint-scss" ],
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"plugin/no-browser-hacks": null
}
}

View File

@@ -1,5 +0,0 @@
{
"recommendations": [
"dbaeumer.vscode-eslint"
]
}

View File

@@ -1,7 +0,0 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.format.enable": true,
"editor.formatOnSave": false
}

View File

@@ -1,162 +1,110 @@
# Jellyfin Contributors
- [JoshuaBoniface](https://github.com/joshuaboniface)
- [nvllsvm](https://github.com/nvllsvm)
- [JustAMan](https://github.com/JustAMan)
- [dcrdev](https://github.com/dcrdev)
- [EraYaN](https://github.com/EraYaN)
- [flemse](https://github.com/flemse)
- [bfayers](https://github.com/bfayers)
- [Bond_009](https://github.com/Bond-009)
- [AnthonyLavado](https://github.com/anthonylavado)
- [dkanada](https://github.com/dkanada)
- [sparky8251](https://github.com/sparky8251)
- [LeoVerto](https://github.com/LeoVerto)
- [cvium](https://github.com/cvium)
- [grafixeyehero](https://github.com/grafixeyehero)
- [Drago96](https://github.com/drago-96)
- [ViXXoR](https://github.com/ViXXoR)
- [nkmerrill](https://github.com/nkmerrill)
- [TtheCreator](https://github.com/Tthecreator)
- [RazeLighter777](https://github.com/RazeLighter777)
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
- [thornbill](https://github.com/thornbill)
- [redSpoutnik](https://github.com/redSpoutnik)
- [DrPandemic](https://github.com/drpandemic)
- [Oddstr13](https://github.com/oddstr13)
- [petermcneil](https://github.com/petermcneil)
- [lewazo](https://github.com/lewazo)
- [Raghu Saxena](https://github.com/ckcr4lyf)
- [Nickbert7](https://github.com/Nickbert7)
- [ferferga](https://github.com/ferferga)
- [bilde2910](https://github.com/bilde2910)
- [Daniel Hartung](https://github.com/dhartung)
- [Ryan Hartzell](https://github.com/ryan-hartzell)
- [Thibault Nocchi](https://github.com/ThibaultNocchi)
- [MrTimscampi](https://github.com/MrTimscampi)
- [artiume](https://github.com/Artiume)
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
- [Sarab Singh](https://github.com/sarab97)
- [DesertCookie](https://github.com/desertcookie)
- [GuilhermeHideki](https://github.com/GuilhermeHideki)
- [Andrei Oanca](https://github.com/OancaAndrei)
- [Cromefire_](https://github.com/cromefire)
- [Orry Verducci](https://github.com/orryverducci)
- [Camc314](https://github.com/camc314)
- [danieladov](https://github.com/danieladov)
- [Stephane Senart](https://github.com/ssenart)
- [imchasingshadows](https://github.com/imchasingshadows)
- [Ömer Erdinç Yağmurlu](https://github.com/omeryagmurlu)
- [Keegan Dahm](https://github.com/keegandahm)
- [GodTamIt](https://github.com/GodTamIt)
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
- [Matthew Jones](https://github.com/matthew-jones-uk)
- [taku0](https://github.com/taku0)
- [Viperinius](https://github.com/Viperinius)
- [is343](https://github.com/is343)
- [Meet Pandya](https://github.com/meet-k-pandya)
- [Peter Spenler](https://github.com/peterspenler)
- [jomp16](https://github.com/jomp16)
- [Leon de Klerk](https://github.com/leondeklerk)
- [CrispyBaguette](https://github.com/CrispyBaguette)
- [Vankerkom](https://github.com/vankerkom)
- [edvwib](https://github.com/edvwib)
- [Rob Farraher](https://github.com/farraherbg)
- [TelepathicWalrus](https://github.com/TelepathicWalrus)
- [Pier-Luc Ducharme](https://github.com/pl-ducharme)
- [Anantharaju S](https://github.com/Anantharajus)
- [Merlin Sievers](https://github.com/dann-merlin)
- [Fishbigger](https://github.com/fishbigger)
- [sleepycatcoding](https://github.com/sleepycatcoding)
- [TheMelmacian](https://github.com/TheMelmacian)
- [v0idMrK](https://github.com/v0idMrK)
- [tehciolo](https://github.com/tehciolo)
- [scampower3](https://github.com/scampower3)
- [LittleBigOwI](https://github.com/LittleBigOwI/)
- [Nate G](https://github.com/GGProGaming)
- [Grady Hallenbeck](https://github.com/grhallenbeck)
- [DinuD](https://github.com/DinuD)
- [Kevin Tan (Valius)](https://github.com/valius)
- [Rasmus Krämer](https://github.com/rasmuslos)
- [ntarelix](https://github.com/ntarelix)
- [btopherjohnson](https://github.com/btopherjohnson)
- [András Maróy](https://github.com/andrasmaroy)
- [Chris-Codes-It](https://github.com/Chris-Codes-It)
- [Vedant](https://github.com/viktory36)
- [GeorgeH005](https://github.com/GeorgeH005)
- [JPUC1143](https://github.com/Jpuc1143)
- [David Angel](https://github.com/davidangel)
- [Pithaya](https://github.com/Pithaya)
- [Peter Santos](https://github.com/prsantos-com)
- [Chaitanya Shahare](https://github.com/Chaitanya-Shahare)
- [Venkat Karasani](https://github.com/venkat-karasani)
- [Connor Smith](https://github.com/ConnorS1110)
- [iFraan](https://github.com/iFraan)
- [JoshuaBoniface](https://github.com/joshuaboniface)
- [nvllsvm](https://github.com/nvllsvm)
- [JustAMan](https://github.com/JustAMan)
- [dcrdev](https://github.com/dcrdev)
- [EraYaN](https://github.com/EraYaN)
- [flemse](https://github.com/flemse)
- [bfayers](https://github.com/bfayers)
- [Bond_009](https://github.com/Bond-009)
- [AnthonyLavado](https://github.com/anthonylavado)
- [dkanada](https://github.com/dkanada)
- [sparky8251](https://github.com/sparky8251)
- [LeoVerto](https://github.com/LeoVerto)
- [cvium](https://github.com/cvium)
- [grafixeyehero](https://github.com/grafixeyehero)
- [Drago96](https://github.com/drago-96)
- [ViXXoR](https://github.com/ViXXoR)
- [nkmerrill](https://github.com/nkmerrill)
- [TtheCreator](https://github.com/Tthecreator)
- [RazeLighter777](https://github.com/RazeLighter777)
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
- [thornbill](https://github.com/thornbill)
- [redSpoutnik](https://github.com/redSpoutnik)
- [DrPandemic](https://github.com/drpandemic)
- [Oddstr13](https://github.com/oddstr13)
- [petermcneil](https://github.com/petermcneil)
- [lewazo](https://github.com/lewazo)
- [Raghu Saxena](https://github.com/ckcr4lyf)
- [Nickbert7](https://github.com/Nickbert7)
- [ferferga](https://github.com/ferferga)
- [bilde2910](https://github.com/bilde2910)
- [Daniel Hartung](https://github.com/dhartung)
- [Ryan Hartzell](https://github.com/ryan-hartzell)
- [Thibault Nocchi](https://github.com/ThibaultNocchi)
- [MrTimscampi](https://github.com/MrTimscampi)
- [artiume](https://github.com/Artiume)
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
- [Sarab Singh](https://github.com/sarab97)
- [DesertCookie](https://github.com/desertcookie)
- [GuilhermeHideki](https://github.com/GuilhermeHideki)
- [Andrei Oanca](https://github.com/OancaAndrei)
- [Cromefire_](https://github.com/cromefire)
- [Orry Verducci](https://github.com/orryverducci)
- [Camc314](https://github.com/camc314)
- [danieladov](https://github.com/danieladov)
- [Stephane Senart](https://github.com/ssenart)
## Emby Contributors
# Emby Contributors
- [LukePulverenti](https://github.com/LukePulverenti)
- [ebr11](https://github.com/ebr11)
- [lalmanzar](https://github.com/lalmanzar)
- [schneifu](https://github.com/schneifu)
- [Mark2xv](https://github.com/Mark2xv)
- [ScottRapsey](https://github.com/ScottRapsey)
- [skynet600](https://github.com/skynet600)
- [Cheesegeezer](https://githum.com/Cheesegeezer)
- [Radeon](https://github.com/radeonorama)
- [gcw07](https://github.com/gcw07)
- [SivaramAdhiappan](https://github.com/shivaram1190)
- [CWatkinsNash](https://github.com/CWatkinsNash)
- [sfnetwork](https://github.com/sfnetwork)
- [Logos302](https://github.com/Logos302)
- [TheWorkz](https://github.com/TheWorkz)
- [mboehler](https://github.com/mboehler)
- [KaHooli](https://github.com/KaHooli)
- [xzener](https://github.com/xzener)
- [CBers](https://github.com/CBers)
- [Sagaia](https://github.com/Sagaia)
- [JHawk111](https://github.com/JHawk111)
- [David3663](https://github.com/david3663)
- [Smyken](https://github.com/Smyken)
- [doron1](https://github.com/doron1)
- [brainfryd](https://github.com/brainfryd)
- [DGMayor](http://github.com/DGMayor)
- [Jon-theHTPC](https://github.com/Jon-theHTPC)
- [aspdend](https://github.com/aspdend)
- [RedshirtMB](https://github.com/RedshirtMB)
- [thealienamongus](https://github.com/thealienamongus)
- [brocass](https://github.com/brocass)
- [pjrollo2000](https://github.com/pjrollo2000)
- [abobader](https://github.com/abobader)
- [milli260876](https://github.com/milli260876)
- [vileboy](https://github.com/vileboy)
- [starkadius](https://github.com/starkadius)
- [wraslor](https://github.com/wraslor)
- [mrwebsmith](https://github.com/mrwebsmith)
- [rickster53](https://github.com/rickster53)
- [Tharnax](https://github.com/Tharnax)
- [0sm0](https://github.com/0sm0)
- [swhitmore](https://github.com/swhitmore)
- [DigiTM](https://github.com/DigiTM)
- [crisliv / xliv](https://github.com/crisliv)
- [Yogi](https://github.com/yogi12)
- [madFloyd](https://github.com/madFloyd)
- [yardameus](https://github.com/yardameus)
- [rrb008](https://github.com/rrb008)
- [Toonguy](https://github.com/Toonguy)
- [Alwin Hummels](https://github.com/AlwinHummels)
- [trooper11](https://github.com/trooper11)
- [danlotfy](https://github.com/danlotfy)
- [jordy1955](https://github.com/jordy1955)
- [JoshFink](https://github.com/JoshFink)
- [Detector1](https://github.com/Detector1)
- [BlackIce013](https://github.com/blackice013)
- [mporcas](https://github.com/mporcas)
- [tikuf](https://github.com/tikuf/)
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
<!--
NOTE: This is the end of the list of past Emby Contributors.
New Jellyfin contributors should add their name to the end
of the list of Jellyfin Contributors above. NOT HERE ;)
-->
- [LukePulverenti](https://github.com/LukePulverenti)
- [ebr11](https://github.com/ebr11)
- [lalmanzar](https://github.com/lalmanzar)
- [schneifu](https://github.com/schneifu)
- [Mark2xv](https://github.com/Mark2xv)
- [ScottRapsey](https://github.com/ScottRapsey)
- [skynet600](https://github.com/skynet600)
- [Cheesegeezer](https://githum.com/Cheesegeezer)
- [Radeon](https://github.com/radeonorama)
- [gcw07](https://github.com/gcw07)
- [SivaramAdhiappan](https://github.com/shivaram1190)
- [CWatkinsNash](https://github.com/CWatkinsNash)
- [sfnetwork](https://github.com/sfnetwork)
- [Logos302](https://github.com/Logos302)
- [TheWorkz](https://github.com/TheWorkz)
- [mboehler](https://github.com/mboehler)
- [KaHooli](https://github.com/KaHooli)
- [xzener](https://github.com/xzener)
- [CBers](https://github.com/CBers)
- [Sagaia](https://github.com/Sagaia)
- [JHawk111](https://github.com/JHawk111)
- [David3663](https://github.com/david3663)
- [Smyken](https://github.com/Smyken)
- [doron1](https://github.com/doron1)
- [brainfryd](https://github.com/brainfryd)
- [DGMayor](http://github.com/DGMayor)
- [Jon-theHTPC](https://github.com/Jon-theHTPC)
- [aspdend](https://github.com/aspdend)
- [RedshirtMB](https://github.com/RedshirtMB)
- [thealienamongus](https://github.com/thealienamongus)
- [brocass](https://github.com/brocass)
- [pjrollo2000](https://github.com/pjrollo2000)
- [abobader](https://github.com/abobader)
- [milli260876](https://github.com/milli260876)
- [vileboy](https://github.com/vileboy)
- [starkadius](https://github.com/starkadius)
- [wraslor](https://github.com/wraslor)
- [mrwebsmith](https://github.com/mrwebsmith)
- [rickster53](https://github.com/rickster53)
- [Tharnax](https://github.com/Tharnax)
- [0sm0](https://github.com/0sm0)
- [swhitmore](https://github.com/swhitmore)
- [DigiTM](https://github.com/DigiTM)
- [crisliv / xliv](https://github.com/crisliv)
- [Yogi](https://github.com/yogi12)
- [madFloyd](https://github.com/madFloyd)
- [yardameus](https://github.com/yardameus)
- [rrb008](https://github.com/rrb008)
- [Toonguy](https://github.com/Toonguy)
- [Alwin Hummels](https://github.com/AlwinHummels)
- [trooper11](https://github.com/trooper11)
- [danlotfy](https://github.com/danlotfy)
- [jordy1955](https://github.com/jordy1955)
- [JoshFink](https://github.com/JoshFink)
- [Detector1](https://github.com/Detector1)
- [BlackIce013](https://github.com/blackice013)
- [mporcas](https://github.com/mporcas)
- [tikuf](https://github.com/tikuf/)
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)

View File

@@ -23,6 +23,9 @@
<a href="https://features.jellyfin.org">
<img alt="Feature Requests" src="https://img.shields.io/badge/fider-vote%20on%20features-success.svg"/>
</a>
<a href="https://forum.jellyfin.org">
<img alt="Discuss on our Forum" src="https://img.shields.io/discourse/https/forum.jellyfin.org/users.svg"/>
</a>
<a href="https://matrix.to/#/+jellyfin:matrix.org">
<img alt="Chat on Matrix" src="https://img.shields.io/matrix/jellyfin:matrix.org.svg?logo=matrix"/>
</a>
@@ -42,7 +45,8 @@ Jellyfin Web is the frontend used for most of the clients available for end user
### Dependencies
- [Node.js](https://nodejs.org/en/download)
- npm (included in Node.js)
- [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install)
- Gulp-cli
### Getting Started
@@ -56,48 +60,17 @@ Jellyfin Web is the frontend used for most of the clients available for end user
2. Install build dependencies in the project directory.
```sh
npm install
yarn install
```
3. Run the web client with webpack for local development.
```sh
npm start
yarn serve
```
4. Build the client with sourcemaps available.
```sh
npm run build:development
yarn build:development
```
## Directory Structure
```
.
└── src
├── apps
│   ├── dashboard # Admin dashboard app layout and routes
│   ├── experimental # New experimental app layout and routes
│   └── stable # Classic (stable) app layout and routes
├── assets # Static assets
├── components # Higher order visual components and React components
├── controllers # Legacy page views and controllers 🧹
├── elements # Basic webcomponents and React wrappers 🧹
├── hooks # Custom React hooks
├── lib # Reusable libraries
│   ├── globalize # Custom localization library
│   ├── legacy # Polyfills for legacy browsers
│   ├── navdrawer # Navigation drawer library for classic layout
│   └── scroller # Content scrolling library
├── plugins # Client plugins
├── scripts # Random assortment of visual components and utilities 🐉
├── strings # Translation files
├── styles # Common app Sass stylesheets
├── themes # CSS themes
├── types # Common TypeScript interfaces/types
└── utils # Utility functions
```
- 🧹 &mdash; Needs cleanup
- 🐉 &mdash; Serious mess (Here be dragons)

View File

@@ -11,12 +11,11 @@ module.exports = {
useBuiltIns: 'usage',
corejs: 3
}
],
'@babel/preset-react'
]
],
plugins: [
'@babel/plugin-transform-class-properties',
'@babel/plugin-transform-private-methods',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-private-methods',
'babel-plugin-dynamic-import-polyfill'
]
};

110
build.sh Executable file
View File

@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# build.sh - Build Jellyfin binary packages
# Part of the Jellyfin Project
set -o errexit
set -o pipefail
usage() {
echo -e "build.sh - Build Jellyfin binary packages"
echo -e "Usage:"
echo -e " $0 -t/--type <BUILD_TYPE> -p/--platform <PLATFORM> [-k/--keep-artifacts] [-l/--list-platforms]"
echo -e "Notes:"
echo -e " * BUILD_TYPE can be one of: [native, docker] and must be specified"
echo -e " * native: Build using the build script in the host OS"
echo -e " * docker: Build using the build script in a standardized Docker container"
echo -e " * PLATFORM can be any platform shown by -l/--list-platforms and must be specified"
echo -e " * If -k/--keep-artifacts is specified, transient artifacts (e.g. Docker containers) will be"
echo -e " retained after the build is finished; the source directory will still be cleaned"
echo -e " * If -l/--list-platforms is specified, all other arguments are ignored; the script will print"
echo -e " the list of supported platforms and exit"
}
list_platforms() {
declare -a platforms
platforms=(
$( find deployment -maxdepth 1 -mindepth 1 -name "build.*" | awk -F'.' '{ $1=""; printf $2; if ($3 != ""){ printf "." $3; }; if ($4 != ""){ printf "." $4; }; print ""; }' | sort )
)
echo -e "Valid platforms:"
echo
for platform in ${platforms[@]}; do
echo -e "* ${platform} : $( grep '^#=' deployment/build.${platform} | sed 's/^#= //' )"
done
}
do_build_native() {
export IS_DOCKER=NO
deployment/build.${PLATFORM}
}
do_build_docker() {
if ! dpkg --print-architecture | grep -q 'amd64'; then
echo "Docker-based builds only support amd64-based cross-building; use a 'native' build instead."
exit 1
fi
if [[ ! -f deployment/Dockerfile.${PLATFORM} ]]; then
echo "Missing Dockerfile for platform ${PLATFORM}"
exit 1
fi
if [[ ${KEEP_ARTIFACTS} == YES ]]; then
docker_args=""
else
docker_args="--rm"
fi
docker build . -t "jellyfin-builder.${PLATFORM}" -f deployment/Dockerfile.${PLATFORM}
mkdir -p ${ARTIFACT_DIR}
docker run $docker_args -v "${SOURCE_DIR}:/jellyfin" -v "${ARTIFACT_DIR}:/dist" "jellyfin-builder.${PLATFORM}"
}
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-t|--type)
BUILD_TYPE="$2"
shift
shift
;;
-p|--platform)
PLATFORM="$2"
shift
shift
;;
-k|--keep-artifacts)
KEEP_ARTIFACTS=YES
shift
;;
-l|--list-platforms)
list_platforms
exit 0
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option $1"
usage
exit 1
;;
esac
done
if [[ -z ${BUILD_TYPE} || -z ${PLATFORM} ]]; then
usage
exit 1
fi
export SOURCE_DIR="$( pwd )"
export ARTIFACT_DIR="${SOURCE_DIR}/../bin/${PLATFORM}"
# Determine build type
case ${BUILD_TYPE} in
native)
do_build_native
;;
docker)
do_build_docker
;;
esac

9
build.yaml Normal file
View File

@@ -0,0 +1,9 @@
---
# We just wrap `build` so this is really it
name: "jellyfin-web"
version: "10.7.7"
packages:
- debian.all
- fedora.all
- centos.all
- portable

View File

@@ -7,7 +7,7 @@ set -o pipefail
set -o xtrace
usage() {
echo -e "bump_version - increase the shared version"
echo -e "bump_version - increase the shared version and generate changelogs"
echo -e ""
echo -e "Usage:"
echo -e " $ bump_version <new_version>"
@@ -18,12 +18,74 @@ if [[ -z $1 ]]; then
exit 1
fi
new_version="$1"
new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )"
shared_version_file="src/components/apphost.js"
build_file="./build.yaml"
# Bump the NPM version
npm --no-git-tag-version --allow-same-version version v${new_version_sed}
new_version="$1"
# Parse the version from shared version file
old_version="$( grep "appVersion" ${shared_version_file} | head -1 | sed -E "s/var appVersion = '([0-9\.]+)';/\1/" | tr -d '[:space:]' )"
echo "Old version in appHost is: $old_version"
# Set the shared version to the specified new_version
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )"
sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file}
old_version="$( grep "version:" ${build_file} | sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/' )"
echo "Old version in ${build_file}: ${old_version}"
# Set the build.yaml version to the specified new_version
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
sed -i "s/${old_version_sed}/${new_version}/g" ${build_file}
if [[ ${new_version} == *"-"* ]]; then
new_version_deb="$( sed 's/-/~/g' <<<"${new_version}" )"
else
new_version_deb="${new_version}-1"
fi
# Write out a temporary Debian changelog with our new stuff appended and some templated formatting
debian_changelog_file="debian/changelog"
debian_changelog_temp="$( mktemp )"
# Create new temp file with our changelog
echo -e "jellyfin-web (${new_version_deb}) unstable; urgency=medium
* New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version}
-- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
" >> ${debian_changelog_temp}
cat ${debian_changelog_file} >> ${debian_changelog_temp}
# Move into place
mv ${debian_changelog_temp} ${debian_changelog_file}
# Write out a temporary Yum changelog with our new stuff prepended and some templated formatting
fedora_spec_file="fedora/jellyfin-web.spec"
fedora_changelog_temp="$( mktemp )"
fedora_spec_temp_dir="$( mktemp -d )"
fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin-web.spec.tmp"
# Make a copy of our spec file for hacking
cp ${fedora_spec_file} ${fedora_spec_temp_dir}/
pushd ${fedora_spec_temp_dir}
# Split out the stuff before and after changelog
csplit jellyfin-web.spec "/^%changelog/" # produces xx00 xx01
# Update the version in xx00
sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00
# Remove the header from xx01
sed -i '/^%changelog/d' xx01
# Create new temp file with our changelog
echo -e "%changelog
* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version}" >> ${fedora_changelog_temp}
cat xx01 >> ${fedora_changelog_temp}
# Reassembble
cat xx00 ${fedora_changelog_temp} > ${fedora_spec_temp}
popd
# Move into place
mv ${fedora_spec_temp} ${fedora_spec_file}
# Clean up
rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir}
# Stage the changed files for commit
git add .
git status -v
git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file}
git status

View File

@@ -1,10 +0,0 @@
module.exports = {
preset: [
'default',
// Turn off `mergeLonghand` because it combines `padding-*` and `margin-*`,
// breaking fallback styles.
// https://github.com/cssnano/cssnano/issues/1163
// https://github.com/cssnano/cssnano/issues/1192
{ mergeLonghand: false }
]
};

45
debian/changelog vendored Normal file
View File

@@ -0,0 +1,45 @@
jellyfin-web (10.7.7-1) unstable; urgency=medium
* New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.7
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 05 Sep 2021 22:32:59 -0400
jellyfin-web (10.7.6-1) unstable; urgency=medium
* New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.6
-- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 20 May 2021 22:06:52 -0400
jellyfin-web (10.7.5-1) unstable; urgency=medium
* New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.5
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 22:08:30 -0400
jellyfin-web (10.7.4-1) unstable; urgency=medium
* New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.4
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 21:16:07 -0400
jellyfin-web (10.7.3-1) unstable; urgency=medium
* New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.3
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 20:00:22 -0400
jellyfin-web (10.7.2-1) unstable; urgency=medium
* New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.2
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 11 Apr 2021 14:19:38 -0400
jellyfin-web (10.7.1-1) unstable; urgency=medium
* New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.1
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 21 Mar 2021 19:23:29 -0400
jellyfin-web (10.7.0-1) unstable; urgency=medium
* New upstream version 10.7.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.0

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
8

1
debian/conffiles vendored Normal file
View File

@@ -0,0 +1 @@
/usr/share/jellyfin/web/config.json

16
debian/control vendored Normal file
View File

@@ -0,0 +1,16 @@
Source: jellyfin-web
Section: misc
Priority: optional
Maintainer: Jellyfin Team <team@jellyfin.org>
Build-Depends: debhelper (>= 9),
npm | nodejs
Standards-Version: 3.9.4
Homepage: https://jellyfin.org/
Vcs-Git: https://github.org/jellyfin/jellyfin-web.git
Vcs-Browser: https://github.org/jellyfin/jellyfin-web
Package: jellyfin-web
Recommends: jellyfin-server
Architecture: all
Description: Jellyfin is the Free Software Media System.
This package provides the Jellyfin web client.

28
debian/copyright vendored Normal file
View File

@@ -0,0 +1,28 @@
Format: http://dep.debian.net/deps/dep5
Upstream-Name: jellyfin-web
Source: https://github.com/jellyfin/jellyfin-web
Files: *
Copyright: 2018-2020 Jellyfin Team
License: GPL-3.0
Files: debian/*
Copyright: 2020 Joshua Boniface <joshua@boniface.me>
License: GPL-3.0
License: GPL-3.0
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
.
On Debian systems, the complete text of the GNU General
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".

6
debian/gbp.conf vendored Normal file
View File

@@ -0,0 +1,6 @@
[DEFAULT]
pristine-tar = False
cleaner = fakeroot debian/rules clean
[import-orig]
filter = [ ".git*", ".hg*", ".vs*", ".vscode*" ]

1
debian/install vendored Normal file
View File

@@ -0,0 +1 @@
web usr/share/jellyfin/

1
debian/po/POTFILES.in vendored Normal file
View File

@@ -0,0 +1 @@
[type: gettext/rfc822deb] templates

57
debian/po/templates.pot vendored Normal file
View File

@@ -0,0 +1,57 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: jellyfin-server\n"
"Report-Msgid-Bugs-To: jellyfin-server@packages.debian.org\n"
"POT-Creation-Date: 2015-06-12 20:51-0600\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#. Type: note
#. Description
#: ../templates:1001
msgid "Jellyfin permission info:"
msgstr ""
#. Type: note
#. Description
#: ../templates:1001
msgid ""
"Jellyfin by default runs under a user named \"jellyfin\". Please ensure that the "
"user jellyfin has read and write access to any folders you wish to add to your "
"library. Otherwise please run jellyfin under a different user."
msgstr ""
#. Type: string
#. Description
#: ../templates:2001
msgid "Username to run Jellyfin as:"
msgstr ""
#. Type: string
#. Description
#: ../templates:2001
msgid "The user that jellyfin will run as."
msgstr ""
#. Type: note
#. Description
#: ../templates:3001
msgid "Jellyfin still running"
msgstr ""
#. Type: note
#. Description
#: ../templates:3001
msgid "Jellyfin is currently running. Please close it and try again."
msgstr ""

20
debian/rules vendored Executable file
View File

@@ -0,0 +1,20 @@
#! /usr/bin/make -f
export DH_VERBOSE=1
%:
dh $@
# disable "make check"
override_dh_auto_test:
# disable stripping debugging symbols
override_dh_clistrip:
override_dh_auto_build:
npx yarn install
mv $(CURDIR)/dist $(CURDIR)/web
override_dh_auto_clean:
test -d $(CURDIR)/dist && rm -rf '$(CURDIR)/dist' || true
test -d $(CURDIR)/web && rm -rf '$(CURDIR)/web' || true
test -d $(CURDIR)/node_modules && rm -rf '$(CURDIR)/node_modules' || true

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
1.0

7
debian/source/options vendored Normal file
View File

@@ -0,0 +1,7 @@
tar-ignore='.git*'
tar-ignore='**/.git'
tar-ignore='**/.hg'
tar-ignore='**/.vs'
tar-ignore='**/.vscode'
tar-ignore='deployment'
tar-ignore='*.deb'

View File

@@ -0,0 +1,29 @@
FROM centos:7
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV IS_DOCKER=YES
# Prepare CentOS environment
RUN yum update -y \
&& yum install -y epel-release \
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core nodejs-yarn autoconf automake glibc-devel
# Install recent NodeJS and Yarn
RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \
&& rpm -i https://rpm.nodesource.com/pub_10.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \
&& yum install -y yarn
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.centos /build.sh
VOLUME ${SOURCE_DIR}
VOLUME ${ARTIFACT_DIR}
ENTRYPOINT ["/build.sh"]

View File

@@ -0,0 +1,27 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
&& apt-get install -y debhelper mmv npm git
# Prepare Yarn
RUN npm install -g yarn
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.debian /build.sh
VOLUME ${SOURCE_DIR}
VOLUME ${ARTIFACT_DIR}
ENTRYPOINT ["/build.sh"]

View File

@@ -0,0 +1,11 @@
FROM node:lts-alpine
ARG SOURCE_DIR=/src
ARG ARTIFACT_DIR=/jellyfin-web
RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3
WORKDIR ${SOURCE_DIR}
COPY . .
RUN yarn install && mv dist ${ARTIFACT_DIR}

View File

@@ -0,0 +1,23 @@
FROM fedora:33
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV IS_DOCKER=YES
# Prepare Fedora environment
RUN dnf update -y \
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs nodejs-yarn autoconf automake glibc-devel
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh
VOLUME ${SOURCE_DIR}
VOLUME ${ARTIFACT_DIR}
ENTRYPOINT ["/build.sh"]

View File

@@ -0,0 +1,26 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
&& apt-get install -y mmv npm git
# Prepare Yarn
RUN npm install -g yarn
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh
VOLUME ${SOURCE_DIR}
VOLUME ${ARTIFACT_DIR}
ENTRYPOINT ["/build.sh"]

41
deployment/build.centos Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
set -o errexit
set -o xtrace
# move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
pushd fedora
PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin-web.spec
sed -i "/%changelog/q" jellyfin-web.spec
cat <<EOF >>jellyfin-web.spec
* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
- Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID}
EOF
popd
fi
# build rpm
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# move the artifacts
mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
rm -f fedora/jellyfin*.tar.gz
cp -a /tmp/yarn.lock yarn.lock
popd

39
deployment/build.debian Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
set -o errexit
set -o xtrace
# move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
pushd debian
PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
cat <<EOF >changelog
jellyfin-web (${BUILD_ID}-unstable) unstable; urgency=medium
* Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID}
-- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
EOF
popd
fi
# build deb
dpkg-buildpackage -us -uc --pre-clean --post-clean
mkdir -p ${ARTIFACT_DIR}
mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}
cp -a /tmp/yarn.lock yarn.lock
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
popd

41
deployment/build.fedora Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
set -o errexit
set -o xtrace
# move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
pushd fedora
PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin-web.spec
sed -i "/%changelog/q" jellyfin-web.spec
cat <<EOF >>jellyfin-web.spec
* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
- Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID}
EOF
popd
fi
# build rpm
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# move the artifacts
mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
rm -f fedora/jellyfin*.tar.gz
cp -a /tmp/yarn.lock yarn.lock
popd

30
deployment/build.portable Executable file
View File

@@ -0,0 +1,30 @@
#!/bin/bash
set -o errexit
set -o xtrace
# move to source directory
pushd ${SOURCE_DIR}
# get version
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
version="${BUILD_ID}"
else
version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
fi
# build archives
npx yarn install
mv dist jellyfin-web_${version}
tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version}
rm -rf dist
# move the artifacts
mkdir -p ${ARTIFACT_DIR}
mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
popd

21
fedora/Makefile Normal file
View File

@@ -0,0 +1,21 @@
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' fedora/jellyfin-web.spec)
srpm:
cd fedora/; \
SOURCE_DIR=.. \
WORKDIR="$${PWD}"; \
tar \
--transform "s,^\.,jellyfin-web-$(VERSION)," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='deployment' \
--exclude='*.deb' \
--exclude='*.rpm' \
--exclude='jellyfin-web-$(VERSION).tar.gz' \
-czf "jellyfin-web-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./
cd fedora/; \
rpmbuild -bs jellyfin-web.spec \
--define "_sourcedir $$PWD/" \
--define "_srcrpmdir $(outdir)"

68
fedora/jellyfin-web.spec Normal file
View File

@@ -0,0 +1,68 @@
%global debug_package %{nil}
Name: jellyfin-web
Version: 10.7.7
Release: 1%{?dist}
Summary: The Free Software Media System web client
License: GPLv3
URL: https://jellyfin.org
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz`
Source0: jellyfin-web-%{version}.tar.gz
%if 0%{?centos}
BuildRequires: yarn
%else
BuildRequires: nodejs-yarn
%endif
# sadly the yarn RPM at https://dl.yarnpkg.com/rpm/ uses git but doesn't Requires: it
# ditto for Fedora's yarn RPM
BuildRequires: git
BuildArch: noarch
%if 0%{?fedora} >= 33
BuildRequires: nodejs
%endif
# Disable Automatic Dependency Processing
AutoReqProv: no
%description
Jellyfin is a free software media system that puts you in control of managing and streaming your media.
%prep
%autosetup -n jellyfin-web-%{version} -b 0
%build
%install
yarn install
%{__mkdir} -p %{buildroot}%{_datadir}
mv dist %{buildroot}%{_datadir}/jellyfin-web
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
%files
%defattr(644,root,root,755)
%{_datadir}/jellyfin-web
%{_datadir}/licenses/jellyfin/LICENSE
%changelog
* Sun Sep 05 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.7
* Thu May 20 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.6
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.5
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.4
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.3
* Sun Apr 11 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.2
* Sun Mar 21 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.1
* Mon Mar 08 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New stable release 10.7.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.0
* Mon Jul 27 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release

41598
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,130 +1,86 @@
{
"name": "jellyfin-web",
"version": "10.10.0",
"version": "0.0.0",
"description": "Web interface for Jellyfin",
"repository": "https://github.com/jellyfin/jellyfin-web",
"license": "GPL-2.0-or-later",
"devDependencies": {
"@babel/core": "7.24.9",
"@babel/plugin-transform-class-properties": "7.24.7",
"@babel/plugin-transform-modules-umd": "7.24.7",
"@babel/plugin-transform-private-methods": "7.24.7",
"@babel/preset-env": "7.24.8",
"@babel/preset-react": "7.24.7",
"@types/dompurify": "3.0.5",
"@types/escape-html": "1.0.4",
"@types/loadable__component": "5.13.9",
"@types/lodash-es": "4.17.12",
"@types/markdown-it": "14.1.2",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"@uupaa/dynamic-import-polyfill": "1.0.2",
"@vitest/coverage-v8": "2.0.5",
"autoprefixer": "10.4.19",
"babel-loader": "9.1.3",
"babel-plugin-dynamic-import-polyfill": "1.0.0",
"clean-webpack-plugin": "4.0.0",
"confusing-browser-globals": "1.0.11",
"copy-webpack-plugin": "12.0.2",
"cross-env": "7.0.3",
"css-loader": "7.1.2",
"cssnano": "7.0.4",
"es-check": "7.2.1",
"eslint": "8.57.0",
"eslint-plugin-compat": "4.2.0",
"eslint-plugin-eslint-comments": "3.2.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsx-a11y": "6.9.0",
"eslint-plugin-react": "7.35.0",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-sonarjs": "0.25.1",
"expose-loader": "5.0.0",
"fork-ts-checker-webpack-plugin": "9.0.2",
"html-loader": "5.1.0",
"html-webpack-plugin": "5.6.0",
"jsdom": "24.1.1",
"mini-css-extract-plugin": "2.9.0",
"postcss": "8.4.40",
"postcss-loader": "8.1.1",
"postcss-preset-env": "9.6.0",
"postcss-scss": "4.0.9",
"sass": "1.77.8",
"sass-loader": "15.0.0",
"source-map-loader": "5.0.0",
"speed-measure-webpack-plugin": "1.5.0",
"style-loader": "4.0.0",
"stylelint": "15.11.0",
"stylelint-config-rational-order": "0.1.2",
"stylelint-no-browser-hacks": "1.3.0",
"stylelint-order": "6.0.4",
"stylelint-scss": "5.3.2",
"ts-loader": "9.5.1",
"typescript": "5.5.4",
"vitest": "2.0.5",
"webpack": "5.93.0",
"webpack-bundle-analyzer": "4.10.2",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.0.4",
"webpack-merge": "6.0.1",
"worker-loader": "3.0.8"
"@babel/core": "^7.12.9",
"@babel/eslint-parser": "^7.12.1",
"@babel/eslint-plugin": "^7.12.1",
"@babel/plugin-proposal-class-properties": "^7.10.1",
"@babel/plugin-proposal-private-methods": "^7.12.1",
"@babel/plugin-transform-modules-umd": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"@uupaa/dynamic-import-polyfill": "^1.0.2",
"autoprefixer": "^9.8.6",
"babel-loader": "^8.2.2",
"babel-plugin-dynamic-import-polyfill": "^1.0.0",
"clean-webpack-plugin": "^3.0.0",
"confusing-browser-globals": "^1.0.10",
"copy-webpack-plugin": "^6.3.2",
"css-loader": "^5.0.1",
"cssnano": "^4.1.10",
"eslint": "^7.14.0",
"eslint-plugin-compat": "^3.5.1",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-promise": "^4.2.1",
"expose-loader": "^1.0.3",
"file-loader": "^6.2.0",
"html-loader": "^1.1.0",
"html-webpack-plugin": "^4.5.0",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
"source-map-loader": "^1.1.1",
"style-loader": "^2.0.0",
"stylelint": "^13.8.0",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-no-browser-hacks": "^1.2.1",
"stylelint-order": "^4.1.0",
"stylelint-scss": "^3.18.0",
"webpack": "^5.9.0",
"webpack-cli": "^4.0.0",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2",
"workbox-webpack-plugin": "^6.1.5",
"worker-plugin": "^5.0.0"
},
"dependencies": {
"@emotion/react": "11.13.0",
"@emotion/styled": "11.13.0",
"@fontsource/noto-sans": "5.0.22",
"@fontsource/noto-sans-hk": "5.0.20",
"@fontsource/noto-sans-jp": "5.0.19",
"@fontsource/noto-sans-kr": "5.0.19",
"@fontsource/noto-sans-sc": "5.0.20",
"@fontsource/noto-sans-tc": "5.0.20",
"@jellyfin/libass-wasm": "4.2.2",
"@jellyfin/sdk": "0.0.0-unstable.202408050429",
"@mui/icons-material": "5.15.19",
"@mui/material": "5.15.19",
"@mui/x-data-grid": "7.6.1",
"@react-hook/resize-observer": "2.0.1",
"@tanstack/react-query": "5.51.11",
"@tanstack/react-query-devtools": "5.51.11",
"@types/react-lazy-load-image-component": "1.6.4",
"abortcontroller-polyfill": "1.7.5",
"blurhash": "2.0.5",
"blurhash": "^1.1.3",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"classnames": "2.5.1",
"core-js": "3.37.1",
"date-fns": "2.30.0",
"dompurify": "3.0.1",
"epubjs": "0.3.93",
"escape-html": "1.0.3",
"fast-text-encoding": "1.0.6",
"flv.js": "1.6.2",
"headroom.js": "0.12.0",
"history": "5.3.0",
"hls.js": "1.5.13",
"intersection-observer": "0.12.2",
"jellyfin-apiclient": "1.11.0",
"jquery": "3.7.1",
"jstree": "3.3.16",
"libarchive.js": "1.3.0",
"lodash-es": "4.17.21",
"markdown-it": "14.1.0",
"material-design-icons-iconfont": "6.7.0",
"native-promise-only": "0.8.1",
"pdfjs-dist": "3.11.174",
"react": "18.3.1",
"react-blurhash": "0.3.0",
"react-dom": "18.3.1",
"react-lazy-load-image-component": "1.6.2",
"react-router-dom": "6.25.1",
"resize-observer-polyfill": "1.5.1",
"screenfull": "6.0.2",
"sortablejs": "1.15.2",
"swiper": "11.1.7",
"usehooks-ts": "3.1.0",
"webcomponents.js": "0.7.24",
"whatwg-fetch": "3.6.20"
"core-js": "^3.8.0",
"date-fns": "^2.16.1",
"epubjs": "^0.3.85",
"fast-text-encoding": "^1.0.3",
"flv.js": "^1.5.0",
"fontsource-noto-sans": "^3.1.5",
"fontsource-noto-sans-hk": "^3.1.5",
"fontsource-noto-sans-jp": "^3.1.5",
"fontsource-noto-sans-kr": "^3.1.5",
"fontsource-noto-sans-sc": "^3.1.5",
"headroom.js": "^0.12.0",
"hls.js": "^0.14.17",
"intersection-observer": "^0.12.0",
"jellyfin-apiclient": "^1.8.0",
"jquery": "^3.5.1",
"jstree": "^3.3.10",
"libarchive.js": "^1.3.0",
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv",
"material-design-icons-iconfont": "^6.1.0",
"native-promise-only": "^0.8.0-a",
"page": "^1.11.6",
"pdfjs-dist": "2.5.207",
"resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.2",
"sortablejs": "^1.12.0",
"swiper": "^6.3.5",
"webcomponents.js": "^0.7.24",
"whatwg-fetch": "^3.5.0",
"workbox-core": "^5.1.4",
"workbox-precaching": "^5.1.4"
},
"browserslist": [
"last 2 Firefox versions",
@@ -143,21 +99,14 @@
"Firefox ESR"
],
"scripts": {
"start": "npm run serve",
"start": "yarn serve",
"serve": "webpack serve --config webpack.dev.js",
"build:analyze": "cross-env NODE_ENV=\"production\" webpack --config webpack.analyze.js",
"prepare": "node ./scripts/prepare.js",
"build:development": "webpack --config webpack.dev.js",
"build:production": "cross-env NODE_ENV=\"production\" webpack --config webpack.prod.js",
"build:check": "tsc --noEmit",
"escheck": "es-check",
"lint": "eslint \"./\"",
"test": "vitest --watch=false --config vite.config.ts",
"test:watch": "vitest --config vite.config.ts",
"stylelint": "stylelint \"src/**/*.{css,scss}\""
},
"engines": {
"node": ">=20.0.0",
"npm": ">=9.6.4",
"yarn": "YARN NO LONGER USED - use npm instead."
"build:production": "webpack --config webpack.prod.js",
"lint": "eslint \"src/\"",
"stylelint": "yarn stylelint:css && yarn stylelint:scss",
"stylelint:css": "stylelint \"src/**/*.css\"",
"stylelint:scss": "stylelint --config=\".stylelintrc.scss.json\" \"src/**/*.scss\""
}
}

View File

@@ -7,8 +7,8 @@ const config = () => ({
plugins: [
// Explicitly specify browserslist to override ones from node_modules
// For example, Swiper has it in its package.json
postcssPresetEnv({ browsers: packageConfig.browserslist }),
autoprefixer({ overrideBrowserslist: packageConfig.browserslist }),
postcssPresetEnv({browsers: packageConfig.browserslist}),
autoprefixer({overrideBrowserslist: packageConfig.browserslist}),
cssnano()
]
});

12
scripts/prepare.js Executable file
View File

@@ -0,0 +1,12 @@
const { execSync } = require('child_process');
/**
* The npm `prepare` script needs to run a build to support installing
* a package from git repositories (this is dumb but a limitation of how
* npm behaves). We don't want to run these in CI though because
* building is slow so this script will skip the build when the
* `SKIP_PREPARE` environment variable has been set.
*/
if (!process.env.SKIP_PREPARE) {
execSync('webpack --config webpack.prod.js', { stdio: 'inherit' });
}

View File

@@ -1,22 +0,0 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import React from 'react';
import { ApiProvider } from 'hooks/useApi';
import { WebConfigProvider } from 'hooks/useWebConfig';
import { queryClient } from 'utils/query/queryClient';
import RootAppRouter from 'RootAppRouter';
const RootApp = () => (
<QueryClientProvider client={queryClient}>
<ApiProvider>
<WebConfigProvider>
<RootAppRouter />
</WebConfigProvider>
</ApiProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
export default RootApp;

View File

@@ -1,54 +0,0 @@
import React from 'react';
import {
RouterProvider,
createHashRouter,
Outlet,
useLocation
} from 'react-router-dom';
import { DASHBOARD_APP_PATHS, DASHBOARD_APP_ROUTES } from 'apps/dashboard/routes/routes';
import { EXPERIMENTAL_APP_ROUTES } from 'apps/experimental/routes/routes';
import { STABLE_APP_ROUTES } from 'apps/stable/routes/routes';
import AppHeader from 'components/AppHeader';
import Backdrop from 'components/Backdrop';
import { createRouterHistory } from 'components/router/routerHistory';
import UserThemeProvider from 'themes/UserThemeProvider';
const layoutMode = localStorage.getItem('layout');
const isExperimentalLayout = layoutMode === 'experimental';
const router = createHashRouter([
{
element: <RootAppLayout />,
children: [
...(isExperimentalLayout ? EXPERIMENTAL_APP_ROUTES : STABLE_APP_ROUTES),
...DASHBOARD_APP_ROUTES
]
}
]);
export const history = createRouterHistory(router);
export default function RootAppRouter() {
return <RouterProvider router={router} />;
}
/**
* Layout component that renders legacy components required on all pages.
* NOTE: The app will crash if these get removed from the DOM.
*/
function RootAppLayout() {
const location = useLocation();
const isNewLayoutPath = Object.values(DASHBOARD_APP_PATHS)
.some(path => location.pathname.startsWith(`/${path}`));
return (
<UserThemeProvider>
<Backdrop />
<AppHeader isHidden={isExperimentalLayout || isNewLayoutPath} />
<Outlet />
</UserThemeProvider>
);
}

361
src/apiclient.d.ts vendored
View File

@@ -1,361 +0,0 @@
// TODO: Move to jellyfin-apiclient
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module 'jellyfin-apiclient' {
import type {
AllThemeMediaResult,
AuthenticationResult,
BaseItemDto,
BaseItemDtoQueryResult,
BufferRequestDto,
ClientCapabilities,
CountryInfo,
CultureDto,
DeviceOptions,
DisplayPreferencesDto,
EndPointInfo,
FileSystemEntryInfo,
GeneralCommand,
GroupInfoDto,
GuideInfo,
IgnoreWaitRequestDto,
ImageInfo,
ImageProviderInfo,
ImageType,
ItemCounts,
LiveTvInfo,
MovePlaylistItemRequestDto,
NewGroupRequestDto,
NextItemRequestDto,
NotificationResultDto,
NotificationsSummaryDto,
ParentalRating,
PingRequestDto,
PlaybackInfoResponse,
PlaybackProgressInfo,
PlaybackStartInfo,
PlaybackStopInfo,
PlayCommand,
PlaystateCommand,
PluginInfo,
PluginSecurityInfo,
PreviousItemRequestDto,
QueryFiltersLegacy,
QueueRequestDto,
QuickConnectResult,
QuickConnectState,
ReadyRequestDto,
RecommendationDto,
RemoteImageResult,
RemoveFromPlaylistRequestDto,
SearchHintResult,
SeekRequestDto,
SeriesTimerInfoDto,
SeriesTimerInfoDtoQueryResult,
ServerConfiguration,
SessionInfo,
SetPlaylistItemRequestDto,
SetRepeatModeRequestDto,
SetShuffleModeRequestDto,
SystemInfo,
TaskInfo,
TaskTriggerInfo,
TimerInfoDto,
TimerInfoDtoQueryResult,
UserConfiguration,
UserDto,
UserItemDataDto,
UserPolicy,
UtcTimeResponse,
VirtualFolderInfo
} from '@jellyfin/sdk/lib/generated-client';
import { ConnectionState } from './utils/jellyfin-apiclient/ConnectionState';
class ApiClient {
constructor(serverAddress: string, appName: string, appVersion: string, deviceName: string, deviceId: string);
accessToken(): string;
addMediaPath(virtualFolderName: string, mediaPath: string, networkSharePath: string, refreshLibrary?: boolean): Promise<void>;
addVirtualFolder(name: string, type?: string, refreshLibrary?: boolean, libraryOptions?: any): Promise<void>;
ajax(request: any): Promise<any>;
appName(): string;
appVersion(): string;
authenticateUserByName(name: string, password: string): Promise<AuthenticationResult>;
cancelLiveTvSeriesTimer(id: string): Promise<void>;
cancelLiveTvTimer(id: string): Promise<void>;
cancelSyncItems(itemIds: string[], targetId?: string): Promise<void>;
clearAuthenticationInfo(): void;
clearUserItemRating(userId: string, itemId: string): Promise<UserItemDataDto>;
closeWebSocket(): void;
createLiveTvSeriesTimer(item: string): Promise<void>;
createLiveTvTimer(item: string): Promise<void>;
createPackageReview(review: any): Promise<any>;
createSyncPlayGroup(options?: NewGroupRequestDto): Promise<void>;
createUser(user: UserDto): Promise<UserDto>;
deleteDevice(deviceId: string): Promise<void>;
deleteItemImage(itemId: string, imageType: ImageType, imageIndex?: number): Promise<void>;
deleteItem(itemId: string): Promise<void>;
deleteLiveTvRecording(id: string): Promise<void>;
deleteUserImage(userId: string, imageType: ImageType, imageIndex?: number): Promise<void>;
deleteUser(userId: string): Promise<void>;
detectBitrate(force: boolean): Promise<number>;
deviceId(): string;
deviceName(): string;
disablePlugin(id: string, version: string): Promise<void>;
downloadRemoteImage(options: any): Promise<void>;
enablePlugin(id: string, version: string): Promise<void>;
encodeName(name: string): string;
ensureWebSocket(): void;
fetch(request: any, includeAuthorization?: boolean): Promise<any>;
fetchWithFailover(request: any, enableReconnection?: boolean): Promise<any>;
getAdditionalVideoParts(userId?: string, itemId: string): Promise<BaseItemDtoQueryResult>;
getAlbumArtists(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getAncestorItems(itemId: string, userId?: string): Promise<BaseItemDto[]>;
getArtist(name: string, userId?: string): Promise<BaseItemDto>;
getArtists(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getAvailablePlugins(options?: any): Promise<PluginInfo[]>;
getAvailableRemoteImages(options: any): Promise<RemoteImageResult>;
getContentUploadHistory(): Promise<any>;
getCountries(): Promise<CountryInfo[]>;
getCriticReviews(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getCultures(): Promise<CultureDto[]>;
getCurrentUser(cache?: boolean): Promise<UserDto>;
getCurrentUserId(): string;
getDateParamValue(date: Date): string;
getDefaultImageQuality(imageType: ImageType): number;
getDevicesOptions(): Promise<DeviceOptions>;
getDirectoryContents(path: string, options?: any): Promise<FileSystemEntryInfo[]>;
getDisplayPreferences(id: string, userId: string, app: string): Promise<DisplayPreferencesDto>;
getDownloadSpeed(byteSize: number): Promise<number>;
getDrives(): Promise<FileSystemEntryInfo[]>;
getEndpointInfo(): Promise<EndPointInfo>;
getEpisodes(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getFilters(options?: any): Promise<QueryFiltersLegacy>;
getGenre(name: string, userId?: string): Promise<BaseItemDto>;
getGenres(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getImageUrl(itemId: string, options?: any): string;
getInstalledPlugins(): Promise<PluginInfo[]>;
getInstantMixFromItem(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getIntros(itemId: string): Promise<BaseItemDtoQueryResult>;
getItemCounts(userId?: string): Promise<ItemCounts>;
getItemDownloadUrl(itemId: string): string;
getItemImageInfos(itemId: string): Promise<ImageInfo[]>;
getItems(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getItem(userId: string, itemId: string): Promise<BaseItemDto>;
getJSON(url: string, includeAuthorization?: boolean): Promise<any>;
getLatestItems(options?: any): Promise<BaseItemDto[]>;
getLiveStreamMediaInfo(liveStreamId: string): Promise<any>;
getLiveTvChannel(id: string, userId?: string): Promise<BaseItemDto>;
getLiveTvChannels(options?: any): Promise<BaseItemDtoQueryResult>;
getLiveTvGuideInfo(userId: string): Promise<GuideInfo>;
getLiveTvInfo(userId: string): Promise<LiveTvInfo>;
getLiveTvProgram(id: string, userId?: string): Promise<BaseItemDto>;
getLiveTvPrograms(options?: any): Promise<BaseItemDtoQueryResult>;
getLiveTvRecommendedPrograms(options?: any): Promise<BaseItemDtoQueryResult>;
getLiveTvRecordingGroup(id: string): Promise<BaseItemDto>;
getLiveTvRecordingGroups(options?: any): Promise<BaseItemDtoQueryResult>;
getLiveTvRecording(id: string, userId?: string): Promise<BaseItemDto>;
getLiveTvRecordingSeries(options?: any): Promise<BaseItemDtoQueryResult>;
getLiveTvRecordings(options?: any): Promise<BaseItemDtoQueryResult>;
getLiveTvSeriesTimer(id: string): Promise<SeriesTimerInfoDto>;
getLiveTvSeriesTimers(options?: any): Promise<SeriesTimerInfoDtoQueryResult>;
getLiveTvTimer(id: string): Promise<TimerInfoDto>;
getLiveTvTimers(options?: any): Promise<TimerInfoDtoQueryResult>;
getLocalTrailers(userId: string, itemId: string): Promise<BaseItemDto[]>;
getMovieRecommendations(options?: any): Promise<RecommendationDto[]>;
getMusicGenre(name: string, userId?: string): Promise<BaseItemDto>;
getMusicGenres(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getNamedConfiguration(name: string): Promise<any>;
getNetworkDevices(): Promise<any>;
getNetworkShares(path: string): Promise<FileSystemEntryInfo[]>;
getNewLiveTvTimerDefaults(options?: any): Promise<SeriesTimerInfoDto>;
getNextUpEpisodes(options?: any): Promise<BaseItemDtoQueryResult>;
getNotificationSummary(userId: string): Promise<NotificationsSummaryDto>;
getNotifications(userId: string, options?: any): Promise<NotificationResultDto>;
getPackageInfo(name: string, guid: string): Promise<PackageInfo>;
getPackageReviews(packageId: string, minRating?: string, maxRating?: string, limit?: string): Promise<any>;
getParentalRatings(): Promise<ParentalRating[]>;
getParentPath(path: string): Promise<string>;
getPeople(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getPerson(name: string, userId?: string): Promise<BaseItemDto>;
getPhysicalPaths(): Promise<string[]>;
getPlaybackInfo(itemId: string, options: any, deviceProfile: any): Promise<PlaybackInfoResponse>;
getPluginConfiguration(id: string): Promise<any>;
getPublicSystemInfo(): Promise<PublicSystemInfo>;
getPublicUsers(): Promise<UserDto[]>;
getQuickConnect(verb: string): Promise<void|boolean|number|QuickConnectResult|QuickConnectState>;
getReadySyncItems(deviceId: string): Promise<any>;
getRecordingFolders(userId: string): Promise<BaseItemDtoQueryResult>;
getRegistrationInfo(feature: string): Promise<any>;
getRemoteImageProviders(options: any): Promise<ImageProviderInfo[]>;
getResumableItems(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getRootFolder(userId: string): Promise<BaseItemDto>;
getSavedEndpointInfo(): EndPointInfo;
getScaledImageUrl(itemId: string, options?: any): string;
getScheduledTask(id: string): Promise<TaskInfo>;
getScheduledTasks(options?: any): Promise<TaskInfo[]>;
getSearchHints(options?: any): Promise<SearchHintResult>;
getSeasons(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getServerConfiguration(): Promise<ServerConfiguration>;
getServerTime(): Promise<UtcTimeResponse>;
getSessions(options?: any): Promise<SessionInfo[]>;
getSimilarItems(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getSpecialFeatures(userId: string, itemId: string): Promise<BaseItemDto[]>;
getStudio(name: string, userId?: string): Promise<BaseItemDto>;
getStudios(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getSyncPlayGroups(): Promise<GroupInfoDto[]>;
getSyncStatus(itemId: string): Promise<any>;
getSystemInfo(): Promise<SystemInfo>;
getThemeMedia(userId?: string, itemId: string, inherit?: boolean): Promise<AllThemeMediaResult>;
getThumbImageUrl(item: BaseItemDto, options?: any): string;
getUpcomingEpisodes(options?: any): Promise<BaseItemDtoQueryResult>;
getUrl(name: string, params?: any, serverAddress?: string): string;
get(url: string): Promise<any>;
getUserImageUrl(userId: string, options?: any): string;
getUsers(options?: any): Promise<UserDto[]>;
getUser(userId: string): Promise<UserDto>;
getUserViews(options?: any, userId: string): Promise<BaseItemDtoQueryResult>;
getVirtualFolders(): Promise<VirtualFolderInfo[]>;
handleMessageReceived(msg: any): void;
installPlugin(name: string, guid: string, version?: string): Promise<void>;
isLoggedIn(): boolean;
isMessageChannelOpen(): boolean;
isMinServerVersion(version: string): boolean;
isWebSocketOpen(): boolean;
isWebSocketOpenOrConnecting(): boolean;
isWebSocketSupported(): boolean;
joinSyncPlayGroup(options?: any): Promise<void>;
leaveSyncPlayGroup(): Promise<void>;
logout(): Promise<void>;
markNotificationsRead(userId: string, idList: string[], isRead: boolean): Promise<void>;
markPlayed(userId: string, itemId: string, date: Date): Promise<UserItemDataDto>;
markUnplayed(userId: string, itemId: string, date: Date): Promise<UserItemDataDto>;
openWebSocket(): void;
quickConnect(secret: string): Promise<AuthenticationResult>;
refreshItem(itemId: string, options?: any): Promise<void>;
removeMediaPath(virtualFolderName: string, mediaPath: string, refreshLibrary?: boolean): Promise<void>;
removeVirtualFolder(name: string, refreshLibrary?: boolean): Promise<void>;
renameVirtualFolder(name: string, newName: string, refreshLibrary?: boolean): Promise<void>;
reportCapabilities(capabilities: ClientCapabilities): Promise<void>;
reportOfflineActions(actions: any): Promise<any>;
reportPlaybackProgress(options: PlaybackProgressInfo): Promise<void>;
reportPlaybackStart(options: PlaybackStartInfo): Promise<void>;
reportPlaybackStopped(options: PlaybackStopInfo): Promise<void>;
reportSyncJobItemTransferred(syncJobItemId: string): Promise<any>;
requestSyncPlayBuffering(options?: BufferRequestDto): Promise<void>;
requestSyncPlayMovePlaylistItem(options?: MovePlaylistItemRequestDto): Promise<void>;
requestSyncPlayNextItem(options?: NextItemRequestDto): Promise<void>;
requestSyncPlayPause(): Promise<void>;
requestSyncPlayPreviousItem(options?: PreviousItemRequestDto): Promise<void>;
requestSyncPlayQueue(options?: QueueRequestDto): Promise<void>;
requestSyncPlayReady(options?: ReadyRequestDto): Promise<void>;
requestSyncPlayRemoveFromPlaylist(options?: RemoveFromPlaylistRequestDto): Promise<void>;
requestSyncPlaySeek(options?: SeekRequestDto): Promise<void>;
requestSyncPlaySetIgnoreWait(options?: IgnoreWaitRequestDto): Promise<void>;
requestSyncPlaySetNewQueue(options?: NewGroupRequestDto): Promise<void>;
requestSyncPlaySetPlaylistItem(options?: SetPlaylistItemRequestDto): Promise<void>;
requestSyncPlaySetRepeatMode(options?: SetRepeatModeRequestDto): Promise<void>;
requestSyncPlaySetShuffleMode(options?: SetShuffleModeRequestDto): Promise<void>;
requestSyncPlayUnpause(): Promise<void>;
resetEasyPassword(userId: string): Promise<void>;
resetLiveTvTuner(id: string): Promise<void>;
resetUserPassword(userId: string): Promise<void>;
restartServer(): Promise<void>;
sendCommand(sessionId: string, command: any): Promise<void>;
sendMessageCommand(sessionId: string, options: GeneralCommand): Promise<void>;
sendMessage(name: string, data: any): void;
sendPlayCommand(sessionId: string, options: PlayCommand): Promise<void>;
sendPlayStateCommand(sessionId: string, command: PlaystateCommand, options?: any): Promise<void>;
sendSyncPlayPing(options?: PingRequestDto): Promise<void>;
sendWebSocketMessage(name: string, data: any): void;
serverAddress(val?: string): string;
serverId(): string;
serverVersion(): string;
setAuthenticationInfo(accessKey?: string, userId?: string): void;
setRequestHeaders(headers: any): void;
setSystemInfo(info: SystemInfo): void;
shutdownServer(): Promise<void>;
startScheduledTask(id: string): Promise<void>;
stopActiveEncodings(playSessionId: string): Promise<void>;
stopScheduledTask(id: string): Promise<void>;
syncData(data: any): Promise<any>;
uninstallPluginByVersion(id: string, version: string): Promise<void>;
uninstallPlugin(id: string): Promise<void>;
updateDisplayPreferences(id: string, obj: DisplayPreferencesDto, userId: string, app: string): Promise<void>;
updateEasyPassword(userId: string, newPassword: string): Promise<void>;
updateFavoriteStatus(userId: string, itemId: string, isFavorite: boolean): Promise<UserItemDataDto>;
updateItemImageIndex(itemId: string, imageType: ImageType, imageIndex: number, newIndex: number): Promise<any>;
updateItem(item: BaseItemDto): Promise<void>;
updateLiveTvSeriesTimer(item: SeriesTimerInfoDto): Promise<void>;
updateLiveTvTimer(item: TimerInfoDto): Promise<void>;
updateMediaPath(virtualFolderName: string, pathInfo: any): Promise<void>;
updateNamedConfiguration(name: string, configuration: any): Promise<void>;
updatePluginConfiguration(id: string, configuration: any): Promise<void>;
updatePluginSecurityInfo(info: PluginSecurityInfo): Promise<void>;
updateScheduledTaskTriggers(id: string, triggers: TaskTriggerInfo[]): Promise<void>;
updateServerConfiguration(configuration: ServerConfiguration): Promise<void>;
updateServerInfo(server: any, serverUrl: string): void;
updateUserConfiguration(userId: string, configuration: UserConfiguration): Promise<void>;
updateUserItemRating(userId: string, itemId: string, likes: boolean): Promise<UserItemDataDto>;
updateUserPassword(userId: string, currentPassword: string, newPassword: string): Promise<void>;
updateUserPolicy(userId: string, policy: UserPolicy): Promise<void>;
updateUser(user: UserDto): Promise<void>;
updateVirtualFolderOptions(id: string, libraryOptions?: any): Promise<void>;
uploadItemImage(itemId: string, imageType: ImageType, file: File): Promise<void>;
uploadItemSubtitle(itemId: string, language: string, isForced: boolean, file: File): Promise<void>;
uploadUserImage(userId: string, imageType: ImageType, file: File): Promise<void>;
}
class AppStore {
constructor();
getItem(name: string): string|null;
removeItem(name: string): void;
setItem(name: string, value: string): void;
}
interface ConnectResponse {
ApiClient: ApiClient
Servers: any[]
State: ConnectionState
}
class ConnectionManager {
constructor(credentialProvider: Credentials, appName: string, appVersion: string, deviceName: string, deviceId: string, capabilities: ClientCapabilities);
addApiClient(apiClient: ApiClient): void;
clearData(): void;
connect(options?: any): Promise<ConnectResponse>;
connectToAddress(address: string, options?: any): Promise<any>;
connectToServer(server: any, options?: any): Promise<any>;
connectToServers(servers: any[], options?: any): Promise<any>;
deleteServer(serverId: string): Promise<void>;
getApiClient(item: BaseItemDto|string): ApiClient;
getApiClients(): ApiClient[];
getAvailableServers(): any[];
getOrCreateApiClient(serverId: string): ApiClient;
getSavedServers(): any[];
handleMessageReceived(msg: any): void;
logout(): Promise<void>;
minServerVersion(val?: string): string;
user(apiClient: ApiClient): Promise<any>;
}
class Credentials {
constructor(key?: string);
addOrUpdateServer(list: any[], server: any): any;
clear(): void;
credentials(data?: any): any;
}
interface Event {
type: string;
}
const Events: {
off(obj: any, eventName: string, fn: (e: Event, ...args: any[]) => void): void;
on(obj: any, eventName: string, fn: (e: Event, ...args: any[]) => void): void;
trigger(obj: any, eventName: string, ...args: any[]): void;
};
}
/* eslint-enable @typescript-eslint/no-explicit-any */

View File

@@ -1,94 +0,0 @@
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import { type Theme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import React, { FC, useCallback, useEffect, useState } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import AppBody from 'components/AppBody';
import AppToolbar from 'components/toolbar/AppToolbar';
import ElevationScroll from 'components/ElevationScroll';
import { DRAWER_WIDTH } from 'components/ResponsiveDrawer';
import { useApi } from 'hooks/useApi';
import AppTabs from './components/AppTabs';
import AppDrawer from './components/drawer/AppDrawer';
import { DASHBOARD_APP_PATHS } from './routes/routes';
import './AppOverrides.scss';
const DRAWERLESS_PATHS = [ DASHBOARD_APP_PATHS.MetadataManager ];
export const Component: FC = () => {
const [ isDrawerActive, setIsDrawerActive ] = useState(false);
const location = useLocation();
const { user } = useApi();
const isMediumScreen = useMediaQuery((t: Theme) => t.breakpoints.up('md'));
const isDrawerAvailable = Boolean(user)
&& !DRAWERLESS_PATHS.some(path => location.pathname.startsWith(`/${path}`));
const isDrawerOpen = isDrawerActive && isDrawerAvailable;
const onToggleDrawer = useCallback(() => {
setIsDrawerActive(!isDrawerActive);
}, [ isDrawerActive, setIsDrawerActive ]);
// Update body class
useEffect(() => {
document.body.classList.add('dashboardDocument');
return () => {
document.body.classList.remove('dashboardDocument');
};
}, []);
return (
<Box sx={{ display: 'flex' }}>
<ElevationScroll elevate={false}>
<AppBar
position='fixed'
sx={{
width: {
xs: '100%',
md: isDrawerAvailable ? `calc(100% - ${DRAWER_WIDTH}px)` : '100%'
},
ml: {
xs: 0,
md: isDrawerAvailable ? DRAWER_WIDTH : 0
}
}}
>
<AppToolbar
isDrawerAvailable={!isMediumScreen && isDrawerAvailable}
isDrawerOpen={isDrawerOpen}
onDrawerButtonClick={onToggleDrawer}
>
<AppTabs isDrawerOpen={isDrawerOpen} />
</AppToolbar>
</AppBar>
</ElevationScroll>
{
isDrawerAvailable && (
<AppDrawer
open={isDrawerOpen}
onClose={onToggleDrawer}
onOpen={onToggleDrawer}
/>
)
}
<Box
component='main'
sx={{
width: '100%',
flexGrow: 1
}}
>
<AppBody>
<Outlet />
</AppBody>
</Box>
</Box>
);
};

View File

@@ -1,34 +0,0 @@
// Default MUI breakpoints
// https://mui.com/material-ui/customization/breakpoints/#default-breakpoints
$mui-bp-sm: 600px;
$mui-bp-md: 900px;
$mui-bp-lg: 1200px;
$mui-bp-xl: 1536px;
$drawer-width: 240px;
// Fix dashboard pages layout to work with drawer
.dashboardDocument {
.mainAnimatedPage {
@media all and (min-width: $mui-bp-md) {
left: $drawer-width;
}
}
.skinBody {
position: unset !important;
}
// Fix the padding of dashboard pages
.content-primary {
padding-top: 3.25rem;
}
// Tabbed pages
.withTabs .content-primary {
padding-top: 6.5rem;
@media all and (min-width: $mui-bp-lg) {
padding-top: 3.25rem;
}
}
}

View File

@@ -1,96 +0,0 @@
import { Theme } from '@mui/material/styles';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import useMediaQuery from '@mui/material/useMediaQuery';
import debounce from 'lodash-es/debounce';
import isEqual from 'lodash-es/isEqual';
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { EventType } from 'types/eventType';
import Events, { type Event } from 'utils/events';
interface AppTabsParams {
isDrawerOpen: boolean
}
interface TabDefinition {
href: string
name: string
}
const handleResize = debounce(() => window.dispatchEvent(new Event('resize')), 100);
const AppTabs: FC<AppTabsParams> = ({
isDrawerOpen
}) => {
const documentRef = useRef<Document>(document);
const [ activeIndex, setActiveIndex ] = useState(0);
const [ tabs, setTabs ] = useState<TabDefinition[]>();
const isBigScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('sm'));
const onTabsUpdate = useCallback((
_e: Event,
_newView?: string,
newIndex: number | undefined = 0,
newTabs?: TabDefinition[]
) => {
setActiveIndex(newIndex);
if (!isEqual(tabs, newTabs)) {
setTabs(newTabs);
}
}, [ tabs ]);
useEffect(() => {
const doc = documentRef.current;
if (doc) Events.on(doc, EventType.SET_TABS, onTabsUpdate);
return () => {
if (doc) Events.off(doc, EventType.SET_TABS, onTabsUpdate);
};
}, [ onTabsUpdate ]);
// HACK: Force resizing to workaround upstream bug with tab resizing
// https://github.com/mui/material-ui/issues/24011
useEffect(() => {
handleResize();
}, [ isDrawerOpen ]);
if (!tabs?.length) return null;
return (
<Tabs
value={activeIndex}
sx={{
width: '100%',
flexShrink: {
xs: 0,
lg: 'unset'
},
order: {
xs: 100,
lg: 'unset'
}
}}
variant={isBigScreen ? 'standard' : 'scrollable'}
centered={isBigScreen}
>
{
tabs.map(({ href, name }, index) => (
<Tab
key={`tab-${name}`}
label={name}
data-tab-index={`${index}`}
component={Link}
to={href}
/>
))
}
</Tabs>
);
};
export default AppTabs;

View File

@@ -1,34 +0,0 @@
import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
import Chip from '@mui/material/Chip';
import React from 'react';
import globalize from 'lib/globalize';
const LogLevelChip = ({ level }: { level: LogLevel }) => {
let color: 'info' | 'warning' | 'error' | undefined;
switch (level) {
case LogLevel.Information:
color = 'info';
break;
case LogLevel.Warning:
color = 'warning';
break;
case LogLevel.Error:
case LogLevel.Critical:
color = 'error';
break;
}
const levelText = globalize.translate(`LogLevel.${level}`);
return (
<Chip
size='small'
color={color}
label={levelText}
title={levelText}
/>
);
};
export default LogLevelChip;

View File

@@ -1,64 +0,0 @@
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';
const OverviewCell: FC<ActivityLogEntry> = ({ Overview, ShortOverview }) => {
const displayValue = ShortOverview ?? Overview;
const [ open, setOpen ] = useState(false);
const onTooltipClose = useCallback(() => {
setOpen(false);
}, []);
const onTooltipOpen = useCallback(() => {
setOpen(true);
}, []);
if (!displayValue) return null;
return (
<Box
sx={{
display: 'flex',
width: '100%',
alignItems: 'center'
}}
>
<Box
sx={{
flexGrow: 1,
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
component='div'
title={displayValue}
>
{displayValue}
</Box>
{ShortOverview && Overview && (
<ClickAwayListener onClickAway={onTooltipClose}>
<Tooltip
title={Overview}
placement='top'
arrow
onClose={onTooltipClose}
open={open}
disableFocusListener
disableHoverListener
disableTouchListener
>
<IconButton onClick={onTooltipOpen}>
<Info />
</IconButton>
</Tooltip>
</ClickAwayListener>
)}
</Box>
);
};
export default OverviewCell;

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,37 +0,0 @@
import ListItem from '@mui/material/ListItem';
import List from '@mui/material/List';
import React, { FC } from 'react';
import DrawerHeaderLink from 'apps/experimental/components/drawers/DrawerHeaderLink';
import ResponsiveDrawer, { ResponsiveDrawerProps } from 'components/ResponsiveDrawer';
import ServerDrawerSection from './sections/ServerDrawerSection';
import DevicesDrawerSection from './sections/DevicesDrawerSection';
import LiveTvDrawerSection from './sections/LiveTvDrawerSection';
import AdvancedDrawerSection from './sections/AdvancedDrawerSection';
import PluginDrawerSection from './sections/PluginDrawerSection';
const AppDrawer: FC<ResponsiveDrawerProps> = ({
open = false,
onClose,
onOpen
}) => (
<ResponsiveDrawer
open={open}
onClose={onClose}
onOpen={onOpen}
>
<List disablePadding>
<ListItem disablePadding>
<DrawerHeaderLink />
</ListItem>
</List>
<ServerDrawerSection />
<DevicesDrawerSection />
<LiveTvDrawerSection />
<PluginDrawerSection />
<AdvancedDrawerSection />
</ResponsiveDrawer>
);
export default AppDrawer;

View File

@@ -1,61 +0,0 @@
import Article from '@mui/icons-material/Article';
import Lan from '@mui/icons-material/Lan';
import Schedule from '@mui/icons-material/Schedule';
import VpnKey from '@mui/icons-material/VpnKey';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import ListSubheader from '@mui/material/ListSubheader';
import React from 'react';
import ListItemLink from 'components/ListItemLink';
import globalize from 'lib/globalize';
const AdvancedDrawerSection = () => {
return (
<List
aria-labelledby='advanced-subheader'
subheader={
<ListSubheader component='div' id='advanced-subheader'>
{globalize.translate('TabAdvanced')}
</ListSubheader>
}
>
<ListItem disablePadding>
<ListItemLink to='/dashboard/networking'>
<ListItemIcon>
<Lan />
</ListItemIcon>
<ListItemText primary={globalize.translate('TabNetworking')} />
</ListItemLink>
</ListItem>
<ListItem disablePadding>
<ListItemLink to='/dashboard/keys'>
<ListItemIcon>
<VpnKey />
</ListItemIcon>
<ListItemText primary={globalize.translate('HeaderApiKeys')} />
</ListItemLink>
</ListItem>
<ListItem disablePadding>
<ListItemLink to='/dashboard/logs'>
<ListItemIcon>
<Article />
</ListItemIcon>
<ListItemText primary={globalize.translate('TabLogs')} />
</ListItemLink>
</ListItem>
<ListItem disablePadding>
<ListItemLink to='/dashboard/tasks'>
<ListItemIcon>
<Schedule />
</ListItemIcon>
<ListItemText primary={globalize.translate('TabScheduledTasks')} />
</ListItemLink>
</ListItem>
</List>
);
};
export default AdvancedDrawerSection;

View File

@@ -1,42 +0,0 @@
import { Devices, Analytics } from '@mui/icons-material';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import ListSubheader from '@mui/material/ListSubheader';
import React from 'react';
import ListItemLink from 'components/ListItemLink';
import globalize from 'lib/globalize';
const DevicesDrawerSection = () => {
return (
<List
aria-labelledby='devices-subheader'
subheader={
<ListSubheader component='div' id='devices-subheader'>
{globalize.translate('HeaderDevices')}
</ListSubheader>
}
>
<ListItem disablePadding>
<ListItemLink to='/dashboard/devices'>
<ListItemIcon>
<Devices />
</ListItemIcon>
<ListItemText primary={globalize.translate('HeaderDevices')} />
</ListItemLink>
</ListItem>
<ListItem disablePadding>
<ListItemLink to='/dashboard/activity'>
<ListItemIcon>
<Analytics />
</ListItemIcon>
<ListItemText primary={globalize.translate('HeaderActivity')} />
</ListItemLink>
</ListItem>
</List>
);
};
export default DevicesDrawerSection;

View File

@@ -1,42 +0,0 @@
import { Dvr, LiveTv } from '@mui/icons-material';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import ListSubheader from '@mui/material/ListSubheader';
import React from 'react';
import ListItemLink from 'components/ListItemLink';
import globalize from 'lib/globalize';
const LiveTvDrawerSection = () => {
return (
<List
aria-labelledby='livetv-subheader'
subheader={
<ListSubheader component='div' id='livetv-subheader'>
{globalize.translate('LiveTV')}
</ListSubheader>
}
>
<ListItem disablePadding>
<ListItemLink to='/dashboard/livetv'>
<ListItemIcon>
<LiveTv />
</ListItemIcon>
<ListItemText primary={globalize.translate('LiveTV')} />
</ListItemLink>
</ListItem>
<ListItem disablePadding>
<ListItemLink to='/dashboard/recordings'>
<ListItemIcon>
<Dvr />
</ListItemIcon>
<ListItemText primary={globalize.translate('HeaderDVR')} />
</ListItemLink>
</ListItem>
</List>
);
};
export default LiveTvDrawerSection;

View File

@@ -1,71 +0,0 @@
import Extension from '@mui/icons-material/Extension';
import Folder from '@mui/icons-material/Folder';
import Public from '@mui/icons-material/Public';
import List from '@mui/material/List';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import ListSubheader from '@mui/material/ListSubheader';
import React, { useEffect } from 'react';
import ListItemLink from 'components/ListItemLink';
import globalize from 'lib/globalize';
import Dashboard from 'utils/dashboard';
import { useConfigurationPages } from 'apps/dashboard/features/plugins/api/useConfigurationPages';
const PluginDrawerSection = () => {
const {
data: pagesInfo,
error
} = useConfigurationPages({ enableInMainMenu: true });
useEffect(() => {
if (error) console.error('[PluginDrawerSection] unable to fetch plugin config pages', error);
}, [ error ]);
return (
<List
aria-labelledby='plugins-subheader'
subheader={
<ListSubheader component='div' id='plugins-subheader'>
{globalize.translate('TabPlugins')}
</ListSubheader>
}
>
<ListItemLink
to='/dashboard/plugins'
includePaths={[ '/configurationpage' ]}
excludePaths={pagesInfo?.map(p => `/${Dashboard.getPluginUrl(p.Name)}`)}
>
<ListItemIcon>
<Extension />
</ListItemIcon>
<ListItemText primary={globalize.translate('TabMyPlugins')} />
</ListItemLink>
<ListItemLink
to='/dashboard/plugins/catalog'
includePaths={[ '/dashboard/plugins/repositories' ]}
>
<ListItemIcon>
<Public />
</ListItemIcon>
<ListItemText primary={globalize.translate('TabCatalog')} />
</ListItemLink>
{pagesInfo?.map(pageInfo => (
<ListItemLink
key={pageInfo.PluginId}
to={`/${Dashboard.getPluginUrl(pageInfo.Name)}`}
>
<ListItemIcon>
{/* TODO: Support different icons? */}
<Folder />
</ListItemIcon>
<ListItemText primary={pageInfo.DisplayName} />
</ListItemLink>
))}
</List>
);
};
export default PluginDrawerSection;

View File

@@ -1,121 +0,0 @@
import { Dashboard, ExpandLess, ExpandMore, LibraryAdd, People, PlayCircle, Settings } from '@mui/icons-material';
import Collapse from '@mui/material/Collapse';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import ListSubheader from '@mui/material/ListSubheader';
import React from 'react';
import { useLocation } from 'react-router-dom';
import ListItemLink from 'components/ListItemLink';
import globalize from 'lib/globalize';
const LIBRARY_PATHS = [
'/dashboard/libraries',
'/dashboard/libraries/display',
'/dashboard/libraries/metadata',
'/dashboard/libraries/nfo'
];
const PLAYBACK_PATHS = [
'/dashboard/playback/transcoding',
'/dashboard/playback/resume',
'/dashboard/playback/streaming',
'/dashboard/playback/trickplay'
];
const ServerDrawerSection = () => {
const location = useLocation();
const isLibrarySectionOpen = LIBRARY_PATHS.includes(location.pathname);
const isPlaybackSectionOpen = PLAYBACK_PATHS.includes(location.pathname);
return (
<List
aria-labelledby='server-subheader'
subheader={
<ListSubheader component='div' id='server-subheader'>
{globalize.translate('TabServer')}
</ListSubheader>
}
>
<ListItem disablePadding>
<ListItemLink to='/dashboard'>
<ListItemIcon>
<Dashboard />
</ListItemIcon>
<ListItemText primary={globalize.translate('TabDashboard')} />
</ListItemLink>
</ListItem>
<ListItem disablePadding>
<ListItemLink to='/dashboard/settings'>
<ListItemIcon>
<Settings />
</ListItemIcon>
<ListItemText primary={globalize.translate('General')} />
</ListItemLink>
</ListItem>
<ListItem disablePadding>
<ListItemLink to='/dashboard/users'>
<ListItemIcon>
<People />
</ListItemIcon>
<ListItemText primary={globalize.translate('HeaderUsers')} />
</ListItemLink>
</ListItem>
<ListItem disablePadding>
<ListItemLink to='/dashboard/libraries' selected={false}>
<ListItemIcon>
<LibraryAdd />
</ListItemIcon>
<ListItemText primary={globalize.translate('HeaderLibraries')} />
{isLibrarySectionOpen ? <ExpandLess /> : <ExpandMore />}
</ListItemLink>
</ListItem>
<Collapse in={isLibrarySectionOpen} timeout='auto' unmountOnExit>
<List component='div' disablePadding>
<ListItemLink to='/dashboard/libraries' sx={{ pl: 4 }}>
<ListItemText inset primary={globalize.translate('HeaderLibraries')} />
</ListItemLink>
<ListItemLink to='/dashboard/libraries/display' sx={{ pl: 4 }}>
<ListItemText inset primary={globalize.translate('Display')} />
</ListItemLink>
<ListItemLink to='/dashboard/libraries/metadata' sx={{ pl: 4 }}>
<ListItemText inset primary={globalize.translate('Metadata')} />
</ListItemLink>
<ListItemLink to='/dashboard/libraries/nfo' sx={{ pl: 4 }}>
<ListItemText inset primary={globalize.translate('TabNfoSettings')} />
</ListItemLink>
</List>
</Collapse>
<ListItem disablePadding>
<ListItemLink to='/dashboard/playback/transcoding' selected={false}>
<ListItemIcon>
<PlayCircle />
</ListItemIcon>
<ListItemText primary={globalize.translate('TitlePlayback')} />
{isPlaybackSectionOpen ? <ExpandLess /> : <ExpandMore />}
</ListItemLink>
</ListItem>
<Collapse in={isPlaybackSectionOpen} timeout='auto' unmountOnExit>
<List component='div' disablePadding>
<ListItemLink to='/dashboard/playback/transcoding' sx={{ pl: 4 }}>
<ListItemText inset primary={globalize.translate('Transcoding')} />
</ListItemLink>
<ListItemLink to='/dashboard/playback/resume' sx={{ pl: 4 }}>
<ListItemText inset primary={globalize.translate('ButtonResume')} />
</ListItemLink>
<ListItemLink to='/dashboard/playback/streaming' sx={{ pl: 4 }}>
<ListItemText inset primary={globalize.translate('TabStreaming')} />
</ListItemLink>
<ListItemLink to='/dashboard/playback/trickplay' sx={{ pl: 4 }}>
<ListItemText inset primary={globalize.translate('Trickplay')} />
</ListItemLink>
</List>
</Collapse>
</List>
);
};
export default ServerDrawerSection;

View File

@@ -1,21 +0,0 @@
import type { ConfigurationPageInfo } from '@jellyfin/sdk/lib/generated-client/models/configuration-page-info';
export const findBestConfigurationPage = (
configurationPages: ConfigurationPageInfo[],
pluginId: string
) => {
// Find candidates matching the plugin id
const candidates = configurationPages.filter(c => c.PluginId === pluginId);
// If none are found, return undefined
if (candidates.length === 0) return;
// If only one is found, return it
if (candidates.length === 1) return candidates[0];
// Prefer the first candidate with the EnableInMainMenu flag for consistency
const menuCandidate = candidates.find(c => !!c.EnableInMainMenu);
if (menuCandidate) return menuCandidate;
// Fallback to the first match
return candidates[0];
};

View File

@@ -1,25 +0,0 @@
import type { PluginInfo } from '@jellyfin/sdk/lib/generated-client/models/plugin-info';
import { PluginStatus } from '@jellyfin/sdk/lib/generated-client/models/plugin-status';
/**
* HACK: The Plugins API is returning garbage data in some cases,
* so we need to try to find the "best" match if multiple exist.
*/
export const findBestPluginInfo = (
pluginId: string,
plugins?: PluginInfo[]
) => {
if (!plugins) return;
// Find all plugin entries with a matching ID
const matches = plugins.filter(p => p.Id === pluginId);
// Get the first match (or undefined if none)
const firstMatch = matches?.[0];
if (matches.length > 1) {
return matches.find(p => p.Status === PluginStatus.Disabled) // Disabled entries take priority
|| matches.find(p => p.Status === PluginStatus.Restart) // Then entries specifying restart is needed
|| firstMatch; // Fallback to the first match
}
return firstMatch;
};

View File

@@ -1,5 +0,0 @@
export enum QueryKey {
ConfigurationPages = 'ConfigurationPages',
PackageInfo = 'PackageInfo',
Plugins = 'Plugins'
}

View File

@@ -1,40 +0,0 @@
import type { Api } from '@jellyfin/sdk';
import type { DashboardApiGetConfigurationPagesRequest } from '@jellyfin/sdk/lib/generated-client/api/dashboard-api';
import { getDashboardApi } from '@jellyfin/sdk/lib/utils/api/dashboard-api';
import { queryOptions, useQuery } from '@tanstack/react-query';
import type { AxiosRequestConfig } from 'axios';
import { useApi } from 'hooks/useApi';
import { QueryKey } from './queryKey';
const fetchConfigurationPages = async (
api?: Api,
params?: DashboardApiGetConfigurationPagesRequest,
options?: AxiosRequestConfig
) => {
if (!api) {
console.warn('[fetchConfigurationPages] No API instance available');
return [];
}
const response = await getDashboardApi(api)
.getConfigurationPages(params, options);
return response.data;
};
const getConfigurationPagesQuery = (
api?: Api,
params?: DashboardApiGetConfigurationPagesRequest
) => queryOptions({
queryKey: [ QueryKey.ConfigurationPages, params?.enableInMainMenu ],
queryFn: ({ signal }) => fetchConfigurationPages(api, params, { signal }),
enabled: !!api
});
export const useConfigurationPages = (
params?: DashboardApiGetConfigurationPagesRequest
) => {
const { api } = useApi();
return useQuery(getConfigurationPagesQuery(api, params));
};

View File

@@ -1,24 +0,0 @@
import type { PluginsApiDisablePluginRequest } from '@jellyfin/sdk/lib/generated-client/api/plugins-api';
import { getPluginsApi } from '@jellyfin/sdk/lib/utils/api/plugins-api';
import { useMutation } from '@tanstack/react-query';
import { useApi } from 'hooks/useApi';
import { queryClient } from 'utils/query/queryClient';
import { QueryKey } from './queryKey';
export const useDisablePlugin = () => {
const { api } = useApi();
return useMutation({
mutationFn: (params: PluginsApiDisablePluginRequest) => (
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
getPluginsApi(api!)
.disablePlugin(params)
),
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [ QueryKey.Plugins ]
});
}
});
};

View File

@@ -1,24 +0,0 @@
import type { PluginsApiEnablePluginRequest } from '@jellyfin/sdk/lib/generated-client/api/plugins-api';
import { getPluginsApi } from '@jellyfin/sdk/lib/utils/api/plugins-api';
import { useMutation } from '@tanstack/react-query';
import { useApi } from 'hooks/useApi';
import { queryClient } from 'utils/query/queryClient';
import { QueryKey } from './queryKey';
export const useEnablePlugin = () => {
const { api } = useApi();
return useMutation({
mutationFn: (params: PluginsApiEnablePluginRequest) => (
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
getPluginsApi(api!)
.enablePlugin(params)
),
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [ QueryKey.Plugins ]
});
}
});
};

View File

@@ -1,27 +0,0 @@
import type { PackageApiInstallPackageRequest } from '@jellyfin/sdk/lib/generated-client/api/package-api';
import { getPackageApi } from '@jellyfin/sdk/lib/utils/api/package-api';
import { useMutation } from '@tanstack/react-query';
import { useApi } from 'hooks/useApi';
import { queryClient } from 'utils/query/queryClient';
import { QueryKey } from './queryKey';
export const useInstallPackage = () => {
const { api } = useApi();
return useMutation({
mutationFn: (params: PackageApiInstallPackageRequest) => (
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
getPackageApi(api!)
.installPackage(params)
),
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [ QueryKey.ConfigurationPages ]
});
void queryClient.invalidateQueries({
queryKey: [ QueryKey.Plugins ]
});
}
});
};

View File

@@ -1,47 +0,0 @@
import { queryOptions, useQuery } from '@tanstack/react-query';
import type { Api } from '@jellyfin/sdk';
import type { PackageApiGetPackageInfoRequest } from '@jellyfin/sdk/lib/generated-client/api/package-api';
import { getPackageApi } from '@jellyfin/sdk/lib/utils/api/package-api';
import type { AxiosRequestConfig } from 'axios';
import { useApi } from 'hooks/useApi';
import { QueryKey } from './queryKey';
const fetchPackageInfo = async (
api?: Api,
params?: PackageApiGetPackageInfoRequest,
options?: AxiosRequestConfig
) => {
if (!api) {
console.warn('[fetchPackageInfo] No API instance available');
return;
}
if (!params) {
console.warn('[fetchPackageInfo] Missing request params');
return;
}
const response = await getPackageApi(api)
.getPackageInfo(params, options);
return response.data;
};
const getPackageInfoQuery = (
api?: Api,
params?: PackageApiGetPackageInfoRequest
) => queryOptions({
// Don't retry since requests for plugins not available in repos fail
retry: false,
queryKey: [ QueryKey.PackageInfo, params?.name, params?.assemblyGuid ],
queryFn: ({ signal }) => fetchPackageInfo(api, params, { signal }),
enabled: !!api && !!params?.name
});
export const usePackageInfo = (
params?: PackageApiGetPackageInfoRequest
) => {
const { api } = useApi();
return useQuery(getPackageInfoQuery(api, params));
};

View File

@@ -1,36 +0,0 @@
import type { Api } from '@jellyfin/sdk';
import { getPluginsApi } from '@jellyfin/sdk/lib/utils/api/plugins-api';
import { queryOptions, useQuery } from '@tanstack/react-query';
import type { AxiosRequestConfig } from 'axios';
import { useApi } from 'hooks/useApi';
import { QueryKey } from './queryKey';
const fetchPlugins = async (
api?: Api,
options?: AxiosRequestConfig
) => {
if (!api) {
console.warn('[fetchPlugins] No API instance available');
return [];
}
const response = await getPluginsApi(api)
.getPlugins(options);
return response.data;
};
const getPluginsQuery = (
api?: Api
) => queryOptions({
queryKey: [ QueryKey.Plugins ],
queryFn: ({ signal }) => fetchPlugins(api, { signal }),
enabled: !!api
});
export const usePlugins = () => {
const { api } = useApi();
return useQuery(getPluginsQuery(api));
};

View File

@@ -1,27 +0,0 @@
import type { PluginsApiUninstallPluginByVersionRequest } from '@jellyfin/sdk/lib/generated-client/api/plugins-api';
import { getPluginsApi } from '@jellyfin/sdk/lib/utils/api/plugins-api';
import { useMutation } from '@tanstack/react-query';
import { useApi } from 'hooks/useApi';
import { queryClient } from 'utils/query/queryClient';
import { QueryKey } from './queryKey';
export const useUninstallPlugin = () => {
const { api } = useApi();
return useMutation({
mutationFn: (params: PluginsApiUninstallPluginByVersionRequest) => (
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
getPluginsApi(api!)
.uninstallPluginByVersion(params)
),
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [ QueryKey.Plugins ]
});
void queryClient.invalidateQueries({
queryKey: [ QueryKey.ConfigurationPages ]
});
}
});
};

View File

@@ -1,94 +0,0 @@
import Link from '@mui/material/Link/Link';
import Paper, { type PaperProps } from '@mui/material/Paper/Paper';
import Skeleton from '@mui/material/Skeleton/Skeleton';
import Table from '@mui/material/Table/Table';
import TableBody from '@mui/material/TableBody/TableBody';
import TableCell from '@mui/material/TableCell/TableCell';
import TableContainer from '@mui/material/TableContainer/TableContainer';
import TableRow from '@mui/material/TableRow/TableRow';
import React, { FC } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import globalize from 'lib/globalize';
import type { PluginDetails } from '../types/PluginDetails';
interface PluginDetailsTableProps extends PaperProps {
isPluginLoading: boolean
isRepositoryLoading: boolean
pluginDetails?: PluginDetails
}
const PluginDetailsTable: FC<PluginDetailsTableProps> = ({
isPluginLoading,
isRepositoryLoading,
pluginDetails,
...paperProps
}) => (
<TableContainer component={Paper} {...paperProps}>
<Table>
<TableBody>
<TableRow>
<TableCell variant='head'>
{globalize.translate('LabelStatus')}
</TableCell>
<TableCell>
{
(isPluginLoading && <Skeleton />)
|| pluginDetails?.status
|| globalize.translate('LabelNotInstalled')
}
</TableCell>
</TableRow>
<TableRow>
<TableCell variant='head'>
{globalize.translate('LabelVersion')}
</TableCell>
<TableCell>
{
(isPluginLoading && <Skeleton />)
|| pluginDetails?.version?.version
}
</TableCell>
</TableRow>
<TableRow>
<TableCell variant='head'>
{globalize.translate('LabelDeveloper')}
</TableCell>
<TableCell>
{
(isRepositoryLoading && <Skeleton />)
|| pluginDetails?.owner
|| globalize.translate('Unknown')
}
</TableCell>
</TableRow>
<TableRow
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell variant='head'>
{globalize.translate('LabelRepository')}
</TableCell>
<TableCell>
{
(isRepositoryLoading && <Skeleton />)
|| (pluginDetails?.version?.repositoryUrl && (
<Link
component={RouterLink}
to={pluginDetails.version.repositoryUrl}
target='_blank'
rel='noopener noreferrer'
>
{pluginDetails.version.repositoryName}
</Link>
))
|| globalize.translate('Unknown')
}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
);
export default PluginDetailsTable;

View File

@@ -1,34 +0,0 @@
import Paper from '@mui/material/Paper/Paper';
import Skeleton from '@mui/material/Skeleton/Skeleton';
import React, { type FC } from 'react';
interface PluginImageProps {
isLoading: boolean
alt?: string
url?: string
}
const PluginImage: FC<PluginImageProps> = ({
isLoading,
alt,
url
}) => (
<Paper sx={{ width: '100%', aspectRatio: 16 / 9, overflow: 'hidden' }}>
{isLoading && (
<Skeleton
variant='rectangular'
width='100%'
height='100%'
/>
)}
{url && (
<img
src={url}
alt={alt}
width='100%'
/>
)}
</Paper>
);
export default PluginImage;

View File

@@ -1,67 +0,0 @@
import Download from '@mui/icons-material/Download';
import DownloadDone from '@mui/icons-material/DownloadDone';
import ExpandMore from '@mui/icons-material/ExpandMore';
import Accordion from '@mui/material/Accordion/Accordion';
import AccordionDetails from '@mui/material/AccordionDetails/AccordionDetails';
import AccordionSummary from '@mui/material/AccordionSummary/AccordionSummary';
import Button from '@mui/material/Button/Button';
import Stack from '@mui/material/Stack/Stack';
import React, { type FC } from 'react';
import MarkdownBox from 'components/MarkdownBox';
import { parseISO8601Date, toLocaleString } from 'scripts/datetime';
import globalize from 'lib/globalize';
import type { PluginDetails } from '../types/PluginDetails';
import { VersionInfo } from '@jellyfin/sdk/lib/generated-client';
interface PluginRevisionsProps {
pluginDetails?: PluginDetails,
onInstall: (version?: VersionInfo) => () => void
}
const PluginRevisions: FC<PluginRevisionsProps> = ({
pluginDetails,
onInstall
}) => (
pluginDetails?.versions?.map(version => (
<Accordion key={version.checksum}>
<AccordionSummary
expandIcon={<ExpandMore />}
>
{version.version}
{version.timestamp && (<>
&nbsp;&mdash;&nbsp;
{toLocaleString(parseISO8601Date(version.timestamp))}
</>)}
</AccordionSummary>
<AccordionDetails>
<Stack spacing={2}>
<MarkdownBox
fallback={globalize.translate('LabelNoChangelog')}
markdown={version.changelog}
/>
{pluginDetails.status && version.version === pluginDetails.version?.version ? (
<Button
disabled
startIcon={<DownloadDone />}
variant='outlined'
>
{globalize.translate('LabelInstalled')}
</Button>
) : (
<Button
startIcon={<Download />}
variant='outlined'
onClick={onInstall(version)}
>
{globalize.translate('HeaderInstall')}
</Button>
)}
</Stack>
</AccordionDetails>
</Accordion>
))
);
export default PluginRevisions;

View File

@@ -1,15 +0,0 @@
import type { ConfigurationPageInfo, PluginStatus, VersionInfo } from '@jellyfin/sdk/lib/generated-client';
export interface PluginDetails {
canUninstall: boolean
description?: string
id: string
imageUrl?: string
isEnabled: boolean
name?: string
owner?: string
configurationPage?: ConfigurationPageInfo
status?: PluginStatus
version?: VersionInfo
versions: VersionInfo[]
}

View File

@@ -1,13 +0,0 @@
import { AsyncRouteType, type AsyncRoute } from 'components/router/AsyncRoute';
export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [
{ path: 'activity', type: AsyncRouteType.Dashboard },
{ path: 'playback/trickplay', type: AsyncRouteType.Dashboard },
{ path: 'plugins/:pluginId', page: 'plugins/plugin', type: AsyncRouteType.Dashboard },
{ path: 'users', type: AsyncRouteType.Dashboard },
{ path: 'users/access', type: AsyncRouteType.Dashboard },
{ path: 'users/add', type: AsyncRouteType.Dashboard },
{ path: 'users/parentalcontrol', type: AsyncRouteType.Dashboard },
{ path: 'users/password', type: AsyncRouteType.Dashboard },
{ path: 'users/profile', type: AsyncRouteType.Dashboard }
];

View File

@@ -1,143 +0,0 @@
import type { LegacyRoute } from 'components/router/LegacyRoute';
export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [
{
path: '/dashboard',
pageProps: {
controller: 'dashboard/dashboard',
view: 'dashboard/dashboard.html'
}
}, {
path: 'settings',
pageProps: {
controller: 'dashboard/general',
view: 'dashboard/general.html'
}
}, {
path: 'networking',
pageProps: {
controller: 'dashboard/networking',
view: 'dashboard/networking.html'
}
}, {
path: 'devices',
pageProps: {
controller: 'dashboard/devices/devices',
view: 'dashboard/devices/devices.html'
}
}, {
path: 'devices/edit',
pageProps: {
controller: 'dashboard/devices/device',
view: 'dashboard/devices/device.html'
}
}, {
path: 'libraries',
pageProps: {
controller: 'dashboard/library',
view: 'dashboard/library.html'
}
}, {
path: 'libraries/display',
pageProps: {
controller: 'dashboard/librarydisplay',
view: 'dashboard/librarydisplay.html'
}
}, {
path: 'playback/transcoding',
pageProps: {
controller: 'dashboard/encodingsettings',
view: 'dashboard/encodingsettings.html'
}
}, {
path: 'logs',
pageProps: {
controller: 'dashboard/logs',
view: 'dashboard/logs.html'
}
}, {
path: 'libraries/metadata',
pageProps: {
controller: 'dashboard/metadataImages',
view: 'dashboard/metadataimages.html'
}
}, {
path: 'libraries/nfo',
pageProps: {
controller: 'dashboard/metadatanfo',
view: 'dashboard/metadatanfo.html'
}
}, {
path: 'playback/resume',
pageProps: {
controller: 'dashboard/playback',
view: 'dashboard/playback.html'
}
}, {
path: 'plugins/catalog',
pageProps: {
controller: 'dashboard/plugins/available/index',
view: 'dashboard/plugins/available/index.html'
}
}, {
path: 'plugins/repositories',
pageProps: {
controller: 'dashboard/plugins/repositories/index',
view: 'dashboard/plugins/repositories/index.html'
}
}, {
path: 'livetv/guide',
pageProps: {
controller: 'livetvguideprovider',
view: 'livetvguideprovider.html'
}
}, {
path: 'recordings',
pageProps: {
controller: 'livetvsettings',
view: 'livetvsettings.html'
}
}, {
path: 'livetv',
pageProps: {
controller: 'livetvstatus',
view: 'livetvstatus.html'
}
}, {
path: 'livetv/tuner',
pageProps: {
controller: 'livetvtuner',
view: 'livetvtuner.html'
}
}, {
path: 'plugins',
pageProps: {
controller: 'dashboard/plugins/installed/index',
view: 'dashboard/plugins/installed/index.html'
}
}, {
path: 'tasks/edit',
pageProps: {
controller: 'dashboard/scheduledtasks/scheduledtask',
view: 'dashboard/scheduledtasks/scheduledtask.html'
}
}, {
path: 'tasks',
pageProps: {
controller: 'dashboard/scheduledtasks/scheduledtasks',
view: 'dashboard/scheduledtasks/scheduledtasks.html'
}
}, {
path: 'keys',
pageProps: {
controller: 'dashboard/apikeys',
view: 'dashboard/apikeys.html'
}
}, {
path: 'playback/streaming',
pageProps: {
view: 'dashboard/streaming.html',
controller: 'dashboard/streaming'
}
}
];

View File

@@ -1,35 +0,0 @@
import type { Redirect } from 'components/router/Redirect';
export const REDIRECTS: Redirect[] = [
{ from: 'apikeys.html', to: '/dashboard/keys' },
{ from: 'availableplugins.html', to: '/dashboard/plugins/catalog' },
{ from: 'dashboard.html', to: '/dashboard' },
{ from: 'dashboardgeneral.html', to: '/dashboard/settings' },
{ from: 'device.html', to: '/dashboard/devices/edit' },
{ from: 'devices.html', to: '/dashboard/devices' },
{ from: 'edititemmetadata.html', to: '/metadata' },
{ from: 'encodingsettings.html', to: '/dashboard/playback/transcoding' },
{ from: 'installedplugins.html', to: '/dashboard/plugins' },
{ from: 'library.html', to: '/dashboard/libraries' },
{ from: 'librarydisplay.html', to: '/dashboard/libraries/display' },
{ from: 'livetvguideprovider.html', to: '/dashboard/livetv/guide' },
{ from: 'livetvsettings.html', to: '/dashboard/recordings' },
{ from: 'livetvstatus.html', to: '/dashboard/livetv' },
{ from: 'livetvtuner.html', to: '/dashboard/livetv/tuner' },
{ from: 'log.html', to: '/dashboard/logs' },
{ from: 'metadataimages.html', to: '/dashboard/libraries/metadata' },
{ from: 'metadatanfo.html', to: '/dashboard/libraries/nfo' },
{ from: 'networking.html', to: '/dashboard/networking' },
{ from: 'playbackconfiguration.html', to: '/dashboard/playback/resume' },
{ from: 'repositories.html', to: '/dashboard/plugins/repositories' },
{ from: 'scheduledtask.html', to: '/dashboard/tasks/edit' },
{ from: 'scheduledtasks.html', to: '/dashboard/tasks' },
{ from: 'serveractivity.html', to: '/dashboard/activity' },
{ from: 'streamingsettings.html', to: '/dashboard/playback/streaming' },
{ from: 'useredit.html', to: '/dashboard/users/profile' },
{ from: 'userlibraryaccess.html', to: '/dashboard/users/access' },
{ from: 'usernew.html', to: '/dashboard/users/add' },
{ from: 'userparentalcontrol.html', to: '/dashboard/users/parentalcontrol' },
{ from: 'userpassword.html', to: '/dashboard/users/password' },
{ from: 'userprofiles.html', to: '/dashboard/users' }
];

View File

@@ -1,274 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
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 Page from 'components/Page';
import UserAvatar from 'components/UserAvatar';
import { useApi } from 'hooks/useApi';
import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } 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';
const DEFAULT_PAGE_SIZE = 25;
const VIEW_PARAM = 'useractivity';
const enum ActivityView {
All,
User,
System
}
const getActivityView = (param: string | null) => {
if (param === null) return ActivityView.All;
if (toBoolean(param)) return ActivityView.User;
return ActivityView.System;
};
const getRowId = (row: ActivityLogEntry) => row.Id ?? -1;
const Activity = () => {
const { api } = useApi();
const [ searchParams, setSearchParams ] = useSearchParams();
const [ activityView, setActivityView ] = useState(
getActivityView(searchParams.get(VIEW_PARAM)));
const [ isLoading, setIsLoading ] = useState(true);
const [ paginationModel, setPaginationModel ] = useState({
page: 0,
pageSize: DEFAULT_PAGE_SIZE
});
const [ rowCount, setRowCount ] = useState(0);
const [ rows, setRows ] = useState<ActivityLogEntry[]>([]);
const [ users, setUsers ] = useState<Record<string, UserDto>>({});
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 onViewChange = useCallback((_e: React.MouseEvent<HTMLElement, MouseEvent>, newView: ActivityView | null) => {
if (newView !== null) {
setActivityView(newView);
}
}, []);
useEffect(() => {
if (api) {
const fetchUsers = async () => {
const { data } = await getUserApi(api).getUsers();
const usersById: Record<string, UserDto> = {};
data.forEach(user => {
if (user.Id) {
usersById[user.Id] = user;
}
});
setUsers(usersById);
};
fetchUsers()
.catch(err => {
console.error('[activity] failed to fetch users', err);
});
}
}, [ api ]);
useEffect(() => {
if (api) {
const fetchActivity = async () => {
const params: {
startIndex: number,
limit: number,
hasUserId?: boolean
} = {
startIndex: paginationModel.page * paginationModel.pageSize,
limit: paginationModel.pageSize
};
if (activityView !== ActivityView.All) {
params.hasUserId = activityView === ActivityView.User;
}
const { data } = await getActivityLogApi(api)
.getLogEntries(params);
setRowCount(data.TotalRecordCount ?? 0);
setRows(data.Items ?? []);
setIsLoading(false);
};
setIsLoading(true);
fetchActivity()
.catch(err => {
console.error('[activity] failed to fetch activity log entries', err);
});
}
}, [ activityView, api, paginationModel ]);
useEffect(() => {
const currentViewParam = getActivityView(searchParams.get(VIEW_PARAM));
if (currentViewParam !== activityView) {
if (activityView === ActivityView.All) {
searchParams.delete(VIEW_PARAM);
} else {
searchParams.set(VIEW_PARAM, `${activityView === ActivityView.User}`);
}
setSearchParams(searchParams);
}
}, [ activityView, searchParams, setSearchParams ]);
return (
<Page
id='serverActivityPage'
title={globalize.translate('HeaderActivity')}
className='mainAnimatedPage type-interior'
>
<div className='content-primary'>
<Box
sx={{
display: 'flex',
alignItems: 'baseline',
marginY: 2
}}
>
<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>
</Box>
<DataGrid
columns={columns}
rows={rows}
pageSizeOptions={[ 10, 25, 50, 100 ]}
paginationMode='server'
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
rowCount={rowCount}
getRowId={getRowId}
loading={isLoading}
sx={{
minHeight: 500
}}
/>
</div>
</Page>
);
};
export default Activity;

View File

@@ -1,313 +0,0 @@
import type { ServerConfiguration } from '@jellyfin/sdk/lib/generated-client/models/server-configuration';
import { TrickplayScanBehavior } from '@jellyfin/sdk/lib/generated-client/models/trickplay-scan-behavior';
import { ProcessPriorityClass } from '@jellyfin/sdk/lib/generated-client/models/process-priority-class';
import React, { type FC, useCallback, useEffect, useRef } from 'react';
import globalize from '../../../../lib/globalize';
import Page from '../../../../components/Page';
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
import ButtonElement from '../../../../elements/ButtonElement';
import CheckBoxElement from '../../../../elements/CheckBoxElement';
import SelectElement from '../../../../elements/SelectElement';
import InputElement from '../../../../elements/InputElement';
import loading from '../../../../components/loading/loading';
import toast from '../../../../components/toast/toast';
import ServerConnections from '../../../../components/ServerConnections';
function onSaveComplete() {
loading.hide();
toast(globalize.translate('SettingsSaved'));
}
const PlaybackTrickplay: FC = () => {
const element = useRef<HTMLDivElement>(null);
const loadConfig = useCallback((config: ServerConfiguration) => {
const page = element.current;
const options = config.TrickplayOptions;
if (!page) {
console.error('Unexpected null reference');
return;
}
(page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked = options?.EnableHwAcceleration || false;
(page.querySelector('.chkEnableHwEncoding') as HTMLInputElement).checked = options?.EnableHwEncoding || false;
(page.querySelector('#selectScanBehavior') as HTMLSelectElement).value = (options?.ScanBehavior || TrickplayScanBehavior.NonBlocking);
(page.querySelector('#selectProcessPriority') as HTMLSelectElement).value = (options?.ProcessPriority || ProcessPriorityClass.Normal);
(page.querySelector('#txtInterval') as HTMLInputElement).value = options?.Interval?.toString() || '10000';
(page.querySelector('#txtWidthResolutions') as HTMLInputElement).value = options?.WidthResolutions?.join(',') || '';
(page.querySelector('#txtTileWidth') as HTMLInputElement).value = options?.TileWidth?.toString() || '10';
(page.querySelector('#txtTileHeight') as HTMLInputElement).value = options?.TileHeight?.toString() || '10';
(page.querySelector('#txtQscale') as HTMLInputElement).value = options?.Qscale?.toString() || '4';
(page.querySelector('#txtJpegQuality') as HTMLInputElement).value = options?.JpegQuality?.toString() || '90';
(page.querySelector('#txtProcessThreads') as HTMLInputElement).value = options?.ProcessThreads?.toString() || '1';
loading.hide();
}, []);
const loadData = useCallback(() => {
loading.show();
ServerConnections.currentApiClient()?.getServerConfiguration().then(function (config) {
loadConfig(config);
}).catch(err => {
console.error('[PlaybackTrickplay] failed to fetch server config', err);
});
}, [loadConfig]);
useEffect(() => {
const page = element.current;
if (!page) {
console.error('Unexpected null reference');
return;
}
const saveConfig = (config: ServerConfiguration) => {
const apiClient = ServerConnections.currentApiClient();
if (!apiClient) {
console.error('[PlaybackTrickplay] No current apiclient instance');
return;
}
if (!config.TrickplayOptions) {
throw new Error('Unexpected null TrickplayOptions');
}
const options = config.TrickplayOptions;
options.EnableHwAcceleration = (page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked;
options.EnableHwEncoding = (page.querySelector('.chkEnableHwEncoding') as HTMLInputElement).checked;
options.ScanBehavior = (page.querySelector('#selectScanBehavior') as HTMLSelectElement).value as TrickplayScanBehavior;
options.ProcessPriority = (page.querySelector('#selectProcessPriority') as HTMLSelectElement).value as ProcessPriorityClass;
options.Interval = Math.max(1, parseInt((page.querySelector('#txtInterval') as HTMLInputElement).value || '10000', 10));
options.WidthResolutions = (page.querySelector('#txtWidthResolutions') as HTMLInputElement).value.replace(' ', '').split(',').map(Number);
options.TileWidth = Math.max(1, parseInt((page.querySelector('#txtTileWidth') as HTMLInputElement).value || '10', 10));
options.TileHeight = Math.max(1, parseInt((page.querySelector('#txtTileHeight') as HTMLInputElement).value || '10', 10));
options.Qscale = Math.min(31, parseInt((page.querySelector('#txtQscale') as HTMLInputElement).value || '4', 10));
options.JpegQuality = Math.min(100, parseInt((page.querySelector('#txtJpegQuality') as HTMLInputElement).value || '90', 10));
options.ProcessThreads = parseInt((page.querySelector('#txtProcessThreads') as HTMLInputElement).value || '1', 10);
apiClient.updateServerConfiguration(config).then(() => {
onSaveComplete();
}).catch(err => {
console.error('[PlaybackTrickplay] failed to update config', err);
});
};
const onSubmit = (e: Event) => {
const apiClient = ServerConnections.currentApiClient();
if (!apiClient) {
console.error('[PlaybackTrickplay] No current apiclient instance');
return;
}
loading.show();
apiClient.getServerConfiguration().then(function (config) {
saveConfig(config);
}).catch(err => {
console.error('[PlaybackTrickplay] failed to fetch server config', err);
});
e.preventDefault();
e.stopPropagation();
return false;
};
(page.querySelector('.trickplayConfigurationForm') as HTMLFormElement).addEventListener('submit', onSubmit);
loadData();
}, [loadData]);
const optionScanBehavior = () => {
let content = '';
content += `<option value='NonBlocking'>${globalize.translate('NonBlockingScan')}</option>`;
content += `<option value='Blocking'>${globalize.translate('BlockingScan')}</option>`;
return content;
};
const optionProcessPriority = () => {
let content = '';
content += `<option value='High'>${globalize.translate('PriorityHigh')}</option>`;
content += `<option value='AboveNormal'>${globalize.translate('PriorityAboveNormal')}</option>`;
content += `<option value='Normal'>${globalize.translate('PriorityNormal')}</option>`;
content += `<option value='BelowNormal'>${globalize.translate('PriorityBelowNormal')}</option>`;
content += `<option value='Idle'>${globalize.translate('PriorityIdle')}</option>`;
return content;
};
return (
<Page
id='trickplayConfigurationPage'
className='mainAnimatedPage type-interior playbackConfigurationPage'
title={globalize.translate('Trickplay')}
>
<div ref={element} className='content-primary'>
<div className='verticalSection'>
<SectionTitleContainer
title={globalize.translate('Trickplay')}
isLinkVisible={false}
/>
</div>
<form className='trickplayConfigurationForm'>
<div className='checkboxContainer checkboxContainer-withDescription'>
<CheckBoxElement
className='chkEnableHwAcceleration'
title='LabelTrickplayAccel'
/>
</div>
<div className='checkboxContainer checkboxContainer-withDescription'>
<CheckBoxElement
className='chkEnableHwEncoding'
title='LabelTrickplayAccelEncoding'
/>
<div className='fieldDescription checkboxFieldDescription'>
<div className='fieldDescription'>
{globalize.translate('LabelTrickplayAccelEncodingHelp')}
</div>
</div>
</div>
<div className='verticalSection'>
<div className='selectContainer fldSelectScanBehavior'>
<SelectElement
id='selectScanBehavior'
label='LabelScanBehavior'
>
{optionScanBehavior()}
</SelectElement>
<div className='fieldDescription'>
{globalize.translate('LabelScanBehaviorHelp')}
</div>
</div>
</div>
<div className='verticalSection'>
<div className='selectContainer fldSelectProcessPriority'>
<SelectElement
id='selectProcessPriority'
label='LabelProcessPriority'
>
{optionProcessPriority()}
</SelectElement>
<div className='fieldDescription'>
{globalize.translate('LabelProcessPriorityHelp')}
</div>
</div>
</div>
<div className='verticalSection'>
<div className='inputContainer'>
<InputElement
type='number'
id='txtInterval'
label='LabelImageInterval'
options={'required inputMode="numeric" pattern="[0-9]*" min="1"'}
/>
<div className='fieldDescription'>
{globalize.translate('LabelImageIntervalHelp')}
</div>
</div>
</div>
<div className='verticalSection'>
<div className='inputContainer'>
<InputElement
type='text'
id='txtWidthResolutions'
label='LabelWidthResolutions'
options={'required pattern="[0-9,]*"'}
/>
<div className='fieldDescription'>
{globalize.translate('LabelWidthResolutionsHelp')}
</div>
</div>
</div>
<div className='verticalSection'>
<div className='inputContainer'>
<InputElement
type='number'
id='txtTileWidth'
label='LabelTileWidth'
options={'required inputMode="numeric" pattern="[0-9]*" min="1"'}
/>
<div className='fieldDescription'>
{globalize.translate('LabelTileWidthHelp')}
</div>
</div>
</div>
<div className='verticalSection'>
<div className='inputContainer'>
<InputElement
type='number'
id='txtTileHeight'
label='LabelTileHeight'
options={'required inputMode="numeric" pattern="[0-9]*" min="1"'}
/>
<div className='fieldDescription'>
{globalize.translate('LabelTileHeightHelp')}
</div>
</div>
</div>
<div className='verticalSection'>
<div className='inputContainer'>
<InputElement
type='number'
id='txtJpegQuality'
label='LabelJpegQuality'
options={'required inputMode="numeric" pattern="[0-9]*" min="1" max="100"'}
/>
<div className='fieldDescription'>
{globalize.translate('LabelJpegQualityHelp')}
</div>
</div>
</div>
<div className='verticalSection'>
<div className='inputContainer'>
<InputElement
type='number'
id='txtQscale'
label='LabelQscale'
options={'required inputMode="numeric" pattern="[0-9]*" min="2" max="31"'}
/>
<div className='fieldDescription'>
{globalize.translate('LabelQscaleHelp')}
</div>
</div>
</div>
<div className='verticalSection'>
<div className='inputContainer'>
<InputElement
type='number'
id='txtProcessThreads'
label='LabelTrickplayThreads'
options={'required inputMode="numeric" pattern="[0-9]*" min="0"'}
/>
<div className='fieldDescription'>
{globalize.translate('LabelTrickplayThreadsHelp')}
</div>
</div>
</div>
<div>
<ButtonElement
type='submit'
className='raised button-submit block'
title='Save'
/>
</div>
</form>
</div>
</Page>
);
};
export default PlaybackTrickplay;

Some files were not shown because too many files have changed in this diff Show More