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
571 changed files with 25945 additions and 61999 deletions

View File

@@ -21,19 +21,19 @@ jobs:
- task: Cache@2
displayName: 'Cache node_modules'
inputs:
key: 'npm | package-lock.json'
key: 'yarn | yarn.lock'
path: 'node_modules'
- script: 'npm ci --no-audit'
- script: 'yarn install --frozen-lockfile'
displayName: 'Install Dependencies'
env:
SKIP_PREPARE: 'true'
- script: 'npm run build:development'
- script: 'yarn build:development'
displayName: 'Build Development'
condition: eq(variables['BuildConfiguration'], 'development')
- script: 'npm run build:production'
- script: 'yarn build:production'
displayName: 'Build Production'
condition: eq(variables['BuildConfiguration'], 'production')

View File

@@ -17,10 +17,6 @@ jobs:
vmImage: 'ubuntu-latest'
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/v')
- 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'))
@@ -123,4 +119,4 @@ jobs:
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) $(Build.SourceBranch)'
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)'

1
.copr
View File

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

1
.copr/Makefile Symbolic link
View File

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

View File

@@ -4,7 +4,6 @@ module.exports = {
root: true,
plugins: [
'@babel',
'react',
'promise',
'import',
'eslint-comments'
@@ -19,31 +18,25 @@ module.exports = {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
impliedStrict: true,
jsx: true
impliedStrict: true
}
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
// 'plugin:promise/recommended',
'plugin:import/errors',
'plugin:eslint-comments/recommended',
'plugin:compat/recommended'
],
rules: {
'array-callback-return': ['error'],
'block-spacing': ['error'],
'brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
'comma-dangle': ['error', 'never'],
'comma-spacing': ['error'],
'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'],
'no-empty-function': ['error'],
'no-floating-decimal': ['error'],
'no-multi-spaces': ['error'],
'no-multiple-empty-lines': ['error', { 'max': 1 }],
@@ -60,110 +53,10 @@ module.exports = {
'space-infix-ops': 'error',
'yoda': 'error'
},
settings: {
react: {
version: 'detect'
},
'import/extensions': [
'.js',
'.ts',
'.jsx',
'.tsx'
],
'import/parsers': {
'@typescript-eslint/parser': [ '.ts', '.tsx' ]
},
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'
]
},
overrides: [
{
files: [
'./src/**/*.js',
'./src/**/*.ts'
'./src/**/*.js'
],
parser: '@babel/eslint-parser',
env: {
@@ -187,7 +80,6 @@ module.exports = {
'jQuery': 'readonly',
// Jellyfin globals
'ApiClient': 'writable',
'Events': 'writable',
'chrome': 'writable',
'DlnaProfilePage': 'writable',
'DashboardPage': 'writable',
@@ -203,30 +95,102 @@ module.exports = {
'Loading': 'writable',
'MetadataEditor': 'writable',
'PlaylistViewer': 'writable',
'ServerNotifications': 'writable',
'TaskButton': 'writable',
'UserParentalControlPage': 'writable',
'Windows': 'readonly'
},
rules: {
// 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'
]
}
},
{
files: [
'./src/**/*.ts',
'./src/**/*.tsx'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
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'
]
}
]
};

4
.github/SUPPORT.md vendored
View File

@@ -13,8 +13,8 @@ question in these venues:
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

51
.github/renovate.json vendored
View File

@@ -1,51 +0,0 @@
{
"packageRules": [
{
"matchManagers": ["npm"],
"addLabels": ["javascript"]
},
{
"description": "Adds label to dev dependency updates",
"matchDepTypes": ["devDependencies"],
"addLabels": ["dev-deps"]
},
{
"description": "Collects and groups dev dependency updates",
"matchDepTypes": ["devDependencies"],
"groupName": "development dependencies",
"groupSlug": "dev-deps"
},
{
"description": "Collects and groups npm dependency updates",
"matchDepTypes": ["dependencies"],
"groupName": "dependencies",
"groupSlug": "deps"
},
{
"description": "Collects and groups GitHub Action dependency updates",
"matchDepTypes": ["action"],
"addLabels": ["github_actions"],
"groupName": "CI dependencies",
"groupSlug": "ci-deps"
},
{
"description": "Disables HLS.js major updates",
"matchPackageNames": ["hls.js"],
"matchUpdateTypes": "major",
"enabled": false
}
],
"vulnerabilityAlerts": {
"addLabels": ["security"]
},
"dependencyDashboard": false,
"ignoreDeps": ["npm", "node"],
"lockFileMaintenance": {
"enabled": false
},
"enabledManagers": ["npm", "github-actions"],
"labels": ["dependencies"],
"prHourlyLimit": 2,
"rebaseWhen": "conflicted",
"rangeStrategy": "pin"
}

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,20 +0,0 @@
name: 'Automation'
on:
push:
branches:
- master
pull_request_target:
types:
- synchronize
jobs:
triage:
name: 'Merge conflict labeling'
runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin-web' }}
steps:
- uses: eps1lon/actions-label-merge-conflict@v2.0.1
with:
dirtyLabel: 'merge conflict'
repoToken: ${{ secrets.JF_BOT_TOKEN }}

View File

@@ -19,7 +19,7 @@ jobs:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:

View File

@@ -1,28 +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@v1.4.5
with:
token: ${{ secrets.JF_BOT_TOKEN }}
comment-id: ${{ github.event.comment.id }}
reactions: '+1'
- name: Checkout the latest code
uses: actions/checkout@v3.0.0
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.5
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}

View File

@@ -2,9 +2,9 @@ name: Lint
on:
push:
branches: [ master, release* ]
branches: [ master ]
pull_request:
branches: [ master, release* ]
branches: [ master ]
jobs:
run-eslint:
@@ -13,34 +13,26 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Setup node environment
uses: actions/setup-node@v3.0.0
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 12
check-latest: true
- name: Get npm cache directory path
id: npm-cache-dir-path
run: echo "::set-output name=dir::$(npm config get cache)"
- name: Cache node_modules
uses: actions/cache@v3.0.0
id: npm-cache
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install Node.js dependencies
run: npm ci --no-audit
run: yarn install --frozen-lockfile
env:
SKIP_PREPARE: true
- name: Run eslint
run: npm run lint
run: yarn lint
run-stylelint-css:
name: Run stylelint (css)
@@ -48,37 +40,29 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Setup node environment
uses: actions/setup-node@v3.0.0
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 12
check-latest: true
- name: Get npm cache directory path
id: npm-cache-dir-path
run: echo "::set-output name=dir::$(npm config get cache)"
- name: Cache node_modules
uses: actions/cache@v3.0.0
id: npm-cache
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- 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: npm ci --no-audit
run: yarn install --frozen-lockfile
env:
SKIP_PREPARE: true
- name: Run stylelint
run: npm run stylelint:css
run: yarn stylelint:css
run-stylelint-scss:
name: Run stylelint (scss)
@@ -86,34 +70,26 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Setup node environment
uses: actions/setup-node@v3.0.0
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 12
check-latest: true
- name: Get npm cache directory path
id: npm-cache-dir-path
run: echo "::set-output name=dir::$(npm config get cache)"
- name: Cache node_modules
uses: actions/cache@v3.0.0
id: npm-cache
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- 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: npm ci --no-audit
run: yarn install --frozen-lockfile
env:
SKIP_PREPARE: true
- name: Run stylelint
run: npm run stylelint:scss
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,27 +0,0 @@
name: Issue Stale Check
on:
schedule:
- cron: '30 1 * * *'
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- uses: actions/stale@v5.0.0
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
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://docs.jellyfin.org/general/getting-help.html).

7
.gitignore vendored
View File

@@ -12,10 +12,3 @@ config.json
# log
yarn-error.log
# vim
*.sw?
# build artifacts
fedora/jellyfin-web-*.src.rpm
fedora/jellyfin-web-*.tar.gz

2
.npmrc
View File

@@ -1,2 +0,0 @@
fund=false
save-exact=true

View File

@@ -59,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",

View File

@@ -1,6 +1,5 @@
{
"extends": [ "./.stylelintrc.json" ],
"customSyntax": "postcss-scss",
"plugins": [ "stylelint-scss" ],
"rules": {
"at-rule-no-unknown": null,

View File

@@ -45,14 +45,6 @@
- [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)
- [Peter Spenler](https://github.com/peterspenler)
# Emby Contributors
@@ -116,6 +108,3 @@
- [tikuf](https://github.com/tikuf/)
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [jomp16](https://github.com/jomp16)
- [Leon de Klerk](https://github.com/leondeklerk)
- [CrispyBaguette](https://github.com/CrispyBaguette)

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,17 +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
```

View File

@@ -11,14 +11,6 @@ module.exports = {
useBuiltIns: 'usage',
corejs: 3
}
],
'@babel/preset-react',
[
'@babel/preset-typescript',
{
isTSX: true,
allExtensions: true
}
]
],
plugins: [

View File

@@ -39,7 +39,7 @@ do_build_native() {
}
do_build_docker() {
if ! [ $(uname -m) = "x86_64" ]; then
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

View File

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

View File

@@ -18,39 +18,38 @@ if [[ -z $1 ]]; then
exit 1
fi
shared_version_file="src/components/apphost.js"
build_file="./build.yaml"
package_file="./package*.json"
new_version="$1"
old_version="$(
grep "version:" ${build_file} \
| sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/'
)"
echo "Old version: ${old_version}"
# 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"
# Bump the NPM 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}" )"
npm --no-git-tag-version --allow-same-version version v${new_version_sed}
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_sed}/g" ${build_file}
sed -i "s/${old_version_sed}/${new_version}/g" ${build_file}
if [[ ${new_version} == *"-"* ]]; then
new_version_pkg="$( sed 's/-/~/g' <<<"${new_version}" )"
new_version_deb_sup=""
new_version_deb="$( sed 's/-/~/g' <<<"${new_version}" )"
else
new_version_pkg="${new_version}"
new_version_deb_sup="-1"
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_pkg}${new_version_deb_sup}) unstable; urgency=medium
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}
@@ -71,7 +70,7 @@ 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_pkg}/g" 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
@@ -85,8 +84,8 @@ popd
# Move into place
mv ${fedora_spec_temp} ${fedora_spec_file}
# Clean up
rm -rf ${fedora_spec_temp_dir}
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

92
debian/changelog vendored
View File

@@ -1,95 +1,45 @@
jellyfin-web (10.8.13-1) unstable; urgency=medium
jellyfin-web (10.7.7-1) unstable; urgency=medium
* New upstream version 10.8.13; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.13
* 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> Tue, 28 Nov 2023 22:21:29 -0500
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 05 Sep 2021 22:32:59 -0400
jellyfin-web (10.8.12-1) unstable; urgency=medium
jellyfin-web (10.7.6-1) unstable; urgency=medium
* New upstream version 10.8.12; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.12
* 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> Sat, 04 Nov 2023 14:42:41 -0400
-- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 20 May 2021 22:06:52 -0400
jellyfin-web (10.8.11-1) unstable; urgency=medium
jellyfin-web (10.7.5-1) unstable; urgency=medium
* New upstream version 10.8.11; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.11
* 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> Sat, 23 Sep 2023 21:41:40 -0400
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 22:08:30 -0400
jellyfin-web (10.8.10-1) unstable; urgency=medium
jellyfin-web (10.7.4-1) unstable; urgency=medium
* New upstream version 10.8.10; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.10
* 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> Sun, 23 Apr 2023 11:01:33 -0400
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 21:16:07 -0400
jellyfin-web (10.8.9-1) unstable; urgency=medium
jellyfin-web (10.7.3-1) unstable; urgency=medium
* New upstream version 10.8.9; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.9
* 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> Sun, 22 Jan 2023 14:09:13 -0500
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 20:00:22 -0400
jellyfin-web (10.8.8-1) unstable; urgency=medium
jellyfin-web (10.7.2-1) unstable; urgency=medium
* New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.8
* 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> Tue, 29 Nov 2022 13:42:54 -0500
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 11 Apr 2021 14:19:38 -0400
jellyfin-web (10.8.7-1) unstable; urgency=medium
jellyfin-web (10.7.1-1) unstable; urgency=medium
* New upstream version 10.8.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.7
* 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> Mon, 31 Oct 2022 23:06:34 -0400
jellyfin-web (10.8.6-1) unstable; urgency=medium
* New upstream version 10.8.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.6
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 28 Oct 2022 22:44:15 -0400
jellyfin-web (10.8.5-1) unstable; urgency=medium
* New upstream version 10.8.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.5
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 24 Sep 2022 22:02:26 -0400
jellyfin-web (10.8.4-1) unstable; urgency=medium
* New upstream version 10.8.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.4
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 13 Aug 2022 21:52:04 -0400
jellyfin-web (10.8.3-1) unstable; urgency=medium
* New upstream version 10.8.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.3
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 01 Aug 2022 20:22:00 -0400
jellyfin-web (10.8.2-1) unstable; urgency=medium
* New upstream version 10.8.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.2
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 01 Aug 2022 14:27:56 -0400
jellyfin-web (10.8.1-1) unstable; urgency=medium
* New upstream version 10.8.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.1
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 26 Jun 2022 20:59:39 -0400
jellyfin-web (10.8.0-1) unstable; urgency=medium
* New upstream version 10.8.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.0
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 10 Jun 2022 22:16:40 -0400
-- 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
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 27 Jul 2020 19:13:31 -0400
jellyfin-web (10.6.0-1) unstable; urgency=medium
* New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 16 Mar 2020 11:15:00 -0400

2
debian/rules vendored
View File

@@ -11,7 +11,7 @@ override_dh_auto_test:
override_dh_clistrip:
override_dh_auto_build:
npm ci --no-audit --unsafe-perm
npx yarn install
mv $(CURDIR)/dist $(CURDIR)/web
override_dh_auto_clean:

View File

@@ -12,9 +12,12 @@ 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 autoconf automake glibc-devel gcc-c++ make \
&& curl -fsSL https://rpm.nodesource.com/setup_12.x | bash - \
&& yum install -y nodejs
&& 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

View File

@@ -12,10 +12,10 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
&& apt-get install -y debhelper mmv git curl \
&& curl -fsSL https://deb.nodesource.com/setup_12.x | bash - \
&& apt-get install -y nodejs
&& 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

View File

@@ -8,4 +8,4 @@ RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool ma
WORKDIR ${SOURCE_DIR}
COPY . .
RUN npm ci --no-audit --unsafe-perm && mv dist ${ARTIFACT_DIR}
RUN yarn install && mv dist ${ARTIFACT_DIR}

View File

@@ -1,4 +1,4 @@
FROM fedora:36
FROM fedora:33
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
@@ -11,7 +11,7 @@ ENV IS_DOCKER=YES
# Prepare Fedora environment
RUN dnf update -y \
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs autoconf automake glibc-devel make
&& 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

View File

@@ -11,9 +11,10 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
&& apt-get install -y mmv curl git \
&& curl -fsSL https://deb.nodesource.com/setup_12.x | bash - \
&& apt-get install -y nodejs
&& 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

View File

@@ -6,7 +6,7 @@ set -o xtrace
# move to source directory
pushd ${SOURCE_DIR}
cp -a package-lock.json /tmp/package-lock.json
cp -a yarn.lock /tmp/yarn.lock
# modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -36,6 +36,6 @@ if [[ ${IS_DOCKER} == YES ]]; then
fi
rm -f fedora/jellyfin*.tar.gz
cp -a /tmp/package-lock.json package-lock.json
cp -a /tmp/yarn.lock yarn.lock
popd

View File

@@ -6,7 +6,7 @@ set -o xtrace
# move to source directory
pushd ${SOURCE_DIR}
cp -a package-lock.json /tmp/package-lock.json
cp -a yarn.lock /tmp/yarn.lock
# modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -30,7 +30,7 @@ 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/package-lock.json package-lock.json
cp -a /tmp/yarn.lock yarn.lock
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}

View File

@@ -6,7 +6,7 @@ set -o xtrace
# move to source directory
pushd ${SOURCE_DIR}
cp -a package-lock.json /tmp/package-lock.json
cp -a yarn.lock /tmp/yarn.lock
# modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -36,6 +36,6 @@ if [[ ${IS_DOCKER} == YES ]]; then
fi
rm -f fedora/jellyfin*.tar.gz
cp -a /tmp/package-lock.json package-lock.json
cp -a /tmp/yarn.lock yarn.lock
popd

View File

@@ -14,7 +14,7 @@ else
fi
# build archives
npm ci --no-audit --unsafe-perm
npx yarn install
mv dist jellyfin-web_${version}
tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version}
rm -rf dist

View File

@@ -1,48 +1,21 @@
DIR := $(dir $(lastword $(MAKEFILE_LIST)))
# install git and npm
$(info $(shell set -x; if [ "$$(id -u)" = "0" ]; then echo "Installing git"; dnf -y install git npm; fi))
NAME := jellyfin-web
VERSION := $(shell set -x; sed -ne '/^Version:/s/.* *//p' $(DIR)/$(NAME).spec)
RELEASE := $(shell set -x; sed -ne '/^Release:/s/.* *\(.*\)%{.*}.*/\1/p' $(DIR)/$(NAME).spec)
SRPM := jellyfin-web-$(subst -,~,$(VERSION))-$(RELEASE)$(shell rpm --eval %dist).src.rpm
TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).tar.gz
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' fedora/jellyfin-web.spec)
epel-7-x86_64_repos := https://rpm.nodesource.com/pub_16.x/el/\$$releasever/\$$basearch/
fed_ver := $(shell rpm -E %fedora)
# fallback when not running on Fedora
fed_ver ?= 36
TARGET ?= fedora-$(fed_ver)-x86_64
outdir ?= $(PWD)/$(DIR)/
srpm: $(DIR)/$(SRPM)
tarball: $(DIR)/$(TARBALL)
$(DIR)/$(TARBALL):
cd $(DIR)/; \
SOURCE_DIR=.. \
WORKDIR="$${PWD}"; \
version=$(VERSION); \
tar \
--transform "s,^\.,$(NAME)-$(subst -,~,$(VERSION))," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude=deployment \
--exclude='*.deb' \
--exclude='*.rpm' \
--exclude=$(notdir $@) \
-czf $(notdir $@) \
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} ./
$(DIR)/$(SRPM): $(DIR)/$(TARBALL) $(DIR)/jellyfin-web.spec
cd $(DIR)/; \
rpmbuild -bs $(NAME).spec \
--define "_sourcedir $$PWD/" \
cd fedora/; \
rpmbuild -bs jellyfin-web.spec \
--define "_sourcedir $$PWD/" \
--define "_srcrpmdir $(outdir)"
rpms: $(DIR)/$(SRPM)
mock $(addprefix --addrepo=, $($(TARGET)_repos)) \
--enable-network \
-r $(TARGET) $<

View File

@@ -1,21 +1,29 @@
%global debug_package %{nil}
Name: jellyfin-web
Version: 10.8.13
Version: 10.7.7
Release: 1%{?dist}
Summary: The Free Software Media System web client
License: GPLv2
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
BuildArch: noarch
%if 0%{?rhel} > 0 && 0%{?rhel} < 8
BuildRequires: nodejs
%if 0%{?centos}
BuildRequires: yarn
%else
BuildRequires: git
BuildRequires: npm
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.
@@ -24,53 +32,37 @@ Jellyfin is a free software media system that puts you in control of managing an
%prep
%autosetup -n jellyfin-web-%{version} -b 0
%if 0%{?rhel} > 0 && 0%{?rhel} < 8
# Required for CentOS build
chown root:root -R .
%endif
%build
npm ci --no-audit --unsafe-perm
%install
%{__mkdir} -p %{buildroot}%{_libdir}/jellyfin/jellyfin-web
%{__cp} -r dist/* %{buildroot}%{_libdir}/jellyfin/jellyfin-web
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)
%{_libdir}/jellyfin/jellyfin-web
%license LICENSE
%{_datadir}/jellyfin-web
%{_datadir}/licenses/jellyfin/LICENSE
%changelog
* Tue Nov 28 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.13; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.13
* Sat Nov 04 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.12; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.12
* Sat Sep 23 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.11; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.11
* Sun Apr 23 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.10; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.10
* Sun Jan 22 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.9; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.9
* Tue Nov 29 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.8
* Mon Oct 31 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.7
* Fri Oct 28 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.6
* Sat Sep 24 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.5
* Sat Aug 13 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.4
* Mon Aug 01 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.3
* Mon Aug 01 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.2
* Sun Jun 26 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.1
* Fri Jun 10 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.0
* 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

15705
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,109 +1,86 @@
{
"name": "jellyfin-web",
"version": "10.8.13",
"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.17.7",
"@babel/eslint-parser": "7.17.0",
"@babel/eslint-plugin": "7.17.7",
"@babel/plugin-proposal-class-properties": "7.16.7",
"@babel/plugin-proposal-private-methods": "7.16.11",
"@babel/plugin-transform-modules-umd": "7.16.7",
"@babel/preset-env": "7.16.11",
"@babel/preset-react": "7.16.7",
"@babel/preset-typescript": "7.16.7",
"@thornbill/jellyfin-sdk": "0.4.1",
"@types/escape-html": "1.0.1",
"@types/lodash-es": "4.17.6",
"@types/react": "17.0.40",
"@types/react-dom": "17.0.13",
"@typescript-eslint/eslint-plugin": "5.15.0",
"@typescript-eslint/parser": "5.15.0",
"@uupaa/dynamic-import-polyfill": "1.0.2",
"autoprefixer": "10.4.4",
"babel-loader": "8.2.3",
"babel-plugin-dynamic-import-polyfill": "1.0.0",
"clean-webpack-plugin": "4.0.0",
"confusing-browser-globals": "1.0.11",
"copy-webpack-plugin": "10.2.4",
"css-loader": "6.7.1",
"cssnano": "5.1.4",
"eslint": "8.11.0",
"eslint-plugin-compat": "4.0.2",
"eslint-plugin-eslint-comments": "3.2.0",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-promise": "6.0.0",
"eslint-plugin-react": "7.29.4",
"eslint-plugin-react-hooks": "4.3.0",
"expose-loader": "3.1.0",
"html-loader": "3.1.0",
"html-webpack-plugin": "5.5.0",
"postcss": "8.4.12",
"postcss-loader": "6.2.1",
"postcss-preset-env": "7.4.2",
"postcss-scss": "4.0.3",
"sass": "1.49.9",
"sass-loader": "12.6.0",
"source-map-loader": "3.0.1",
"style-loader": "3.3.1",
"stylelint": "14.6.0",
"stylelint-config-rational-order": "0.1.2",
"stylelint-no-browser-hacks": "1.2.1",
"stylelint-order": "5.0.0",
"stylelint-scss": "4.2.0",
"ts-loader": "9.2.8",
"typescript": "4.6.2",
"webpack": "5.70.0",
"webpack-cli": "4.9.2",
"webpack-dev-server": "4.7.4",
"webpack-merge": "5.8.0",
"workbox-webpack-plugin": "6.5.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": {
"@fontsource/noto-sans": "4.5.1",
"@fontsource/noto-sans-hk": "4.5.2",
"@fontsource/noto-sans-jp": "4.5.2",
"@fontsource/noto-sans-kr": "4.5.2",
"@fontsource/noto-sans-sc": "4.5.2",
"@fontsource/noto-sans-tc": "4.5.2",
"@jellyfin/libass-wasm": "4.1.1",
"blurhash": "1.1.4",
"blurhash": "^1.1.3",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"classnames": "2.3.1",
"core-js": "3.20.2",
"date-fns": "2.28.0",
"dompurify": "2.3.4",
"epubjs": "0.3.93",
"escape-html": "1.0.3",
"fast-text-encoding": "1.0.3",
"flv.js": "1.6.2",
"headroom.js": "0.12.0",
"hls.js": "0.14.17",
"intersection-observer": "0.12.0",
"jellyfin-apiclient": "1.10.0",
"jquery": "3.6.0",
"jstree": "3.3.12",
"libarchive.js": "1.3.0",
"lodash-es": "4.17.21",
"marked": "4.0.10",
"material-design-icons-iconfont": "6.1.1",
"native-promise-only": "0.8.1",
"page": "1.11.6",
"pdfjs-dist": "2.12.313",
"react": "17.0.2",
"react-dom": "17.0.2",
"resize-observer-polyfill": "1.5.1",
"screenfull": "6.0.0",
"sortablejs": "1.14.0",
"swiper": "6.8.4",
"webcomponents.js": "0.7.24",
"whatwg-fetch": "3.6.2",
"workbox-core": "6.2.4",
"workbox-precaching": "6.2.4"
"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",
@@ -122,17 +99,14 @@
"Firefox ESR"
],
"scripts": {
"start": "npm run serve",
"start": "yarn serve",
"serve": "webpack serve --config webpack.dev.js",
"prepare": "node ./scripts/prepare.js",
"build:development": "webpack --config webpack.dev.js",
"build:production": "webpack --config webpack.prod.js",
"lint": "eslint \"src/\"",
"stylelint": "npm run stylelint:css && npm run stylelint:scss",
"stylelint": "yarn stylelint:css && yarn stylelint:scss",
"stylelint:css": "stylelint \"src/**/*.css\"",
"stylelint:scss": "stylelint --config=\".stylelintrc.scss.json\" \"src/**/*.scss\""
},
"engines": {
"yarn": "YARN NO LONGER USED - use npm instead."
}
}

352
src/apiclient.d.ts vendored
View File

@@ -1,352 +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 '@thornbill/jellyfin-sdk/dist/generated-client';
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>;
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[]>;
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;
}
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<any>;
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

@@ -214,7 +214,6 @@ div[data-role=controlgroup] a.ui-btn-active {
background-repeat: no-repeat;
background-position: center center;
position: absolute;
border-radius: 0.2em;
top: 0;
left: 0;
right: 0;
@@ -346,15 +345,11 @@ div[data-role=controlgroup] a.ui-btn-active {
right: 0;
bottom: 0;
font-weight: 400;
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
}
.darkenContent {
background: rgba(0, 0, 0, 0.7);
color: #ddd;
}
.sessionAppName {
vertical-align: top;
max-width: 200px;

View File

@@ -1,38 +1,18 @@
@import "../../styles/noto-sans/index.scss";
@mixin font($weight: null, $size: null) {
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", sans-serif;
font-weight: $weight;
font-size: $size;
}
html {
@include font($size: 93%);
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", sans-serif;
text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
html[lang|="ja"] {
font-family: "Noto Sans", "Noto Sans JP", "Noto Sans HK", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", sans-serif;
}
html[lang|="ko"] {
font-family: "Noto Sans", "Noto Sans KR", "Noto Sans HK", "Noto Sans JP", "Noto Sans SC", "Noto Sans TC", sans-serif;
}
html[lang|="zh-CN"] {
font-family: "Noto Sans", "Noto Sans SC", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans TC", sans-serif;
}
html[lang|="zh-TW"] {
font-family: "Noto Sans", "Noto Sans TC", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", sans-serif;
}
html[lang|="zh-HK"] {
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", sans-serif;
}
h1 {
@include font(400, 1.8em);
}

View File

@@ -1,37 +1,3 @@
// The padding of the header content on mobile needs to be adjusted
// based on the size of the poster card (values from card.scss)
@mixin header-poster-padding() {
padding-left: 37.5%;
@media all and (min-width: 43.75em) {
padding-left: 25%;
}
@media all and (min-width: 50em) {
padding-left: 20%;
}
@media all and (min-width: 75em) {
padding-left: 16.666666666666666666666666666667%;
}
@media all and (min-width: 87.5em) {
padding-left: 14.285714285714285714285714285714%;
}
@media all and (min-width: 100em) {
padding-left: 12.5%;
}
@media all and (min-width: 120em) {
padding-left: 11.111111111111111111111111111111%;
}
@media all and (min-width: 131.25em) {
padding-left: 10%;
}
}
.headerUserImage,
.navMenuOption,
.pageTitle {
@@ -86,6 +52,8 @@
z-index: 1;
margin: 0 !important;
top: 6.9em !important;
-webkit-transition: -webkit-transform 0.2s ease-out;
-o-transition: transform 0.2s ease-out;
transition: transform 0.2s ease-out;
}
@@ -94,14 +62,17 @@
}
.headerUserImage {
-webkit-background-size: contain;
background-size: contain;
background-repeat: no-repeat;
background-position: center center;
-webkit-border-radius: 100em;
border-radius: 100em;
display: inline-block;
}
.headerUserButtonRound div {
-webkit-border-radius: 100em;
border-radius: 100em;
background-size: cover;
background-repeat: no-repeat;
@@ -109,6 +80,7 @@
}
.headerButton {
-webkit-flex-shrink: 0;
flex-shrink: 0;
}
@@ -118,15 +90,23 @@
.headerLeft {
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
overflow: hidden;
justify-content: flex-start;
}
.headerRight {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
}
@@ -136,10 +116,15 @@
}
.pageTitle {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
margin: 0 0 0 0.5em;
height: 1.7em;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-flex-shrink: 1;
flex-shrink: 1;
}
@@ -149,16 +134,21 @@
.headerLeft,
.skinHeader {
display: flex;
display: -webkit-box;
display: -webkit-flex;
}
.detailButton,
.skinHeader {
flex-direction: column;
-webkit-flex-direction: column;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
}
.pageTitleWithLogo {
background-position: left center;
-webkit-background-size: contain;
background-size: contain;
background-repeat: no-repeat;
width: 13.2em;
@@ -204,19 +194,27 @@
}
.navMenuOption {
display: -webkit-box !important;
display: -webkit-flex !important;
display: flex !important;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
text-decoration: none;
color: inherit;
padding: 0.9em 0 0.9em 2.4em !important;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
font-weight: 400 !important;
margin: 0 !important;
-webkit-border-radius: 0 !important;
border-radius: 0 !important;
}
.navMenuOptionIcon {
margin-right: 1.2em;
-webkit-flex-shrink: 0;
flex-shrink: 0;
}
@@ -231,6 +229,8 @@
}
.dashboardDocument .skinBody {
-webkit-transition: left ease-in-out 0.3s, padding ease-in-out 0.3s;
-o-transition: left ease-in-out 0.3s, padding ease-in-out 0.3s;
transition: left ease-in-out 0.3s, padding ease-in-out 0.3s;
position: absolute;
top: 0;
@@ -250,6 +250,26 @@
padding-bottom: 10vh;
}
.primaryImageWrapper {
display: none;
}
.primaryImageWrapper > img {
display: block;
margin: 0 auto;
max-width: 80vw;
max-height: 50vh;
}
.primaryImageWrapper > img.aspect-square {
max-height: 45vh;
}
.layout-mobile .primaryImageWrapper {
display: block;
flex: 1 0 auto;
}
@media all and (min-width: 40em) {
.dashboardDocument .adminDrawerLogo,
.dashboardDocument .mainDrawerButton {
@@ -260,7 +280,9 @@
z-index: inherit !important;
left: 0 !important;
top: 0 !important;
-webkit-transform: none !important;
transform: none !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
width: 20.205em !important;
font-size: 94%;
@@ -295,9 +317,14 @@
}
.headerTabs {
-webkit-align-self: center;
align-self: center;
width: auto;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
position: relative;
margin-top: -4.3em;
@@ -354,6 +381,8 @@
}
.flexPageTabContent.is-active {
display: -webkit-box !important;
display: -webkit-flex !important;
display: flex !important;
}
@@ -374,17 +403,13 @@
margin: 1.5em 0;
background: #222;
padding: 0.8em 0.8em 0.8em 3em;
-webkit-border-radius: 0.3em;
border-radius: 0.3em;
position: relative;
}
.detailLogo {
width: 25vw;
height: 16vh;
position: absolute;
top: 10vh;
right: 25vw;
background-size: contain;
.detailLogo,
.itemBackdrop {
background-repeat: no-repeat;
background-position: center center;
}
@@ -437,34 +462,30 @@
}
.itemBackdrop {
-webkit-background-size: cover;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
background-position: center;
background-attachment: fixed;
height: 40vh;
position: relative;
animation: backdrop-fadein 800ms ease-in normal both;
.layout-mobile & {
background-attachment: initial;
background-position: top center;
margin-top: 3rem;
@media all and (orientation: portrait) and (max-width: 40em) {
height: 30vh;
}
}
.layout-desktop &::after {
content: "";
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.65);
display: block;
}
}
.layout-tv .itemBackdrop {
.layout-mobile .itemBackdrop {
display: none;
}
.layout-desktop .itemBackdrop::after {
content: "";
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.65);
display: block;
}
.layout-tv .itemBackdrop,
.layout-desktop .noBackdrop .itemBackdrop {
display: none;
}
@@ -473,18 +494,26 @@
flex-direction: column;
padding-left: 32.45vw;
padding-right: 2%;
}
.layout-mobile & {
padding-left: 5%;
padding-right: 5%;
}
.layout-mobile .detailPageContent {
padding-left: 5%;
padding-right: 5%;
}
.layout-desktop &,
.layout-tv & {
.emby-scroller {
margin-left: 0;
}
}
.layout-desktop .detailPageContent .emby-scroller,
.layout-tv .detailPageContent .emby-scroller {
margin-left: 0;
}
.layout-desktop .noBackdrop .detailPageContent,
.layout-tv .noBackdrop .detailPageContent {
margin-top: 2.5em;
}
.layout-desktop .noBackdrop .detailImageContainer img,
.layout-tv .noBackdrop .detailImageContainer img {
margin-top: 0;
}
.detailSectionContent a {
@@ -524,8 +553,14 @@
margin: -0.25em 0 0.25em;
}
.layout-mobile .itemExternalLinks {
display: none;
}
.mainDetailButtons {
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
margin: 1em 0;
}
@@ -533,19 +568,13 @@
.detailButton,
.mainDetailButtons {
display: flex;
display: -webkit-box;
display: -webkit-flex;
}
.itemName {
margin: 0.5em 0;
font-weight: 600;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
.layout-mobile & {
white-space: normal;
overflow: visible;
}
}
.itemName.originalTitle {
@@ -584,19 +613,14 @@
}
.itemMiscInfo {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
.layout-mobile & {
@media all and (orientation: portrait) and (max-width: 40em) {
margin-bottom: 0 !important;
.mediaInfoItem {
margin-top: 0.5em;
}
}
}
}
.layout-mobile .parentName,
@@ -609,30 +633,17 @@
}
.layout-mobile .mainDetailButtons {
margin-top: 1em;
flex: 2 0 70%;
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-left: 0;
@include header-poster-padding;
// The buttons row is full width on small screens
@media all and (max-width: 32em) {
margin-bottom: 0;
padding-left: 0;
}
}
.subtitle {
margin: 0.15em 0 0.2em;
// Leave room for a focused button
margin-left: -1em;
padding-left: 1em;
}
.layout-mobile .subtitle {
margin: 0.2em 0 0.2em;
padding-left: 0; // Reset padding for focused button since 'margin-left' is 0
}
.detailPagePrimaryContainer {
@@ -640,22 +651,23 @@
align-items: center;
align-content: center;
z-index: 2;
}
.layout-mobile & {
display: block;
position: relative;
padding: 0.5rem 5%;
}
.layout-tv .detailPagePrimaryContainer {
display: block;
}
.layout-desktop & {
position: relative;
padding-left: 32.45vw;
}
.layout-mobile .detailPagePrimaryContainer {
flex-wrap: wrap;
position: relative;
padding: 4.5rem 3.3% 0.5rem;
}
.layout-tv & {
display: block;
padding-left: 32.45vw;
}
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
.layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer {
position: relative;
top: 0;
padding-left: 32.45vw;
}
.layout-desktop .detailRibbon {
@@ -668,24 +680,30 @@
height: inherit;
}
.layout-desktop .noBackdrop .detailRibbon,
.layout-tv .noBackdrop .detailRibbon {
margin-top: 0;
}
.infoWrapper {
min-width: 0;
max-width: 100%;
flex: 1 0 0;
}
.layout-mobile & {
@include header-poster-padding;
@media all and (max-width: 32em) {
position: relative;
}
}
.layout-mobile .infoWrapper {
flex: 2 0 70%;
}
.infoText {
white-space: nowrap;
text-overflow: ellipsis;
text-align: left;
min-width: 0;
max-width: 100%;
overflow: hidden;
}
.layout-mobile .infoText {
white-space: normal;
}
.detailPageSecondaryContainer {
@@ -696,65 +714,46 @@
margin: 1em 0;
}
.layout-mobile .detailImageContainer {
display: none;
}
.detailImageContainer .card {
// important is needed here to override :focus setting
// the position to relative in the tv layout
position: absolute !important;
top: 20%;
max-width: 25vw;
max-height: 80vh;
position: absolute;
top: 50%;
float: left;
width: 25vw;
z-index: 3;
transform: translateY(-50%);
}
.cardBox {
margin: 0;
}
.detailImageContainer .card.backdropCard {
top: 35%;
}
&.backdropCard {
top: 35%;
}
.detailImageContainer .card.squareCard {
top: 40%;
}
&.squareCard {
top: 40%;
}
.layout-mobile & {
left: 5%;
bottom: 1rem;
max-width: 30vw;
filter: drop-shadow(0 0 0.5rem #000);
@media all and (max-width: 32em) {
left: 0;
bottom: 0;
}
&,
&.backdropCard,
&.squareCard {
top: auto;
}
}
.layout-desktop & {
left: 3.3%;
top: -80%;
width: 25vw;
// FIXME: the fixed width + max height cause the card to be cropped this needs a proper fix
max-height: none;
}
.layout-tv & {
left: 5%;
top: 50%;
width: 25vw;
transform: translateY(-50%);
}
.layout-desktop .noBackdrop .detailImageContainer,
.layout-tv .noBackdrop .detailImageContainer {
margin-top: 0;
}
.detailPagePrimaryContent {
position: relative;
}
.detailLogo {
width: 25vw;
height: 16vh;
position: absolute;
top: 10vh;
right: 25vw;
background-size: contain;
}
.noBackdrop .detailLogo,
.layout-mobile .detailLogo {
display: none;
}
@@ -767,6 +766,7 @@
.itemDetailImage {
width: 100% !important;
-webkit-box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.75);
box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.75);
}
@@ -873,6 +873,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
.recordingFields button {
margin-left: 0;
margin-right: 0.5em;
-webkit-flex-shrink: 0;
flex-shrink: 0;
}
@@ -883,7 +884,11 @@ div.itemDetailGalleryLink.defaultCardBackground {
.detailButton {
display: flex;
flex-direction: column;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
margin: 0 !important;
padding: 0.7em 0.7em !important;
@@ -911,9 +916,18 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
.detailButton-content {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
}
@@ -970,6 +984,8 @@ div.itemDetailGalleryLink.defaultCardBackground {
@media all and (max-width: 31.25em) {
.mobileDetails .itemMiscInfo {
text-align: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
}
@@ -991,8 +1007,9 @@ div.itemDetailGalleryLink.defaultCardBackground {
border-collapse: collapse;
}
.mediaInfoContent .btnCopy .material-icons {
font-size: inherit;
.layout-desktop .noBackdrop .detailPageWrapperContainer,
.layout-tv .noBackdrop .detailPageWrapperContainer {
margin-top: 3.8em;
}
.mediaInfoStream {
@@ -1003,10 +1020,6 @@ div.itemDetailGalleryLink.defaultCardBackground {
.mediaInfoStreamType {
display: block;
margin: 0.622em 0; /* copy button height compensation */
}
.layout-tv .mediaInfoStreamType {
margin: 1em 0;
}
@@ -1058,9 +1071,14 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
.mediaInfoIcons {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
margin: 1em 0;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
}
@@ -1108,6 +1126,7 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
.sectionTitleButton {
margin-left: 1.5em !important;
-webkit-flex-shrink: 0;
flex-shrink: 0;
}
@@ -1117,17 +1136,22 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
.sectionTitleIconButton {
margin-left: 1.5em !important;
-webkit-flex-shrink: 0;
flex-shrink: 0;
font-size: 84% !important;
padding: 0.5em !important;
}
.horizontalItemsContainer {
display: -webkit-box;
display: -webkit-flex;
display: flex;
}
.sectionTitleTextButton {
margin: 0 !important;
display: -webkit-inline-box !important;
display: -webkit-inline-flex !important;
display: inline-flex !important;
color: inherit !important;
}
@@ -1195,6 +1219,8 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
}
.itemsViewSettingsContainer {
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
}
@@ -1219,7 +1245,7 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
}
.itemDetailsGroup {
margin-top: 1.5em;
margin-bottom: 1.5em;
}
.trackSelections {

View File

@@ -7,8 +7,3 @@
padding-left: 0.5em;
}
}
// FIXME: background sizing for cards really needs revisited, but these are particularly terrible
#channelsTab .cardImageContainer {
background-size: contain;
}

View File

@@ -1 +0,0 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Apple</title><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 663 B

View File

@@ -8,12 +8,8 @@ class ServerConnections extends ConnectionManager {
super(...arguments);
this.localApiClient = null;
Events.on(this, 'localusersignedout', function (eventName, logoutInfo) {
Events.on(this, 'localusersignedout', function () {
setUserInfo(null, null);
if (window.NativeShell && typeof window.NativeShell.onLocalUserSignedOut === 'function') {
window.NativeShell.onLocalUserSignedOut(logoutInfo);
}
});
}
@@ -66,12 +62,7 @@ class ServerConnections extends ConnectionManager {
onLocalUserSignedIn(user) {
const apiClient = this.getApiClient(user.ServerId);
this.setLocalApiClient(apiClient);
return setUserInfo(user.Id, apiClient).then(() => {
if (window.NativeShell && typeof window.NativeShell.onLocalUserSignedIn === 'function') {
return window.NativeShell.onLocalUserSignedIn(user, apiClient.accessToken());
}
return Promise.resolve();
});
return setUserInfo(user.Id, apiClient);
}
}

View File

@@ -11,7 +11,7 @@ import datetime from '../../scripts/datetime';
import globalize from '../../scripts/globalize';
import '../../elements/emby-select/emby-select';
import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss';
import '../formdialog.css';
import template from './accessSchedule.template.html';
function getDisplayTime(hours) {

View File

@@ -90,13 +90,12 @@
.actionSheetTitle {
margin: 0.6em 0 0.7em !important;
padding: 0 0.75rem;
padding: 0 0.9em;
flex-grow: 0;
}
.actionSheetText {
margin-top: 0;
padding: 0 0.75rem;
padding: 0 1em;
flex-grow: 0;
}

View File

@@ -1,13 +1,12 @@
import escapeHtml from 'escape-html';
import dialogHelper from '../dialogHelper/dialogHelper';
import layoutManager from '../layoutManager';
import globalize from '../../scripts/globalize';
import dom from '../../scripts/dom';
import '../../elements/emby-button/emby-button';
import './actionSheet.scss';
import './actionSheet.css';
import 'material-design-icons-iconfont';
import '../../assets/css/scrollstyles.scss';
import '../../components/listview/listview.scss';
import '../../assets/css/scrollstyles.css';
import '../../components/listview/listview.css';
function getOffsets(elems) {
const results = [];
@@ -142,8 +141,8 @@ export function show(options) {
}
if (layoutManager.tv) {
html += `<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1" title="${globalize.translate('ButtonBack')}">
<span class="material-icons arrow_back" aria-hidden="true"></span>
html += `<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1">
<span class="material-icons arrow_back"></span>
</button>`;
}
@@ -157,10 +156,10 @@ export function show(options) {
}
if (options.title) {
html += '<h1 class="actionSheetTitle">' + escapeHtml(options.title) + '</h1>';
html += '<h1 class="actionSheetTitle">' + options.title + '</h1>';
}
if (options.text) {
html += '<p class="actionSheetText">' + escapeHtml(options.text) + '</p>';
html += '<p class="actionSheetText">' + options.text + '</p>';
}
let scrollerClassName = 'actionSheetScroller';
@@ -205,25 +204,25 @@ export function show(options) {
itemIcon = icons[i];
if (itemIcon) {
html += `<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ${itemIcon}" aria-hidden="true"></span>`;
html += `<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ${itemIcon}"></span>`;
} else if (renderIcon && !center) {
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" aria-hidden="true" style="visibility:hidden;"></span>';
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>';
}
html += '<div class="listItemBody actionsheetListItemBody">';
html += '<div class="listItemBodyText actionSheetItemText">';
html += escapeHtml(item.name || item.textContent || item.innerText);
html += (item.name || item.textContent || item.innerText);
html += '</div>';
if (item.secondaryText) {
html += `<div class="listItemBodyText secondary">${escapeHtml(item.secondaryText)}</div>`;
html += `<div class="listItemBodyText secondary">${item.secondaryText}</div>`;
}
html += '</div>';
if (item.asideText) {
html += `<div class="listItemAside actionSheetItemAsideText">${escapeHtml(item.asideText)}</div>`;
html += `<div class="listItemAside actionSheetItemAsideText">${item.asideText}</div>`;
}
html += '</button>';

View File

@@ -1,4 +1,3 @@
import escapeHtml from 'escape-html';
import { Events } from 'jellyfin-apiclient';
import globalize from '../scripts/globalize';
import dom from '../scripts/dom';
@@ -6,7 +5,7 @@ import * as datefns from 'date-fns';
import dfnshelper from '../scripts/dfnshelper';
import serverNotifications from '../scripts/serverNotifications';
import '../elements/emby-button/emby-button';
import './listview/listview.scss';
import './listview/listview.css';
import ServerConnections from './ServerConnections';
import alert from './alert';
@@ -24,29 +23,29 @@ import alert from './alert';
}
if (entry.UserId && entry.UserPrimaryImageTag) {
html += '<span class="listItemIcon material-icons dvr" aria-hidden="true" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, {
html += '<span class="listItemIcon material-icons dvr" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, {
type: 'Primary',
tag: entry.UserPrimaryImageTag
}) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\"></span>";
} else {
html += '<span class="listItemIcon material-icons ' + icon + '" aria-hidden="true" style="background-color:' + color + '"></span>';
html += '<span class="listItemIcon material-icons ' + icon + '" style="background-color:' + color + '"></span>';
}
html += '<div class="listItemBody three-line">';
html += '<div class="listItemBodyText">';
html += escapeHtml(entry.Name);
html += entry.Name;
html += '</div>';
html += '<div class="listItemBodyText secondary">';
html += datefns.formatRelative(Date.parse(entry.Date), Date.parse(new Date()), { locale: dfnshelper.getLocale() });
html += '</div>';
html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">';
html += escapeHtml(entry.ShortOverview || '');
html += entry.ShortOverview || '';
html += '</div>';
html += '</div>';
if (entry.Overview) {
html += `<button type="button" is="paper-icon-button-light" class="btnEntryInfo" data-id="${entry.Id}" title="${globalize.translate('Info')}">
<span class="material-icons info" aria-hidden="true"></span>
<span class="material-icons info"></span>
</button>`;
}
@@ -55,7 +54,7 @@ import alert from './alert';
return html;
}
function renderList(elem, apiClient, result) {
function renderList(elem, apiClient, result, startIndex, limit) {
elem.innerHTML = result.Items.map(function (i) {
return getEntryHtml(i, apiClient);
}).join('');
@@ -98,11 +97,11 @@ import alert from './alert';
}
instance.items = result.Items;
renderList(elem, apiClient, result);
renderList(elem, apiClient, result, startIndex, limit);
});
}
function onActivityLogUpdate(e, apiClient) {
function onActivityLogUpdate(e, apiClient, data) {
const options = this.options;
if (options && options.serverId === apiClient.serverId()) {

View File

@@ -1,4 +1,4 @@
import { appRouter } from './appRouter';
import browser from '../scripts/browser';
import dialog from './dialog/dialog';
import globalize from '../scripts/globalize';
@@ -10,16 +10,7 @@ import globalize from '../scripts/globalize';
return originalString.replace(reg, strWith);
}
function useNativeAlert() {
// webOS seems to block modals
// Tizen 2.x seems to block modals
return !browser.web0s
&& !(browser.tizenVersion && browser.tizenVersion < 3)
&& browser.tv
&& window.alert;
}
export default async function (text, title) {
export default function (text, title) {
let options;
if (typeof text === 'string') {
options = {
@@ -30,11 +21,8 @@ import globalize from '../scripts/globalize';
options = text;
}
await appRouter.ready();
if (useNativeAlert()) {
if (browser.tv && window.alert) {
alert(replaceAll(options.text || '', '<br/>', '\n'));
return Promise.resolve();
} else {
const items = [];
@@ -45,8 +33,17 @@ import globalize from '../scripts/globalize';
});
options.buttons = items;
return dialog.show(options);
return dialog.show(options).then(function (result) {
if (result === 'ok') {
return Promise.resolve();
}
return Promise.reject();
});
}
return Promise.resolve();
}
/* eslint-enable indent */

View File

@@ -1,37 +0,0 @@
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
import AlphaPicker from './alphaPicker';
type AlphaPickerProps = {
onAlphaPicked?: (e: Event) => void
};
// React compatibility wrapper component for alphaPicker.js
// eslint-disable-next-line @typescript-eslint/no-empty-function
const AlphaPickerComponent: FunctionComponent<AlphaPickerProps> = ({ onAlphaPicked = () => {} }: AlphaPickerProps) => {
const [ alphaPicker, setAlphaPicker ] = useState<AlphaPicker>();
const element = useRef<HTMLDivElement>(null);
useEffect(() => {
setAlphaPicker(new AlphaPicker({
element: element.current,
mode: 'keyboard'
}));
element.current?.addEventListener('alphavalueclicked', onAlphaPicked);
return () => {
alphaPicker?.destroy();
};
// eslint-disable-next-line react-hooks/exhaustive-deps -- Disabled for wrapper components
}, []);
return (
<div
ref={element}
className='alphaPicker align-items-center'
/>
);
};
export default AlphaPickerComponent;

View File

@@ -8,8 +8,7 @@
import focusManager from '../focusManager';
import layoutManager from '../layoutManager';
import dom from '../../scripts/dom';
import globalize from '../../scripts/globalize';
import './style.scss';
import './style.css';
import '../../elements/emby-button/paper-icon-button-light';
import 'material-design-icons-iconfont';
@@ -76,7 +75,7 @@ import 'material-design-icons-iconfont';
html += `<div class="${rowClassName}">`;
if (options.mode === 'keyboard') {
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}" aria-label="${globalize.translate('ButtonSpace')}"><span class="material-icons alphaPickerButtonIcon space_bar" aria-hidden="true"></span></button>`;
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>`;
} else {
letters = ['#'];
html += mapLetters(letters, vertical).join('');
@@ -86,7 +85,7 @@ import 'material-design-icons-iconfont';
html += mapLetters(letters, vertical).join('');
if (options.mode === 'keyboard') {
html += `<button data-value="backspace" is="paper-icon-button-light" class="${alphaPickerButtonClassName}" aria-label="${globalize.translate('ButtonBackspace')}"><span class="material-icons alphaPickerButtonIcon backspace" aria-hidden="true"></span></button>`;
html += `<button data-value="backspace" is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon backspace"></span></button>`;
html += '</div>';
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
@@ -281,16 +280,6 @@ import 'material-design-icons-iconfont';
element.removeEventListener(name, fn);
}
updateControls(query) {
if (query.NameLessThan) {
this.value('#');
} else {
this.value(query.NameStartsWith);
}
this.visible(query.SortBy.indexOf('SortName') !== -1);
}
visible(visible) {
const element = this.options.element;
element.style.visibility = visible ? 'visible' : 'hidden';

View File

@@ -1,6 +1,6 @@
import './appFooter.scss';
import './appFooter.css';
function render() {
function render(options) {
const elem = document.createElement('div');
elem.classList.add('appfooter');
@@ -10,10 +10,10 @@ function render() {
}
class appFooter {
constructor() {
constructor(options) {
const self = this;
self.element = render();
self.element = render(options);
self.add = function (elem) {
self.element.appendChild(elem);
};
@@ -33,4 +33,4 @@ class appFooter {
}
}
export default new appFooter();
export default new appFooter({});

View File

@@ -11,7 +11,6 @@ import viewManager from './viewManager/viewManager';
import Dashboard from '../scripts/clientUtils';
import ServerConnections from './ServerConnections';
import alert from './alert';
import reactControllerFactory from './reactControllerFactory';
class AppRouter {
allRoutes = [];
@@ -24,33 +23,24 @@ class AppRouter {
isDummyBackToHome;
msgTimeout;
popstateOccurred = false;
promiseShow;
resolveOnNextShow;
previousRoute = {};
/**
* Pages of "no return" (when "Go back" should behave differently, probably quitting the application).
*/
startPages = ['home', 'login', 'selectserver'];
constructor() {
// WebKit fires a popstate event on document load
// Skip it using boolean
// For Tizen 2.x
// See `page` node module
let loaded = document.readyState === 'complete';
if (!loaded) {
window.addEventListener('load', () => {
setTimeout(() => {
loaded = true;
}, 0);
});
}
window.addEventListener('popstate', () => {
if (!loaded) return;
this.popstateOccurred = true;
});
document.addEventListener('viewshow', () => this.onViewShow());
document.addEventListener('viewshow', () => {
const resolve = this.resolveOnNextShow;
if (resolve) {
this.resolveOnNextShow = null;
resolve();
}
});
this.baseRoute = window.location.href.split('?')[0].replace(this.getRequestFile(), '');
// support hashbang
@@ -59,6 +49,8 @@ class AppRouter {
this.baseRoute = this.baseRoute.substring(0, this.baseRoute.length - 1);
}
this.setBaseRoute();
// paths that start with a hashbang (i.e. /#!/page.html) get transformed to starting with //
// we need to strip one "/" for our routes to work
page('//*', (ctx) => {
@@ -66,6 +58,18 @@ class AppRouter {
});
}
/**
* @private
*/
setBaseRoute() {
let baseRoute = window.location.pathname.replace(this.getRequestFile(), '');
if (baseRoute.lastIndexOf('/') === baseRoute.length - 1) {
baseRoute = baseRoute.substring(0, baseRoute.length - 1);
}
console.debug('setting page base to ' + baseRoute);
page.base(baseRoute);
}
addRoute(path, newRoute) {
page(path, this.getHandler(newRoute));
this.allRoutes.push(newRoute);
@@ -114,24 +118,11 @@ class AppRouter {
}
}
ready() {
return this.promiseShow || Promise.resolve();
back() {
page.back();
}
async back() {
if (this.promiseShow) await this.promiseShow;
this.promiseShow = new Promise((resolve) => {
this.resolveOnNextShow = resolve;
page.back();
});
return this.promiseShow;
}
async show(path, options) {
if (this.promiseShow) await this.promiseShow;
show(path, options) {
// ensure the path does not start with '#!' since the router adds this
if (path.startsWith('#!')) {
path = path.substring(2);
@@ -151,25 +142,17 @@ class AppRouter {
}
}
this.promiseShow = new Promise((resolve) => {
return new Promise((resolve) => {
this.resolveOnNextShow = resolve;
// Schedule a call to return the promise
setTimeout(() => page.show(path, options), 0);
page.show(path, options);
});
return this.promiseShow;
}
async showDirect(path) {
if (this.promiseShow) await this.promiseShow;
this.promiseShow = new Promise((resolve) => {
showDirect(path) {
return new Promise(function(resolve) {
this.resolveOnNextShow = resolve;
// Schedule a call to return the promise
setTimeout(() => page.show(this.baseUrl() + path), 0);
page.show(this.baseUrl() + path);
});
return this.promiseShow;
}
start(options) {
@@ -358,9 +341,7 @@ class AppRouter {
this.sendRouteToViewManager(ctx, next, route, controllerFactory);
};
if (route.pageComponent) {
onInitComplete(reactControllerFactory);
} else if (route.controller) {
if (route.controller) {
import('../controllers/' + route.controller).then(onInitComplete);
} else {
onInitComplete();
@@ -392,7 +373,6 @@ class AppRouter {
fullscreen: route.fullscreen,
controllerFactory: controllerFactory,
options: {
pageComponent: route.pageComponent,
supportsThemeMedia: route.supportsThemeMedia || false,
enableMediaControl: route.enableMediaControl !== false
},
@@ -424,15 +404,6 @@ class AppRouter {
});
}
onViewShow() {
const resolve = this.resolveOnNextShow;
if (resolve) {
this.promiseShow = null;
this.resolveOnNextShow = null;
resolve();
}
}
onForcedLogoutMessageTimeout() {
const msg = this.forcedLogoutMsg;
this.forcedLogoutMsg = null;
@@ -650,17 +621,8 @@ class AppRouter {
getHandler(route) {
return (ctx, next) => {
ctx.isBack = this.popstateOccurred;
this.popstateOccurred = false;
const ignore = route.dummyRoute === true || this.previousRoute.dummyRoute === true;
this.previousRoute = route;
if (ignore) {
// Resolve 'show' promise
this.onViewShow();
return;
}
this.handleRoute(ctx, next, route);
this.popstateOccurred = false;
};
}
@@ -788,10 +750,6 @@ class AppRouter {
return '#!/list.html?type=Programs&IsAiring=true&serverId=' + options.serverId;
}
if (options.section === 'channels') {
return '#!/livetv.html?tab=2&serverId=' + options.serverId;
}
if (options.section === 'dvrschedule') {
return '#!/livetv.html?tab=4&serverId=' + options.serverId;
}

View File

@@ -1,4 +1,4 @@
import Package from '../../package.json';
import appSettings from '../scripts/settings/appSettings';
import browser from '../scripts/browser';
import { Events } from 'jellyfin-apiclient';
@@ -8,6 +8,7 @@ import globalize from '../scripts/globalize';
import profileBuilder from '../scripts/browserDeviceProfile';
const appName = 'Jellyfin Web';
const appVersion = '10.7.6';
function getBaseProfileOptions(item) {
const disableHlsVideoAudioCodecs = [];
@@ -28,37 +29,17 @@ function getBaseProfileOptions(item) {
};
}
function getDeviceProfile(item) {
function getDeviceProfile(item, options = {}) {
return new Promise(function (resolve) {
let profile;
if (window.NativeShell) {
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder, Package.version);
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder, appVersion);
} else {
const builderOpts = getBaseProfileOptions(item);
profile = profileBuilder(builderOpts);
}
const maxVideoWidth = appSettings.maxVideoWidth();
const maxTranscodingVideoWidth = maxVideoWidth < 0 ? appHost.screen()?.maxAllowedWidth : maxVideoWidth;
if (maxTranscodingVideoWidth) {
profile.TranscodingProfiles.forEach((transcodingProfile) => {
if (transcodingProfile.Type === 'Video') {
transcodingProfile.Conditions = (transcodingProfile.Conditions || []).filter((condition) => {
return condition.Property !== 'Width';
});
transcodingProfile.Conditions.push({
Condition: 'LessThanEqual',
Property: 'Width',
Value: maxTranscodingVideoWidth.toString(),
IsRequired: false
});
}
});
}
resolve(profile);
});
}
@@ -219,6 +200,7 @@ const supportedFeatures = function () {
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
features.push('exit');
} else {
features.push('exitmenu');
features.push('plugins');
}
@@ -294,7 +276,7 @@ const supportedFeatures = function () {
*/
function doExit() {
try {
if (window.NativeShell?.AppHost?.exit) {
if (window.NativeShell) {
window.NativeShell.AppHost.exit();
} else if (browser.tizen) {
tizen.application.getCurrentApplication().exit();
@@ -379,20 +361,16 @@ export const appHost = {
};
},
deviceName: function () {
return window.NativeShell?.AppHost?.deviceName
? window.NativeShell.AppHost.deviceName() : getDeviceName();
return window.NativeShell ? window.NativeShell.AppHost.deviceName() : getDeviceName();
},
deviceId: function () {
return window.NativeShell?.AppHost?.deviceId
? window.NativeShell.AppHost.deviceId() : getDeviceId();
return window.NativeShell ? window.NativeShell.AppHost.deviceId() : getDeviceId();
},
appName: function () {
return window.NativeShell?.AppHost?.appName
? window.NativeShell.AppHost.appName() : appName;
return window.NativeShell ? window.NativeShell.AppHost.appName() : appName;
},
appVersion: function () {
return window.NativeShell?.AppHost?.appVersion
? window.NativeShell.AppHost.appVersion() : Package.version;
return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion;
},
getPushTokenInfo: function () {
return {};
@@ -402,27 +380,6 @@ export const appHost = {
const att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no';
document.querySelector('meta[name=viewport]').setAttribute('content', att);
}
},
screen: () => {
let hostScreen = null;
const appHostImpl = window.NativeShell?.AppHost;
if (appHostImpl?.screen) {
hostScreen = appHostImpl.screen();
} else if (window.screen && !browser.tv) {
hostScreen = {
width: Math.floor(window.screen.width * window.devicePixelRatio),
height: Math.floor(window.screen.height * window.devicePixelRatio)
};
}
if (hostScreen) {
// Use larger dimension to account for screen orientation changes
hostScreen.maxAllowedWidth = Math.max(hostScreen.width, hostScreen.height);
}
return hostScreen;
}
};

View File

@@ -64,6 +64,7 @@ import layoutManager from './layoutManager';
candidates.push(activeElement);
}
candidates = candidates.concat(Array.from(container.querySelectorAll('.btnResume')));
candidates = candidates.concat(Array.from(container.querySelectorAll('.btnPlay')));
let focusedElement;

View File

@@ -2,12 +2,12 @@ import browser from '../../scripts/browser';
import { playbackManager } from '../playback/playbackmanager';
import dom from '../../scripts/dom';
import * as userSettings from '../../scripts/settings/userSettings';
import './backdrop.scss';
import './backdrop.css';
import ServerConnections from '../ServerConnections';
/* eslint-disable indent */
function enableAnimation() {
function enableAnimation(elem) {
if (browser.slow) {
return false;
}
@@ -47,7 +47,7 @@ import ServerConnections from '../ServerConnections';
backdropImage.classList.add('backdropImageFadeIn');
parent.appendChild(backdropImage);
if (!enableAnimation()) {
if (!enableAnimation(backdropImage)) {
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
}

View File

@@ -110,7 +110,6 @@ button::-moz-focus-inner {
.card.show-focus:not(.show-animation) .cardBox.visualCardBox,
.card.show-focus:not(.show-animation) .cardBox:not(.visualCardBox) .cardScalable {
border: 0.5em solid transparent;
border-radius: 0.7em; /* card border + card border-radius */
}
.card.show-animation:focus > .cardBox {
@@ -150,21 +149,17 @@ button::-moz-focus-inner {
left: 0.3em;
text-align: center;
vertical-align: middle;
font-size: 88%;
font-weight: 500;
width: 2em;
height: 2em;
width: 1.6em;
height: 1.6em;
border-radius: 50%;
color: #fff;
background: rgb(51, 136, 204);
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
}
.cardImageContainer {
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
border-radius: 0.2em;
display: flex;
align-items: center;
justify-content: center;
@@ -199,7 +194,6 @@ button::-moz-focus-inner {
left: 0;
right: 0;
bottom: 0;
border-radius: 0.2em;
/* Needed in case this is a button */
display: block;
@@ -227,17 +221,13 @@ button::-moz-focus-inner {
background-color: transparent;
}
.cardPadder {
position: relative; // For centering the cardImageIcon
}
.cardBox:not(.visualCardBox) .cardPadder {
border-radius: 0.2em;
background-color: #242424;
}
.blurhash-canvas {
border-radius: 0.2em;
.visualCardBox .cardContent {
border-top-left-radius: 0.2em;
border-top-right-radius: 0.2em;
}
.cardContent-shadow,
@@ -273,7 +263,7 @@ button::-moz-focus-inner {
.visualCardBox {
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
border-radius: 0.2em;
border-radius: 0.145em;
}
.innerCardFooter {
@@ -337,7 +327,6 @@ button::-moz-focus-inner {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
.innerCardFooter > .cardText {
@@ -360,8 +349,7 @@ button::-moz-focus-inner {
background-position: center center;
}
.cardTextCentered,
.cardTextCentered > .textActionButton {
.cardTextCentered {
text-align: center;
}
@@ -381,18 +369,6 @@ button::-moz-focus-inner {
color: inherit;
}
.cardPadder .cardImageIcon {
color: #111;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.cardImageContainer .cardImageIcon {
margin: auto; /* 'justify-content: center' doesn't work in Safari 10 */
}
.cardIndicators {
right: 0.225em;
top: 0.225em;
@@ -793,14 +769,6 @@ button::-moz-focus-inner {
bottom: 0;
right: 0;
user-select: none;
border-radius: 0.2em;
}
.visualCardBox .blurhash-canvas,
.visualCardBox .cardContent,
.visualCardBox .cardOverlayContainer {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.card-hoverable:hover .cardOverlayContainer {

View File

@@ -5,7 +5,6 @@
* @module components/cardBuilder/cardBuilder
*/
import escapeHtml from 'escape-html';
import datetime from '../../scripts/datetime';
import imageLoader from '../images/imageLoader';
import itemHelper from '../itemHelper';
@@ -18,9 +17,9 @@ import browser from '../../scripts/browser';
import { playbackManager } from '../playback/playbackmanager';
import itemShortcuts from '../shortcuts';
import imageHelper from '../../scripts/imagehelper';
import './card.scss';
import './card.css';
import '../../elements/emby-button/paper-icon-button-light';
import '../guide/programs.scss';
import '../guide/programs.css';
import ServerConnections from '../ServerConnections';
const enableFocusTransform = !browser.slow && !browser.edge;
@@ -146,7 +145,7 @@ import ServerConnections from '../ServerConnections';
return 100 / 14.2857142857;
}
if (screenWidth >= 1200) {
return 100 / 16.66666667;
return 100 / 16.666666666666666666;
}
if (screenWidth >= 1000) {
return 5;
@@ -482,20 +481,12 @@ import ServerConnections from '../ServerConnections';
return null;
}
/**
* @typedef {Object} CardImageUrl
* @property {string} imgUrl - Image URL.
* @property {string} blurhash - Image blurhash.
* @property {boolean} forceName - Force name.
* @property {boolean} coverImage - Use cover style.
*/
/** Get the URL of the card's image.
* @param {Object} item - Item for which to generate a card.
* @param {Object} apiClient - API client object.
* @param {Object} options - Options of the card.
* @param {string} shape - Shape of the desired image.
* @returns {CardImageUrl} Object representing the URL of the card's image.
* @returns {Object} Object representing the URL of the card's image.
*/
function getCardImageUrl(item, apiClient, options, shape) {
item = item.ProgramInfo || item;
@@ -648,7 +639,7 @@ import ServerConnections from '../ServerConnections';
/**
* Generates an index used to select the default color of a card based on a string.
* @param {?string} [str] - String to use for generating the index.
* @param {string} str - String to use for generating the index.
* @returns {number} Index of the color.
*/
function getDefaultColorIndex(str) {
@@ -735,8 +726,8 @@ import ServerConnections from '../ServerConnections';
/**
* Returns the air time text for the item based on the given times.
* @param {object} item - Item used to generate the air time text.
* @param {boolean} showAirDateTime - ISO8601 date for the start of the show.
* @param {boolean} showAirEndTime - ISO8601 date for the end of the show.
* @param {string} showAirDateTime - ISO8601 date for the start of the show.
* @param {string} showAirEndTime - ISO8601 date for the end of the show.
* @returns {string} The air time text for the item based on the given dates.
*/
function getAirTimeText(item, showAirDateTime, showAirEndTime) {
@@ -780,7 +771,6 @@ import ServerConnections from '../ServerConnections';
* @returns {string} HTML markup of the card's footer text element.
*/
function getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerClass, progressHtml, logoUrl, isOuterFooter) {
item = item.ProgramInfo || item;
let html = '';
if (logoUrl) {
@@ -791,7 +781,7 @@ import ServerConnections from '../ServerConnections';
if (isOuterFooter && options.cardLayout && layoutManager.mobile) {
if (options.cardFooterAside !== 'none') {
html += `<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu" title="${globalize.translate('ButtonMore')}"><span class="material-icons more_vert" aria-hidden="true"></span></button>`;
html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu"><span class="material-icons more_vert"></span></button>';
}
}
@@ -814,20 +804,20 @@ import ServerConnections from '../ServerConnections';
IsFolder: true
}));
} else {
lines.push(escapeHtml(item.SeriesName));
lines.push(item.SeriesName);
}
} else {
if (isUsingLiveTvNaming(item)) {
lines.push(escapeHtml(item.Name));
lines.push(item.Name);
if (!item.EpisodeTitle && !item.IndexNumber) {
if (!item.EpisodeTitle) {
titleAdded = true;
}
} else {
const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || '';
if (parentTitle || showTitle) {
lines.push(escapeHtml(parentTitle));
lines.push(parentTitle);
}
}
}
@@ -861,14 +851,10 @@ import ServerConnections from '../ServerConnections';
item.AlbumArtists[0].IsFolder = true;
lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId));
} else {
lines.push(escapeHtml(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || '')));
lines.push(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''));
}
}
if (item.ExtraType && item.ExtraType !== 'Unknown') {
lines.push(globalize.translate(item.ExtraType));
}
if (options.showItemCounts) {
lines.push(getItemCountsHtml(options, item));
}
@@ -949,13 +935,13 @@ import ServerConnections from '../ServerConnections';
}, item.ChannelName));
} else {
lines.push(escapeHtml(item.ChannelName || '') || '&nbsp;');
lines.push(item.ChannelName || '&nbsp;');
}
}
if (options.showCurrentProgram && item.Type === 'TvChannel') {
if (item.CurrentProgram) {
lines.push(escapeHtml(item.CurrentProgram.Name));
lines.push(item.CurrentProgram.Name);
} else {
lines.push('');
}
@@ -981,13 +967,13 @@ import ServerConnections from '../ServerConnections';
if (item.RecordAnyChannel) {
lines.push(globalize.translate('AllChannels'));
} else {
lines.push(escapeHtml(item.ChannelName || '') || globalize.translate('OneChannel'));
lines.push(item.ChannelName || globalize.translate('OneChannel'));
}
}
if (options.showPersonRoleOrType) {
if (item.Role) {
lines.push(globalize.translate('PersonRole', escapeHtml(item.Role)));
lines.push(globalize.translate('PersonRole', item.Role));
}
}
}
@@ -997,7 +983,7 @@ import ServerConnections from '../ServerConnections';
}
if (overlayText && showTitle) {
lines = [escapeHtml(item.Name)];
lines = [item.Name];
}
const addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile;
@@ -1032,8 +1018,6 @@ import ServerConnections from '../ServerConnections';
text = itemHelper.getDisplayName(item);
}
text = escapeHtml(text);
if (layoutManager.tv) {
return text;
}
@@ -1140,7 +1124,7 @@ import ServerConnections from '../ServerConnections';
/**
* Returns the default background class for a card based on a string.
* @param {?string} [str] - Text used to generate the background class.
* @param {string} str - Text used to generate the background class.
* @returns {string} CSS classes for default card backgrounds.
*/
export function getDefaultBackgroundClass(str) {
@@ -1312,15 +1296,15 @@ import ServerConnections from '../ServerConnections';
const btnCssClass = 'cardOverlayButton cardOverlayButton-br itemAction';
if (options.centerPlayButton) {
overlayButtons += `<button is="paper-icon-button-light" class="${btnCssClass} cardOverlayButton-centered" data-action="play" title="${globalize.translate('Play')}"><span class="material-icons cardOverlayButtonIcon play_arrow" aria-hidden="true"></span></button>`;
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayButton-centered" data-action="play"><span class="material-icons cardOverlayButtonIcon play_arrow"></span></button>';
}
if (overlayPlayButton && !item.IsPlaceHolder && (item.LocationType !== 'Virtual' || !item.MediaType || item.Type === 'Program') && item.Type !== 'Person') {
overlayButtons += `<button is="paper-icon-button-light" class="${btnCssClass}" data-action="play" title="${globalize.translate('Play')}"><span class="material-icons cardOverlayButtonIcon play_arrow" aria-hidden="true"></span></button>`;
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="play"><span class="material-icons cardOverlayButtonIcon play_arrow"></span></button>';
}
if (options.overlayMoreButton) {
overlayButtons += `<button is="paper-icon-button-light" class="${btnCssClass}" data-action="menu" title="${globalize.translate('ButtonMore')}"><span class="material-icons cardOverlayButtonIcon more_vert" aria-hidden="true"></span></button>`;
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon more_vert"></span></button>';
}
}
@@ -1347,28 +1331,15 @@ import ServerConnections from '../ServerConnections';
cardImageContainerClose = '</div>';
} else {
const cardImageContainerAriaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`;
// Don't use the IMG tag with safari because it puts a white border around it
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + cardImageContainerAriaLabelAttribute + '>') : ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction"' + cardImageContainerAriaLabelAttribute + '>');
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction">');
cardImageContainerClose = '</button>';
}
const cardScalableClass = 'cardScalable';
let cardPadderIcon = '';
// TV Channel logos are transparent so skip the placeholder to avoid overlapping
if (imgUrl && item.Type !== 'TvChannel') {
cardPadderIcon = getDefaultText(item, {
// Always use an icon
defaultCardImageIcon: 'folder',
...options
});
}
cardImageContainerOpen = `<div class="${cardBoxClass}"><div class="${cardScalableClass}"><div class="cardPadder cardPadder-${shape}">${cardPadderIcon}</div>${cardImageContainerOpen}`;
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
cardBoxClose = '</div>';
cardScalableClose = '</div>';
@@ -1425,12 +1396,10 @@ import ServerConnections from '../ServerConnections';
}
let actionAttribute;
let ariaLabelAttribute = '';
if (tagName === 'button') {
className += ' itemAction';
actionAttribute = ' data-action="' + action + '"';
ariaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`;
} else {
actionAttribute = '';
}
@@ -1445,7 +1414,7 @@ import ServerConnections from '../ServerConnections';
const mediaTypeData = item.MediaType ? (' data-mediatype="' + item.MediaType + '"') : '';
const collectionTypeData = item.CollectionType ? (' data-collectiontype="' + item.CollectionType + '"') : '';
const channelIdData = item.ChannelId ? (' data-channelid="' + item.ChannelId + '"') : '';
const pathData = item.Path ? (' data-path="' + escapeHtml(item.Path) + '"') : '';
const pathData = item.Path ? (' data-path="' + item.Path + '"') : '';
const contextData = options.context ? (' data-context="' + options.context + '"') : '';
const parentIdData = options.parentId ? (' data-parentid="' + options.parentId + '"') : '';
const startDate = item.StartDate ? (' data-startdate="' + item.StartDate.toString() + '"') : '';
@@ -1457,7 +1426,7 @@ import ServerConnections from '../ServerConnections';
additionalCardContent += getHoverMenuHtml(item, action);
}
return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + escapeHtml(prefix) + '" class="' + className + '"' + ariaLabelAttribute + '>' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
}
/**
@@ -1474,7 +1443,7 @@ import ServerConnections from '../ServerConnections';
const btnCssClass = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light';
if (playbackManager.canPlay(item)) {
html += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayFab-primary" data-action="resume"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover play_arrow" aria-hidden="true"></span></button>';
html += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayFab-primary" data-action="resume"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover play_arrow"></span></button>';
}
html += '<div class="cardOverlayButton-br flex">';
@@ -1484,7 +1453,7 @@ import ServerConnections from '../ServerConnections';
if (itemHelper.canMarkPlayed(item)) {
/* eslint-disable-next-line @babel/no-unused-expressions */
import('../../elements/emby-playstatebutton/emby-playstatebutton');
html += '<button is="emby-playstatebutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover check" aria-hidden="true"></span></button>';
html += '<button is="emby-playstatebutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover check"></span></button>';
}
if (itemHelper.canRate(item)) {
@@ -1492,10 +1461,10 @@ import ServerConnections from '../ServerConnections';
/* eslint-disable-next-line @babel/no-unused-expressions */
import('../../elements/emby-ratingbutton/emby-ratingbutton');
html += '<button is="emby-ratingbutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover favorite" aria-hidden="true"></span></button>';
html += '<button is="emby-ratingbutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover favorite"></span></button>';
}
html += `<button is="paper-icon-button-light" class="${btnCssClass}" data-action="menu" title="${globalize.translate('ButtonMore')}"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover more_vert" aria-hidden="true"></span></button>`;
html += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover more_vert"></span></button>';
html += '</div>';
html += '</div>';
@@ -1510,44 +1479,39 @@ import ServerConnections from '../ServerConnections';
*/
export function getDefaultText(item, options) {
if (item.CollectionType) {
return '<span class="cardImageIcon material-icons ' + imageHelper.getLibraryIcon(item.CollectionType) + '" aria-hidden="true"></span>';
return '<span class="cardImageIcon material-icons ' + imageHelper.getLibraryIcon(item.CollectionType) + '"></span>';
}
switch (item.Type) {
case 'MusicAlbum':
return '<span class="cardImageIcon material-icons album" aria-hidden="true"></span>';
return '<span class="cardImageIcon material-icons album"></span>';
case 'MusicArtist':
case 'Person':
return '<span class="cardImageIcon material-icons person" aria-hidden="true"></span>';
return '<span class="cardImageIcon material-icons person"></span>';
case 'Audio':
return '<span class="cardImageIcon material-icons audiotrack" aria-hidden="true"></span>';
return '<span class="cardImageIcon material-icons audiotrack"></span>';
case 'Movie':
return '<span class="cardImageIcon material-icons movie" aria-hidden="true"></span>';
case 'Episode':
return '<span class="cardImageIcon material-icons movie"></span>';
case 'Series':
return '<span class="cardImageIcon material-icons tv" aria-hidden="true"></span>';
case 'Program':
return '<span class="cardImageIcon material-icons live_tv" aria-hidden="true"></span>';
return '<span class="cardImageIcon material-icons tv"></span>';
case 'Book':
return '<span class="cardImageIcon material-icons book" aria-hidden="true"></span>';
return '<span class="cardImageIcon material-icons book"></span>';
case 'Folder':
return '<span class="cardImageIcon material-icons folder" aria-hidden="true"></span>';
return '<span class="cardImageIcon material-icons folder"></span>';
case 'BoxSet':
return '<span class="cardImageIcon material-icons collections" aria-hidden="true"></span>';
return '<span class="cardImageIcon material-icons collections"></span>';
case 'Playlist':
return '<span class="cardImageIcon material-icons view_list" aria-hidden="true"></span>';
case 'Photo':
return '<span class="cardImageIcon material-icons photo" aria-hidden="true"></span>';
return '<span class="cardImageIcon material-icons view_list"></span>';
case 'PhotoAlbum':
return '<span class="cardImageIcon material-icons photo_album" aria-hidden="true"></span>';
return '<span class="cardImageIcon material-icons photo_album"></span>';
}
if (options?.defaultCardImageIcon) {
return '<span class="cardImageIcon material-icons ' + options.defaultCardImageIcon + '" aria-hidden="true"></span>';
if (options && options.defaultCardImageIcon) {
return '<span class="cardImageIcon material-icons ' + options.defaultCardImageIcon + '"></span>';
}
const defaultName = isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item);
return '<div class="cardText cardDefaultText">' + escapeHtml(defaultName) + '</div>';
return '<div class="cardText cardDefaultText">' + defaultName + '</div>';
}
/**
@@ -1640,7 +1604,7 @@ import ServerConnections from '../ServerConnections';
indicatorsElem = ensureIndicators(card, indicatorsElem);
indicatorsElem.appendChild(playedIndicator);
}
playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check" aria-hidden="true"></span>';
playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check"></span>';
} else {
playedIndicator = card.querySelector('.playedIndicator');
if (playedIndicator) {
@@ -1723,7 +1687,7 @@ import ServerConnections from '../ServerConnections';
const icon = cell.querySelector('.timerIndicator');
if (!icon) {
const indicatorsElem = ensureIndicators(cell);
indicatorsElem.insertAdjacentHTML('beforeend', '<span class="material-icons timerIndicator indicatorIcon fiber_manual_record" aria-hidden="true"></span>');
indicatorsElem.insertAdjacentHTML('beforeend', '<span class="material-icons timerIndicator indicatorIcon fiber_manual_record"></span>');
}
cell.setAttribute('data-timerid', newTimerId);
}

View File

@@ -5,7 +5,6 @@
* @module components/cardBuilder/chaptercardbuilder
*/
import escapeHtml from 'escape-html';
import datetime from '../../scripts/datetime';
import imageLoader from '../images/imageLoader';
import layoutManager from '../layoutManager';
@@ -95,11 +94,11 @@ import ServerConnections from '../ServerConnections';
let cardImageContainer = imgUrl ? (`<div class="${cardImageContainerClass} lazy" data-src="${imgUrl}">`) : (`<div class="${cardImageContainerClass}">`);
if (!imgUrl) {
cardImageContainer += '<span class="material-icons cardImageIcon local_movies" aria-hidden="true"></span>';
cardImageContainer += '<span class="material-icons cardImageIcon local_movies"></span>';
}
let nameHtml = '';
nameHtml += `<div class="cardText">${escapeHtml(chapter.Name)}</div>`;
nameHtml += `<div class="cardText">${chapter.Name}</div>`;
nameHtml += `<div class="cardText">${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}</div>`;
const cardBoxCssClass = 'cardBox';

View File

@@ -1,4 +1,3 @@
import escapeHtml from 'escape-html';
import dom from '../../scripts/dom';
import dialogHelper from '../dialogHelper/dialogHelper';
import loading from '../loading/loading';
@@ -7,9 +6,9 @@ import actionsheet from '../actionSheet/actionSheet';
import '../../elements/emby-input/emby-input';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-button/emby-button';
import '../listview/listview.scss';
import '../listview/listview.css';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
import '../formdialog.css';
import ServerConnections from '../ServerConnections';
export default class channelMapper {
@@ -30,7 +29,7 @@ export default class channelMapper {
}).then(mapping => {
const listItem = dom.parentWithClass(button, 'listItem');
button.setAttribute('data-providerid', mapping.ProviderChannelId);
listItem.querySelector('.secondary').innerText = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName);
listItem.querySelector('.secondary').innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName);
loading.hide();
});
}
@@ -73,20 +72,20 @@ export default class channelMapper {
function getTunerChannelHtml(channel, providerName) {
let html = '';
html += '<div class="listItem">';
html += '<span class="material-icons listItemIcon dvr" aria-hidden="true"></span>';
html += '<span class="material-icons listItemIcon dvr"></span>';
html += '<div class="listItemBody two-line">';
html += '<h3 class="listItemBodyText">';
html += escapeHtml(channel.Name);
html += channel.Name;
html += '</h3>';
html += '<div class="secondary listItemBodyText">';
if (channel.ProviderChannelName) {
html += escapeHtml(getMappingSecondaryName(channel, providerName));
html += getMappingSecondaryName(channel, providerName);
}
html += '</div>';
html += '</div>';
html += `<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="${channel.Id}" data-providerid="${channel.ProviderChannelId}"><span class="material-icons mode_edit" aria-hidden="true"></span></button>`;
html += `<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="${channel.Id}" data-providerid="${channel.ProviderChannelId}"><span class="material-icons mode_edit"></span></button>`;
return html += '</div>';
}
@@ -128,7 +127,7 @@ export default class channelMapper {
let html = '';
const title = globalize.translate('MapChannels');
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';

View File

@@ -1,4 +1,3 @@
import escapeHtml from 'escape-html';
import dom from '../../scripts/dom';
import dialogHelper from '../dialogHelper/dialogHelper';
import loading from '../loading/loading';
@@ -11,7 +10,7 @@ import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-input/emby-input';
import '../../elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
import '../formdialog.css';
import '../../assets/css/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
@@ -113,7 +112,7 @@ import toast from '../toast/toast';
html += `<option value="">${globalize.translate('OptionNew')}</option>`;
html += result.Items.map(i => {
return `<option value="${i.Id}">${escapeHtml(i.Name)}</option>`;
return `<option value="${i.Id}">${i.Name}</option>`;
});
select.innerHTML = html;
@@ -230,7 +229,7 @@ import toast from '../toast/toast';
const title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';

View File

@@ -1,4 +1,3 @@
import { appRouter } from '../appRouter';
import browser from '../../scripts/browser';
import dialog from '../dialog/dialog';
import globalize from '../../scripts/globalize';
@@ -7,16 +6,7 @@ function replaceAll(str, find, replace) {
return str.split(find).join(replace);
}
function useNativeConfirm() {
// webOS seems to block modals
// Tizen 2.x seems to block modals
return !browser.web0s
&& !(browser.tizenVersion && browser.tizenVersion < 3)
&& browser.tv
&& window.confirm;
}
async function nativeConfirm(options) {
function nativeConfirm(options) {
if (typeof options === 'string') {
options = {
title: '',
@@ -25,7 +15,6 @@ async function nativeConfirm(options) {
}
const text = replaceAll(options.text || '', '<br/>', '\n');
await appRouter.ready();
const result = window.confirm(text);
if (result) {
@@ -35,7 +24,7 @@ async function nativeConfirm(options) {
}
}
async function customConfirm(text, title) {
function customConfirm(text, title) {
let options;
if (typeof text === 'string') {
options = {
@@ -62,8 +51,6 @@ async function customConfirm(text, title) {
options.buttons = items;
await appRouter.ready();
return dialog.show(options).then(result => {
if (result === 'ok') {
return Promise.resolve();
@@ -73,6 +60,6 @@ async function customConfirm(text, title) {
});
}
const confirm = useNativeConfirm() ? nativeConfirm : customConfirm;
const confirm = browser.tv && window.confirm ? nativeConfirm : customConfirm;
export default confirm;

View File

@@ -1,58 +0,0 @@
import React, { FunctionComponent } from 'react';
import datetime from '../../../scripts/datetime';
import globalize from '../../../scripts/globalize';
const createButtonElement = (index: number) => ({
__html: `<button
type='button'
is='paper-icon-button-light'
class='btnDelete listItemButton'
data-index='${index}'
>
<span class='material-icons delete' aria-hidden='true' />
</button>`
});
type IProps = {
index: number;
Id: number;
DayOfWeek?: string;
StartHour?: number ;
EndHour?: number;
}
function getDisplayTime(hours = 0) {
let minutes = 0;
const pct = hours % 1;
if (pct) {
minutes = Math.floor(60 * pct);
}
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
}
const AccessScheduleList: FunctionComponent<IProps> = ({index, DayOfWeek, StartHour, EndHour}: IProps) => {
return (
<div
className='liSchedule listItem'
data-day={ DayOfWeek}
data-start={ StartHour}
data-end={ EndHour}
>
<div className='listItemBody two-line'>
<h3 className='listItemBodyText'>
{globalize.translate(DayOfWeek)}
</h3>
<div className='listItemBodyText secondary'>
{getDisplayTime(StartHour) + ' - ' + getDisplayTime(EndHour)}
</div>
</div>
<div
dangerouslySetInnerHTML={createButtonElement(index)}
/>
</div>
);
};
export default AccessScheduleList;

View File

@@ -1,36 +0,0 @@
import React, { FunctionComponent } from 'react';
const createButtonElement = (tag?: string) => ({
__html: `<button
type='button'
is='paper-icon-button-light'
class='blockedTag btnDeleteTag listItemButton'
data-tag='${tag}'
>
<span class='material-icons delete' aria-hidden='true' />
</button>`
});
type IProps = {
tag?: string;
}
const BlockedTagList: FunctionComponent<IProps> = ({tag}: IProps) => {
return (
<div className='paperList'>
<div className='listItem'>
<div className='listItemBody'>
<h3 className='listItemBodyText'>
{tag}
</h3>
</div>
<div
dangerouslySetInnerHTML={createButtonElement(tag)}
/>
</div>
</div>
);
};
export default BlockedTagList;

View File

@@ -1,32 +0,0 @@
import React, { FunctionComponent } from 'react';
import globalize from '../../../scripts/globalize';
const createButtonElement = ({ type, className, title }: { type?: string, className?: string, title?: string }) => ({
__html: `<button
is="emby-button"
type="${type}"
class="${className}"
>
<span>${title}</span>
</button>`
});
type IProps = {
type?: string;
className?: string;
title?: string
}
const ButtonElement: FunctionComponent<IProps> = ({ type, className, title }: IProps) => {
return (
<div
dangerouslySetInnerHTML={createButtonElement({
type: type,
className: className,
title: globalize.translate(title)
})}
/>
);
};
export default ButtonElement;

View File

@@ -1,36 +0,0 @@
import React, { FunctionComponent } from 'react';
import globalize from '../../../scripts/globalize';
const createCheckBoxElement = ({ labelClassName, type, className, title }: { labelClassName?: string, type?: string, className?: string, title?: string }) => ({
__html: `<label class="${labelClassName}">
<input
is="emby-checkbox"
type="${type}"
class="${className}"
/>
<span>${title}</span>
</label>`
});
type IProps = {
labelClassName?: string;
type?: string;
className?: string;
title?: string
}
const CheckBoxElement: FunctionComponent<IProps> = ({ labelClassName, type, className, title }: IProps) => {
return (
<div
className='sectioncheckbox'
dangerouslySetInnerHTML={createCheckBoxElement({
labelClassName: labelClassName ? labelClassName : '',
type: type,
className: className,
title: globalize.translate(title)
})}
/>
);
};
export default CheckBoxElement;

View File

@@ -1,41 +0,0 @@
import escapeHtml from 'escape-html';
import React, { FunctionComponent } from 'react';
type IProps = {
className?: string;
Name?: string;
Id?: string;
ItemType?: string;
AppName?: string;
checkedAttribute?: string;
}
const createCheckBoxElement = ({className, Name, dataAttributes, AppName, checkedAttribute}: {className?: string, Name?: string, dataAttributes?: string, AppName?: string, checkedAttribute?: string}) => ({
__html: `<label>
<input
type="checkbox"
is="emby-checkbox"
class="${className}"
${dataAttributes} ${checkedAttribute}
/>
<span>${escapeHtml(Name || '')} ${AppName}</span>
</label>`
});
const CheckBoxListItem: FunctionComponent<IProps> = ({className, Name, Id, ItemType, AppName, checkedAttribute}: IProps) => {
return (
<div
className='sectioncheckbox'
dangerouslySetInnerHTML={createCheckBoxElement({
className: className,
Name: Name,
dataAttributes: ItemType ? `data-itemtype='${ItemType}'` : `data-id='${Id}'`,
AppName: AppName ? `- ${AppName}` : '',
checkedAttribute: checkedAttribute
})}
/>
);
};
export default CheckBoxListItem;

View File

@@ -1,34 +0,0 @@
import React, { FunctionComponent } from 'react';
import globalize from '../../../scripts/globalize';
const createInputElement = ({ type, id, label, options }: { type?: string, id?: string, label?: string, options?: string }) => ({
__html: `<input
is="emby-input"
type="${type}"
id="${id}"
label="${label}"
${options}
/>`
});
type IProps = {
type?: string;
id?: string;
label?: string;
options?: string
}
const InputElement: FunctionComponent<IProps> = ({ type, id, label, options }: IProps) => {
return (
<div
dangerouslySetInnerHTML={createInputElement({
type: type,
id: id,
label: globalize.translate(label),
options: options ? options : ''
})}
/>
);
};
export default InputElement;

View File

@@ -1,30 +0,0 @@
import React, { FunctionComponent } from 'react';
import globalize from '../../../scripts/globalize';
type IProps = {
title?: string;
className?: string;
}
const createLinkElement = ({ className, title }: IProps) => ({
__html: `<a
is="emby-linkbutton"
class="${className}"
href='#'
>
${title}
</a>`
});
const LinkEditUserPreferences: FunctionComponent<IProps> = ({ className, title }: IProps) => {
return (
<div
dangerouslySetInnerHTML={createLinkElement({
className: className,
title: globalize.translate(title)
})}
/>
);
};
export default LinkEditUserPreferences;

View File

@@ -1,50 +0,0 @@
import React, { FunctionComponent } from 'react';
import globalize from '../../../scripts/globalize';
type IProps = {
activeTab: string;
}
const createLinkElement = (activeTab: string) => ({
__html: `<a href="#"
is="emby-linkbutton"
data-role="button"
class="${activeTab === 'useredit' ? 'ui-btn-active' : ''}"
onclick="Dashboard.navigate('useredit.html', true);">
${globalize.translate('Profile')}
</a>
<a href="#"
is="emby-linkbutton"
data-role="button"
class="${activeTab === 'userlibraryaccess' ? 'ui-btn-active' : ''}"
onclick="Dashboard.navigate('userlibraryaccess.html', true);">
${globalize.translate('TabAccess')}
</a>
<a href="#"
is="emby-linkbutton"
data-role="button"
class="${activeTab === 'userparentalcontrol' ? 'ui-btn-active' : ''}"
onclick="Dashboard.navigate('userparentalcontrol.html', true);">
${globalize.translate('TabParentalControl')}
</a>
<a href="#"
is="emby-linkbutton"
data-role="button"
class="${activeTab === 'userpassword' ? 'ui-btn-active' : ''}"
onclick="Dashboard.navigate('userpassword.html', true);">
${globalize.translate('HeaderPassword')}
</a>`
});
const SectionTabs: FunctionComponent<IProps> = ({activeTab}: IProps) => {
return (
<div
data-role='controlgroup'
data-type='horizontal'
className='localnav'
dangerouslySetInnerHTML={createLinkElement(activeTab)}
/>
);
};
export default SectionTabs;

View File

@@ -1,34 +0,0 @@
import React, { FunctionComponent } from 'react';
import globalize from '../../../scripts/globalize';
type IProps = {
title: string;
className?: string;
icon: string,
}
const createButtonElement = ({ className, title, icon }: { className?: string, title: string, icon: string }) => ({
__html: `<button
is="emby-button"
type="button"
class="${className}"
style="margin-left:1em;"
title="${title}"
>
<span class="material-icons ${icon}" aria-hidden="true"></span>
</button>`
});
const SectionTitleButtonElement: FunctionComponent<IProps> = ({ className, title, icon }: IProps) => {
return (
<div
dangerouslySetInnerHTML={createButtonElement({
className: className,
title: globalize.translate(title),
icon: icon
})}
/>
);
};
export default SectionTitleButtonElement;

View File

@@ -1,34 +0,0 @@
import React, { FunctionComponent } from 'react';
import globalize from '../../../scripts/globalize';
const createLinkElement = ({ className, title, href }: { className?: string, title?: string, href?: string }) => ({
__html: `<a
is="emby-linkbutton"
rel="noopener noreferrer"
class="${className}"
target="_blank"
href="${href}"
>
${title}
</a>`
});
type IProps = {
title?: string;
className?: string;
url?: string
}
const SectionTitleLinkElement: FunctionComponent<IProps> = ({ className, title, url }: IProps) => {
return (
<div
dangerouslySetInnerHTML={createLinkElement({
className: className,
title: globalize.translate(title),
href: url
})}
/>
);
};
export default SectionTitleLinkElement;

View File

@@ -1,44 +0,0 @@
import escapeHtml from 'escape-html';
import React, { FunctionComponent } from 'react';
import globalize from '../../../scripts/globalize';
const createSelectElement = ({ className, label, option }: { className?: string, label: string, option: string[] }) => ({
__html: `<select
class="${className}"
is="emby-select"
label="${label}"
>
${option}
</select>`
});
type ProvidersArr = {
Name?: string;
Id?: string;
}
type IProps = {
className?: string;
label?: string;
currentProviderId: string;
providers: ProvidersArr[]
}
const SelectElement: FunctionComponent<IProps> = ({ className, label, currentProviderId, providers }: IProps) => {
const renderOption = providers.map((provider) => {
const selected = provider.Id === currentProviderId || providers.length < 2 ? ' selected' : '';
return '<option value="' + provider.Id + '"' + selected + '>' + escapeHtml(provider.Name) + '</option>';
});
return (
<div
dangerouslySetInnerHTML={createSelectElement({
className: className,
label: globalize.translate(label),
option: renderOption
})}
/>
);
};
export default SelectElement;

View File

@@ -1,47 +0,0 @@
import escapeHtml from 'escape-html';
import React, { FunctionComponent } from 'react';
import globalize from '../../../scripts/globalize';
const createSelectElement = ({ className, label, option }: { className?: string, label: string, option: string }) => ({
__html: `<select
class="${className}"
is="emby-select"
label="${label}"
>
<option value=''></option>
${option}
</select>`
});
type RatingsArr = {
Name: string;
Value: number;
}
type IProps = {
className?: string;
label?: string;
parentalRatings: RatingsArr[];
}
const SelectMaxParentalRating: FunctionComponent<IProps> = ({ className, label, parentalRatings }: IProps) => {
const renderOption = () => {
let content = '';
for (const rating of parentalRatings) {
content += `<option value='${rating.Value}'>${escapeHtml(rating.Name)}</option>`;
}
return content;
};
return (
<div
dangerouslySetInnerHTML={createSelectElement({
className: className,
label: globalize.translate(label),
option: renderOption()
})}
/>
);
};
export default SelectMaxParentalRating;

View File

@@ -1,35 +0,0 @@
import React, { FunctionComponent } from 'react';
import globalize from '../../../scripts/globalize';
const createSelectElement = ({ className, id, label }: { className?: string, id?: string, label: string }) => ({
__html: `<select
class="${className}"
is="emby-select"
id="${id}"
label="${label}"
>
<option value='CreateAndJoinGroups'>${globalize.translate('LabelSyncPlayAccessCreateAndJoinGroups')}</option>
<option value='JoinGroups'>${globalize.translate('LabelSyncPlayAccessJoinGroups')}</option>
<option value='None'>${globalize.translate('LabelSyncPlayAccessNone')}</option>
</select>`
});
type IProps = {
className?: string;
id?: string;
label?: string
}
const SelectSyncPlayAccessElement: FunctionComponent<IProps> = ({ className, id, label }: IProps) => {
return (
<div
dangerouslySetInnerHTML={createSelectElement({
className: className,
id: id,
label: globalize.translate(label)
})}
/>
);
};
export default SelectSyncPlayAccessElement;

View File

@@ -1,101 +0,0 @@
import type { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, { FunctionComponent } from 'react';
import { formatDistanceToNow } from 'date-fns';
import { getLocaleWithSuffix } from '../../../scripts/dfnshelper';
import globalize from '../../../scripts/globalize';
import cardBuilder from '../../cardbuilder/cardBuilder';
const createLinkElement = ({ user, renderImgUrl }: { user: UserDto, renderImgUrl: string }) => ({
__html: `<a
is="emby-linkbutton"
class="cardContent"
href="#!/useredit.html?userId=${user.Id}"
>
${renderImgUrl}
</a>`
});
const createButtonElement = () => ({
__html: `<button
is="paper-icon-button-light"
type="button"
class="btnUserMenu flex-shrink-zero"
>
<span class="material-icons more_vert" aria-hidden="true"></span>
</button>`
});
type IProps = {
user?: UserDto;
}
const getLastSeenText = (lastActivityDate?: string | null) => {
if (lastActivityDate) {
return globalize.translate('LastSeen', formatDistanceToNow(Date.parse(lastActivityDate), getLocaleWithSuffix()));
}
return '';
};
const UserCardBox: FunctionComponent<IProps> = ({ user = {} }: IProps) => {
let cssClass = 'card squareCard scalableCard squareCard-scalable';
if (user.Policy?.IsDisabled) {
cssClass += ' grayscale';
}
let imgUrl;
if (user.PrimaryImageTag && user.Id) {
imgUrl = window.ApiClient.getUserImageUrl(user.Id, {
width: 300,
tag: user.PrimaryImageTag,
type: 'Primary'
});
}
let imageClass = 'cardImage';
if (user.Policy?.IsDisabled) {
imageClass += ' disabledUser';
}
const lastSeen = getLastSeenText(user.LastActivityDate);
const renderImgUrl = imgUrl ?
`<div class='${imageClass}' style='background-image:url(${imgUrl})'></div>` :
`<div class='${imageClass} ${cardBuilder.getDefaultBackgroundClass(user.Name)} flex align-items-center justify-content-center'>
<span class='material-icons cardImageIcon person' aria-hidden='true'></span>
</div>`;
return (
<div data-userid={user.Id} className={cssClass}>
<div className='cardBox visualCardBox'>
<div className='cardScalable visualCardBox-cardScalable'>
<div className='cardPadder cardPadder-square'></div>
<div
dangerouslySetInnerHTML={createLinkElement({
user: user,
renderImgUrl: renderImgUrl
})}
/>
</div>
<div className='cardFooter visualCardBox-cardFooter'>
<div className='cardText flex align-items-center'>
<div className='flex-grow' style={{overflow: 'hidden', textOverflow: 'ellipsis'}}>
{user.Name}
</div>
<div
dangerouslySetInnerHTML={createButtonElement()}
/>
</div>
<div className='cardText cardText-secondary'>
{lastSeen != '' ? lastSeen : ''}
</div>
</div>
</div>
</div>
);
};
export default UserCardBox;

View File

@@ -1,310 +0,0 @@
import type { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, { FunctionComponent, useCallback, useEffect, useRef } from 'react';
import Dashboard from '../../../scripts/clientUtils';
import globalize from '../../../scripts/globalize';
import LibraryMenu from '../../../scripts/libraryMenu';
import confirm from '../../confirm/confirm';
import loading from '../../loading/loading';
import toast from '../../toast/toast';
import ButtonElement from './ButtonElement';
import CheckBoxElement from './CheckBoxElement';
import InputElement from './InputElement';
type IProps = {
userId: string;
}
const UserPasswordForm: FunctionComponent<IProps> = ({userId}: IProps) => {
const element = useRef<HTMLDivElement>(null);
const loadUser = useCallback(() => {
const page = element.current;
if (!page) {
console.error('Unexpected null reference');
return;
}
window.ApiClient.getUser(userId).then(function (user) {
Dashboard.getCurrentUser().then(function (loggedInUser: UserDto) {
if (!user.Policy) {
throw new Error('Unexpected null user.Policy');
}
if (!user.Configuration) {
throw new Error('Unexpected null user.Configuration');
}
LibraryMenu.setTitle(user.Name);
let showLocalAccessSection = false;
if (user.HasConfiguredPassword) {
(page.querySelector('.btnResetPassword') as HTMLDivElement).classList.remove('hide');
(page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.remove('hide');
showLocalAccessSection = true;
} else {
(page.querySelector('.btnResetPassword') as HTMLDivElement).classList.add('hide');
(page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.add('hide');
}
if (loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess) {
(page.querySelector('.passwordSection') as HTMLDivElement).classList.remove('hide');
} else {
(page.querySelector('.passwordSection') as HTMLDivElement).classList.add('hide');
}
if (showLocalAccessSection && (loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) {
(page.querySelector('.localAccessSection') as HTMLDivElement).classList.remove('hide');
} else {
(page.querySelector('.localAccessSection') as HTMLDivElement).classList.add('hide');
}
const txtEasyPassword = page.querySelector('#txtEasyPassword') as HTMLInputElement;
txtEasyPassword.value = '';
if (user.HasConfiguredEasyPassword) {
txtEasyPassword.placeholder = '******';
(page.querySelector('.btnResetEasyPassword') as HTMLDivElement).classList.remove('hide');
} else {
txtEasyPassword.removeAttribute('placeholder');
txtEasyPassword.placeholder = '';
(page.querySelector('.btnResetEasyPassword') as HTMLDivElement).classList.add('hide');
}
const chkEnableLocalEasyPassword = page.querySelector('.chkEnableLocalEasyPassword') as HTMLInputElement;
chkEnableLocalEasyPassword.checked = user.Configuration.EnableLocalPassword || false;
import('../../autoFocuser').then(({default: autoFocuser}) => {
autoFocuser.autoFocus(page);
});
});
});
(page.querySelector('#txtCurrentPassword') as HTMLInputElement).value = '';
(page.querySelector('#txtNewPassword') as HTMLInputElement).value = '';
(page.querySelector('#txtNewPasswordConfirm') as HTMLInputElement).value = '';
}, [userId]);
useEffect(() => {
const page = element.current;
if (!page) {
console.error('Unexpected null reference');
return;
}
loadUser();
const onSubmit = (e: Event) => {
if ((page.querySelector('#txtNewPassword') as HTMLInputElement).value != (page.querySelector('#txtNewPasswordConfirm') as HTMLInputElement).value) {
toast(globalize.translate('PasswordMatchError'));
} else {
loading.show();
savePassword();
}
e.preventDefault();
return false;
};
const savePassword = () => {
let currentPassword = (page.querySelector('#txtCurrentPassword') as HTMLInputElement).value;
const newPassword = (page.querySelector('#txtNewPassword') as HTMLInputElement).value;
if ((page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.contains('hide')) {
// Firefox does not respect autocomplete=off, so clear it if the field is supposed to be hidden (and blank)
// This should only happen when user.HasConfiguredPassword is false, but this information is not passed on
currentPassword = '';
}
window.ApiClient.updateUserPassword(userId, currentPassword, newPassword).then(function () {
loading.hide();
toast(globalize.translate('PasswordSaved'));
loadUser();
}, function () {
loading.hide();
Dashboard.alert({
title: globalize.translate('HeaderLoginFailure'),
message: globalize.translate('MessageInvalidUser')
});
});
};
const onLocalAccessSubmit = (e: Event) => {
loading.show();
saveEasyPassword();
e.preventDefault();
return false;
};
const saveEasyPassword = () => {
const easyPassword = (page.querySelector('#txtEasyPassword') as HTMLInputElement).value;
if (easyPassword) {
window.ApiClient.updateEasyPassword(userId, easyPassword).then(function () {
onEasyPasswordSaved();
});
} else {
onEasyPasswordSaved();
}
};
const onEasyPasswordSaved = () => {
window.ApiClient.getUser(userId).then(function (user) {
if (!user.Configuration) {
throw new Error('Unexpected null user.Configuration');
}
if (!user.Id) {
throw new Error('Unexpected null user.Id');
}
user.Configuration.EnableLocalPassword = (page.querySelector('.chkEnableLocalEasyPassword') as HTMLInputElement).checked;
window.ApiClient.updateUserConfiguration(user.Id, user.Configuration).then(function () {
loading.hide();
toast(globalize.translate('SettingsSaved'));
loadUser();
});
});
};
const resetEasyPassword = () => {
const msg = globalize.translate('PinCodeResetConfirmation');
confirm(msg, globalize.translate('HeaderPinCodeReset')).then(function () {
loading.show();
window.ApiClient.resetEasyPassword(userId).then(function () {
loading.hide();
Dashboard.alert({
message: globalize.translate('PinCodeResetComplete'),
title: globalize.translate('HeaderPinCodeReset')
});
loadUser();
});
});
};
const resetPassword = () => {
const msg = globalize.translate('PasswordResetConfirmation');
confirm(msg, globalize.translate('ResetPassword')).then(function () {
loading.show();
window.ApiClient.resetUserPassword(userId).then(function () {
loading.hide();
Dashboard.alert({
message: globalize.translate('PasswordResetComplete'),
title: globalize.translate('ResetPassword')
});
loadUser();
});
});
};
(page.querySelector('.updatePasswordForm') as HTMLFormElement).addEventListener('submit', onSubmit);
(page.querySelector('.localAccessForm') as HTMLFormElement).addEventListener('submit', onLocalAccessSubmit);
(page.querySelector('.btnResetEasyPassword') as HTMLButtonElement).addEventListener('click', resetEasyPassword);
(page.querySelector('.btnResetPassword') as HTMLButtonElement).addEventListener('click', resetPassword);
}, [loadUser, userId]);
return (
<div ref={element}>
<form
className='updatePasswordForm passwordSection hide'
style={{margin: '0 auto 2em'}}
>
<div className='detailSection'>
<div id='fldCurrentPassword' className='inputContainer hide'>
<InputElement
type='password'
id='txtCurrentPassword'
label='LabelCurrentPassword'
options={'autoComplete="off"'}
/>
</div>
<div className='inputContainer'>
<InputElement
type='password'
id='txtNewPassword'
label='LabelNewPassword'
options={'autoComplete="off"'}
/>
</div>
<div className='inputContainer'>
<InputElement
type='password'
id='txtNewPasswordConfirm'
label='LabelNewPasswordConfirm'
options={'autoComplete="off"'}
/>
</div>
<br />
<div>
<ButtonElement
type='submit'
className='raised button-submit block'
title='Save'
/>
<ButtonElement
type='button'
className='raised btnResetPassword button-cancel block hide'
title='ResetPassword'
/>
</div>
</div>
</form>
<br />
<form
className='localAccessForm localAccessSection'
style={{margin: '0 auto'}}
>
<div className='detailSection'>
<div className='detailSectionHeader'>
{globalize.translate('HeaderEasyPinCode')}
</div>
<br />
<div>
{globalize.translate('EasyPasswordHelp')}
</div>
<br />
<div className='inputContainer'>
<InputElement
type='number'
id='txtEasyPassword'
label='LabelEasyPinCode'
options={'autoComplete="off" pattern="[0-9]*" step="1" maxlength="5"'}
/>
</div>
<br />
<div className='checkboxContainer checkboxContainer-withDescription'>
<CheckBoxElement
type='checkbox'
className='chkEnableLocalEasyPassword'
title='LabelInNetworkSignInWithEasyPassword'
/>
<div className='fieldDescription checkboxFieldDescription'>
{globalize.translate('LabelInNetworkSignInWithEasyPasswordHelp')}
</div>
</div>
<div>
<ButtonElement
type='submit'
className='raised button-submit block'
title='Save'
/>
<ButtonElement
type='button'
className='raised btnResetEasyPassword button-cancel block hide'
title='ButtonResetEasyPassword'
/>
</div>
</div>
</form>
</div>
);
};
export default UserPasswordForm;

View File

@@ -1,5 +1,3 @@
import DOMPurify from 'dompurify';
import escapeHtml from 'escape-html';
import dialogHelper from '../dialogHelper/dialogHelper';
import dom from '../../scripts/dom';
import layoutManager from '../layoutManager';
@@ -9,7 +7,7 @@ import 'material-design-icons-iconfont';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-input/emby-input';
import '../formdialog.scss';
import '../formdialog.css';
import '../../assets/css/flexstyles.scss';
import template from './dialog.template.html';
@@ -49,13 +47,13 @@ import template from './dialog.template.html';
}
if (options.title) {
dlg.querySelector('.formDialogHeaderTitle').innerText = options.title || '';
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title || '';
} else {
dlg.querySelector('.formDialogHeaderTitle').classList.add('hide');
}
const displayText = options.html || options.text || '';
dlg.querySelector('.text').innerHTML = DOMPurify.sanitize(displayText);
dlg.querySelector('.text').innerHTML = displayText;
if (!displayText) {
dlg.querySelector('.dialogContentInner').classList.add('hide');
@@ -84,7 +82,7 @@ import template from './dialog.template.html';
buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom';
}
html += `<button is="emby-button" type="button" class="${buttonClass}" data-id="${item.id}"${autoFocus}>${escapeHtml(item.name)}</button>`;
html += `<button is="emby-button" type="button" class="${buttonClass}" data-id="${item.id}"${autoFocus}>${item.name}</button>`;
if (item.description) {
html += `<div class="formDialogFooterItem formDialogFooterItem-autosize fieldDescription" style="margin-top:.25em!important;margin-bottom:1.25em!important;">${item.description}</div>`;

View File

@@ -4,8 +4,8 @@ import browser from '../../scripts/browser';
import layoutManager from '../layoutManager';
import inputManager from '../../scripts/inputManager';
import dom from '../../scripts/dom';
import './dialoghelper.scss';
import '../../assets/css/scrollstyles.scss';
import './dialoghelper.css';
import '../../assets/css/scrollstyles.css';
/* eslint-disable indent */
@@ -48,7 +48,7 @@ import '../../assets/css/scrollstyles.scss';
const activeElement = document.activeElement;
let removeScrollLockOnClose = false;
function onHashChange() {
function onHashChange(e) {
const isBack = self.originalUrl === window.location.href;
if (isBack || !isOpened(dlg)) {
@@ -87,7 +87,7 @@ import '../../assets/css/scrollstyles.scss';
if (!self.closedByBack && isHistoryEnabled(dlg)) {
const state = window.history.state || {};
if (state.dialogId === hash) {
appRouter.back();
window.history.back();
}
}
@@ -142,7 +142,7 @@ import '../../assets/css/scrollstyles.scss';
animateDialogOpen(dlg);
if (isHistoryEnabled(dlg)) {
appRouter.show(`/dialog?dlg=${hash}`, { dialogId: hash });
appRouter.pushState({ dialogId: hash }, 'Dialog', `#${hash}`);
window.addEventListener('popstate', onHashChange);
} else {
@@ -200,7 +200,7 @@ import '../../assets/css/scrollstyles.scss';
dlg.dialogContainer = dialogContainer;
document.body.appendChild(dialogContainer);
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve);
});
}
@@ -213,7 +213,7 @@ import '../../assets/css/scrollstyles.scss';
export function close(dlg) {
if (isOpened(dlg)) {
if (isHistoryEnabled(dlg)) {
appRouter.back();
window.history.back();
} else {
closeDialog(dlg);
}
@@ -265,7 +265,6 @@ import '../../assets/css/scrollstyles.scss';
dom.addEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: true
});
return;
}
@@ -379,7 +378,7 @@ import '../../assets/css/scrollstyles.scss';
dlg.setAttribute('data-lockscroll', 'true');
}
if (options.enableHistory !== false) {
if (options.enableHistory === true) {
dlg.setAttribute('data-history', 'true');
}

View File

@@ -122,8 +122,6 @@
right: 0 !important;
margin: 0 !important;
box-shadow: none;
width: auto !important;
height: auto !important;
}
}

View File

@@ -1,301 +1,312 @@
import escapeHtml from 'escape-html';
import loading from '../loading/loading';
import dialogHelper from '../dialogHelper/dialogHelper';
import dom from '../../scripts/dom';
import globalize from '../../scripts/globalize';
import '../listview/listview.scss';
import '../listview/listview.css';
import '../../elements/emby-input/emby-input';
import '../../elements/emby-button/paper-icon-button-light';
import './directorybrowser.scss';
import '../formdialog.scss';
import './directorybrowser.css';
import '../formdialog.css';
import '../../elements/emby-button/emby-button';
import alert from '../alert';
function getSystemInfo() {
return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then(
info => {
systemInfo = info;
return info;
/* eslint-disable indent */
function getSystemInfo() {
return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then(
info => {
systemInfo = info;
return info;
}
);
}
function onDialogClosed() {
loading.hide();
}
function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
if (path && typeof path !== 'string') {
throw new Error('invalid path');
}
);
}
function onDialogClosed() {
loading.hide();
}
loading.show();
function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
if (path && typeof path !== 'string') {
throw new Error('invalid path');
}
loading.show();
const promises = [];
if (path) {
promises.push(ApiClient.getDirectoryContents(path, fileOptions));
promises.push(ApiClient.getParentPath(path));
} else {
promises.push(ApiClient.getDrives());
}
Promise.all(promises).then(
responses => {
const folders = responses[0];
const parentPath = (responses[1] ? JSON.parse(responses[1]) : '') || '';
let html = '';
page.querySelector('.results').scrollTop = 0;
page.querySelector('#txtDirectoryPickerPath').value = path || '';
const promises = [];
if (path === 'Network') {
promises.push(ApiClient.getNetworkDevices());
} else {
if (path) {
html += getItem('lnkPath lnkDirectory', '', parentPath, '...');
}
for (let i = 0, length = folders.length; i < length; i++) {
const folder = folders[i];
const cssClass = folder.Type === 'File' ? 'lnkPath lnkFile' : 'lnkPath lnkDirectory';
html += getItem(cssClass, folder.Type, folder.Path, folder.Name);
}
page.querySelector('.results').innerHTML = html;
loading.hide();
}, () => {
if (updatePathOnError) {
page.querySelector('#txtDirectoryPickerPath').value = '';
page.querySelector('.results').innerHTML = '';
loading.hide();
}
}
);
}
function getItem(cssClass, type, path, name) {
let html = '';
html += `<div class="listItem listItem-border ${cssClass}" data-type="${type}" data-path="${escapeHtml(path)}">`;
html += '<div class="listItemBody" style="padding-left:0;padding-top:.5em;padding-bottom:.5em;">';
html += '<div class="listItemBodyText">';
html += escapeHtml(name);
html += '</div>';
html += '</div>';
html += '<span class="material-icons arrow_forward" aria-hidden="true" style="font-size:inherit;"></span>';
html += '</div>';
return html;
}
function getEditorHtml(options, systemInfo) {
let html = '';
html += '<div class="formDialogContent scrollY">';
html += '<div class="dialogContentInner dialog-content-centered" style="padding-top:2em;">';
if (!options.pathReadOnly) {
const instruction = options.instruction ? `${escapeHtml(options.instruction)}<br/><br/>` : '';
html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
html += instruction;
if (systemInfo.OperatingSystem.toLowerCase() === 'bsd') {
html += '<br/>';
html += '<br/>';
html += globalize.translate('MessageDirectoryPickerBSDInstruction');
html += '<br/>';
} else if (systemInfo.OperatingSystem.toLowerCase() === 'linux') {
html += '<br/>';
html += '<br/>';
html += globalize.translate('MessageDirectoryPickerLinuxInstruction');
html += '<br/>';
}
html += '</div>';
}
html += '<form style="margin:auto;">';
html += '<div class="inputContainer" style="display: flex; align-items: center;">';
html += '<div style="flex-grow:1;">';
let labelKey;
if (options.includeFiles !== true) {
labelKey = 'LabelFolder';
} else {
labelKey = 'LabelPath';
}
const readOnlyAttribute = options.pathReadOnly ? ' readonly' : '';
html += `<input is="emby-input" id="txtDirectoryPickerPath" type="text" required="required" ${readOnlyAttribute} label="${globalize.translate(labelKey)}"/>`;
html += '</div>';
if (!readOnlyAttribute) {
html += `<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="${globalize.translate('Refresh')}"><span class="material-icons search" aria-hidden="true"></span></button>`;
}
html += '</div>';
if (!readOnlyAttribute) {
html += '<div class="results paperList" style="max-height: 200px; overflow-y: auto;"></div>';
}
if (options.enableNetworkSharePath) {
html += '<div class="inputContainer" style="margin-top:2em;">';
html += `<input is="emby-input" id="txtNetworkPath" type="text" label="${globalize.translate('LabelOptionalNetworkPath')}"/>`;
html += '<div class="fieldDescription">';
html += globalize.translate('LabelOptionalNetworkPathHelp', '<b>\\\\server</b>', '<b>\\\\192.168.1.101</b>');
html += '</div>';
html += '</div>';
}
html += '<div class="formDialogFooter">';
html += `<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">${globalize.translate('ButtonOk')}</button>`;
html += '</div>';
html += '</form>';
html += '</div>';
html += '</div>';
html += '</div>';
return html;
}
function alertText(text) {
alertTextWithOptions({
text: text
});
}
function alertTextWithOptions(options) {
alert(options);
}
function validatePath(path, validateWriteable, apiClient) {
return apiClient.ajax({
type: 'POST',
url: apiClient.getUrl('Environment/ValidatePath'),
data: JSON.stringify({
ValidateWriteable: validateWriteable,
Path: path
}),
contentType: 'application/json'
}).catch(response => {
if (response) {
if (response.status === 404) {
alertText(globalize.translate('PathNotFound'));
return Promise.reject();
}
if (response.status === 500) {
if (validateWriteable) {
alertText(globalize.translate('WriteAccessRequired'));
} else {
alertText(globalize.translate('PathNotFound'));
}
return Promise.reject();
}
}
return Promise.resolve();
});
}
function initEditor(content, options, fileOptions) {
content.addEventListener('click', e => {
const lnkPath = dom.parentWithClass(e.target, 'lnkPath');
if (lnkPath) {
const path = lnkPath.getAttribute('data-path');
if (lnkPath.classList.contains('lnkFile')) {
content.querySelector('#txtDirectoryPickerPath').value = path;
promises.push(ApiClient.getDirectoryContents(path, fileOptions));
promises.push(ApiClient.getParentPath(path));
} else {
refreshDirectoryBrowser(content, path, fileOptions, true);
promises.push(ApiClient.getDrives());
}
}
});
content.addEventListener('click', e => {
if (dom.parentWithClass(e.target, 'btnRefreshDirectories')) {
const path = content.querySelector('#txtDirectoryPickerPath').value;
refreshDirectoryBrowser(content, path, fileOptions);
}
});
Promise.all(promises).then(
responses => {
const folders = responses[0];
const parentPath = responses[1] || '';
let html = '';
content.addEventListener('change', e => {
const txtDirectoryPickerPath = dom.parentWithTag(e.target, 'INPUT');
if (txtDirectoryPickerPath && txtDirectoryPickerPath.id === 'txtDirectoryPickerPath') {
refreshDirectoryBrowser(content, txtDirectoryPickerPath.value, fileOptions);
}
});
page.querySelector('.results').scrollTop = 0;
page.querySelector('#txtDirectoryPickerPath').value = path || '';
content.querySelector('form').addEventListener('submit', function(e) {
if (options.callback) {
let networkSharePath = this.querySelector('#txtNetworkPath');
networkSharePath = networkSharePath ? networkSharePath.value : null;
const path = this.querySelector('#txtDirectoryPickerPath').value;
validatePath(path, options.validateWriteable, ApiClient).then(options.callback(path, networkSharePath));
}
e.preventDefault();
e.stopPropagation();
return false;
});
}
if (path) {
html += getItem('lnkPath lnkDirectory', '', parentPath, '...');
}
for (let i = 0, length = folders.length; i < length; i++) {
const folder = folders[i];
const cssClass = folder.Type === 'File' ? 'lnkPath lnkFile' : 'lnkPath lnkDirectory';
html += getItem(cssClass, folder.Type, folder.Path, folder.Name);
}
function getDefaultPath(options) {
if (options.path) {
return Promise.resolve(options.path);
} else {
return ApiClient.getJSON(ApiClient.getUrl('Environment/DefaultDirectoryBrowser')).then(
result => {
return result.Path || '';
if (!path) {
html += getItem('lnkPath lnkDirectory', '', 'Network', globalize.translate('ButtonNetwork'));
}
page.querySelector('.results').innerHTML = html;
loading.hide();
}, () => {
return '';
if (updatePathOnError) {
page.querySelector('#txtDirectoryPickerPath').value = '';
page.querySelector('.results').innerHTML = '';
loading.hide();
}
}
);
}
}
let systemInfo;
class DirectoryBrowser {
currentDialog;
function getItem(cssClass, type, path, name) {
let html = '';
html += `<div class="listItem listItem-border ${cssClass}" data-type="${type}" data-path="${path}">`;
html += '<div class="listItemBody" style="padding-left:0;padding-top:.5em;padding-bottom:.5em;">';
html += '<div class="listItemBodyText">';
html += name;
html += '</div>';
html += '</div>';
html += '<span class="material-icons arrow_forward" style="font-size:inherit;"></span>';
html += '</div>';
return html;
}
show = options => {
options = options || {};
const fileOptions = {
includeDirectories: true
};
if (options.includeDirectories != null) {
fileOptions.includeDirectories = options.includeDirectories;
function getEditorHtml(options, systemInfo) {
let html = '';
html += '<div class="formDialogContent scrollY">';
html += '<div class="dialogContentInner dialog-content-centered" style="padding-top:2em;">';
if (!options.pathReadOnly) {
const instruction = options.instruction ? `${options.instruction}<br/><br/>` : '';
html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
html += instruction;
if (systemInfo.OperatingSystem.toLowerCase() === 'bsd') {
html += '<br/>';
html += '<br/>';
html += globalize.translate('MessageDirectoryPickerBSDInstruction');
html += '<br/>';
} else if (systemInfo.OperatingSystem.toLowerCase() === 'linux') {
html += '<br/>';
html += '<br/>';
html += globalize.translate('MessageDirectoryPickerLinuxInstruction');
html += '<br/>';
}
html += '</div>';
}
if (options.includeFiles != null) {
fileOptions.includeFiles = options.includeFiles;
html += '<form style="margin:auto;">';
html += '<div class="inputContainer" style="display: flex; align-items: center;">';
html += '<div style="flex-grow:1;">';
let labelKey;
if (options.includeFiles !== true) {
labelKey = 'LabelFolder';
} else {
labelKey = 'LabelPath';
}
Promise.all([getSystemInfo(), getDefaultPath(options)]).then(
responses => {
const systemInfo = responses[0];
const initialPath = responses[1];
const dlg = dialogHelper.createDialog({
size: 'small',
removeOnClose: true,
scrollY: false
});
dlg.classList.add('ui-body-a');
dlg.classList.add('background-theme-a');
dlg.classList.add('directoryPicker');
dlg.classList.add('formDialog');
const readOnlyAttribute = options.pathReadOnly ? ' readonly' : '';
html += `<input is="emby-input" id="txtDirectoryPickerPath" type="text" required="required" ${readOnlyAttribute} label="${globalize.translate(labelKey)}"/>`;
html += '</div>';
if (!readOnlyAttribute) {
html += `<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="${globalize.translate('Refresh')}"><span class="material-icons search"></span></button>`;
}
html += '</div>';
if (!readOnlyAttribute) {
html += '<div class="results paperList" style="max-height: 200px; overflow-y: auto;"></div>';
}
if (options.enableNetworkSharePath) {
html += '<div class="inputContainer" style="margin-top:2em;">';
html += `<input is="emby-input" id="txtNetworkPath" type="text" label="${globalize.translate('LabelOptionalNetworkPath')}"/>`;
html += '<div class="fieldDescription">';
html += globalize.translate('LabelOptionalNetworkPathHelp', '<b>\\\\server</b>', '<b>\\\\192.168.1.101</b>');
html += '</div>';
html += '</div>';
}
html += '<div class="formDialogFooter">';
html += `<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">${globalize.translate('ButtonOk')}</button>`;
html += '</div>';
html += '</form>';
html += '</div>';
html += '</div>';
html += '</div>';
let html = '';
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<h3 class="formDialogHeaderTitle">';
html += escapeHtml(options.header || '') || globalize.translate('HeaderSelectPath');
html += '</h3>';
html += '</div>';
html += getEditorHtml(options, systemInfo);
dlg.innerHTML = html;
initEditor(dlg, options, fileOptions);
dlg.addEventListener('close', onDialogClosed);
dialogHelper.open(dlg);
dlg.querySelector('.btnCloseDialog').addEventListener('click', () => {
dialogHelper.close(dlg);
});
this.currentDialog = dlg;
dlg.querySelector('#txtDirectoryPickerPath').value = initialPath;
const txtNetworkPath = dlg.querySelector('#txtNetworkPath');
if (txtNetworkPath) {
txtNetworkPath.value = options.networkSharePath || '';
return html;
}
function alertText(text) {
alertTextWithOptions({
text: text
});
}
function alertTextWithOptions(options) {
alert(options);
}
function validatePath(path, validateWriteable, apiClient) {
return apiClient.ajax({
type: 'POST',
url: apiClient.getUrl('Environment/ValidatePath'),
data: JSON.stringify({
ValidateWriteable: validateWriteable,
Path: path
}),
contentType: 'application/json'
}).catch(response => {
if (response) {
if (response.status === 404) {
alertText(globalize.translate('PathNotFound'));
return Promise.reject();
}
if (!options.pathReadOnly) {
refreshDirectoryBrowser(dlg, initialPath, fileOptions, true);
if (response.status === 500) {
if (validateWriteable) {
alertText(globalize.translate('WriteAccessRequired'));
} else {
alertText(globalize.translate('PathNotFound'));
}
return Promise.reject();
}
}
);
};
return Promise.resolve();
});
}
close = () => {
if (this.currentDialog) {
dialogHelper.close(this.currentDialog);
function initEditor(content, options, fileOptions) {
content.addEventListener('click', e => {
const lnkPath = dom.parentWithClass(e.target, 'lnkPath');
if (lnkPath) {
const path = lnkPath.getAttribute('data-path');
if (lnkPath.classList.contains('lnkFile')) {
content.querySelector('#txtDirectoryPickerPath').value = path;
} else {
refreshDirectoryBrowser(content, path, fileOptions, true);
}
}
});
content.addEventListener('click', e => {
if (dom.parentWithClass(e.target, 'btnRefreshDirectories')) {
const path = content.querySelector('#txtDirectoryPickerPath').value;
refreshDirectoryBrowser(content, path, fileOptions);
}
});
content.addEventListener('change', e => {
const txtDirectoryPickerPath = dom.parentWithTag(e.target, 'INPUT');
if (txtDirectoryPickerPath && txtDirectoryPickerPath.id === 'txtDirectoryPickerPath') {
refreshDirectoryBrowser(content, txtDirectoryPickerPath.value, fileOptions);
}
});
content.querySelector('form').addEventListener('submit', function(e) {
if (options.callback) {
let networkSharePath = this.querySelector('#txtNetworkPath');
networkSharePath = networkSharePath ? networkSharePath.value : null;
const path = this.querySelector('#txtDirectoryPickerPath').value;
validatePath(path, options.validateWriteable, ApiClient).then(options.callback(path, networkSharePath));
}
e.preventDefault();
e.stopPropagation();
return false;
});
}
function getDefaultPath(options) {
if (options.path) {
return Promise.resolve(options.path);
} else {
return ApiClient.getJSON(ApiClient.getUrl('Environment/DefaultDirectoryBrowser')).then(
result => {
return result.Path || '';
}, () => {
return '';
}
);
}
};
}
}
export default DirectoryBrowser;
class directoryBrowser {
constructor() {
let currentDialog;
this.show = options => {
options = options || {};
const fileOptions = {
includeDirectories: true
};
if (options.includeDirectories != null) {
fileOptions.includeDirectories = options.includeDirectories;
}
if (options.includeFiles != null) {
fileOptions.includeFiles = options.includeFiles;
}
Promise.all([getSystemInfo(), getDefaultPath(options)]).then(
responses => {
const systemInfo = responses[0];
const initialPath = responses[1];
const dlg = dialogHelper.createDialog({
size: 'small',
removeOnClose: true,
scrollY: false
});
dlg.classList.add('ui-body-a');
dlg.classList.add('background-theme-a');
dlg.classList.add('directoryPicker');
dlg.classList.add('formDialog');
let html = '';
html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">';
html += options.header || globalize.translate('HeaderSelectPath');
html += '</h3>';
html += '</div>';
html += getEditorHtml(options, systemInfo);
dlg.innerHTML = html;
initEditor(dlg, options, fileOptions);
dlg.addEventListener('close', onDialogClosed);
dialogHelper.open(dlg);
dlg.querySelector('.btnCloseDialog').addEventListener('click', () => {
dialogHelper.close(dlg);
});
currentDialog = dlg;
dlg.querySelector('#txtDirectoryPickerPath').value = initialPath;
const txtNetworkPath = dlg.querySelector('#txtNetworkPath');
if (txtNetworkPath) {
txtNetworkPath.value = options.networkSharePath || '';
}
if (!options.pathReadOnly) {
refreshDirectoryBrowser(dlg, initialPath, fileOptions, true);
}
}
);
};
this.close = () => {
if (currentDialog) {
dialogHelper.close(currentDialog);
}
};
}
}
let systemInfo;
/* eslint-enable indent */
export default directoryBrowser;

View File

@@ -1,4 +1,3 @@
import escapeHtml from 'escape-html';
import browser from '../../scripts/browser';
import layoutManager from '../layoutManager';
import { pluginManager } from '../pluginManager';
@@ -12,7 +11,6 @@ import { Events } from 'jellyfin-apiclient';
import '../../elements/emby-select/emby-select';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-textarea/emby-textarea';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './displaySettings.template.html';
@@ -22,7 +20,7 @@ import template from './displaySettings.template.html';
function fillThemes(select, selectedTheme) {
skinManager.getThemes().then(themes => {
select.innerHTML = themes.map(t => {
return `<option value="${t.id}">${escapeHtml(t.name)}</option>`;
return `<option value="${t.id}">${t.name}</option>`;
}).join('');
// get default theme
@@ -48,7 +46,7 @@ import template from './displaySettings.template.html';
});
selectScreensaver.innerHTML = options.map(o => {
return `<option value="${o.value}">${escapeHtml(o.name)}</option>`;
return `<option value="${o.value}">${o.name}</option>`;
}).join('');
selectScreensaver.value = userSettings.screensaver();
@@ -101,6 +99,16 @@ import template from './displaySettings.template.html';
context.querySelector('.fldDateTimeLocale').classList.add('hide');
}
if (!browser.tizen && !browser.web0s) {
context.querySelector('.fldBackdrops').classList.remove('hide');
context.querySelector('.fldThemeSong').classList.remove('hide');
context.querySelector('.fldThemeVideo').classList.remove('hide');
} else {
context.querySelector('.fldBackdrops').classList.add('hide');
context.querySelector('.fldThemeSong').classList.add('hide');
context.querySelector('.fldThemeVideo').classList.add('hide');
}
fillThemes(context.querySelector('#selectTheme'), userSettings.theme());
fillThemes(context.querySelector('#selectDashboardTheme'), userSettings.dashboardTheme());
@@ -115,18 +123,11 @@ import template from './displaySettings.template.html';
context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops();
context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner();
context.querySelector('#chkDisableCustomCss').checked = userSettings.disableCustomCss();
context.querySelector('#txtLocalCustomCss').value = userSettings.customCss();
context.querySelector('#selectLanguage').value = userSettings.language() || '';
context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || '';
context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize();
context.querySelector('#txtMaxDaysForNextUp').value = userSettings.maxDaysForNextUp();
context.querySelector('#chkRewatchingNextUp').checked = userSettings.enableRewatchingInNextUp();
context.querySelector('#chkUseEpisodeImagesInNextUp').checked = userSettings.useEpisodeImagesInNextUpAndResume();
context.querySelector('.selectLayout').value = layoutManager.getSavedLayout() || '';
showOrHideMissingEpisodesField(context);
@@ -151,18 +152,11 @@ import template from './displaySettings.template.html';
userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value);
userSettingsInstance.maxDaysForNextUp(context.querySelector('#txtMaxDaysForNextUp').value);
userSettingsInstance.enableRewatchingInNextUp(context.querySelector('#chkRewatchingNextUp').checked);
userSettingsInstance.useEpisodeImagesInNextUpAndResume(context.querySelector('#chkUseEpisodeImagesInNextUp').checked);
userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked);
userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked);
userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked);
userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked);
userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked);
userSettingsInstance.customCss(context.querySelector('#txtLocalCustomCss').value);
if (user.Id === apiClient.getCurrentUserId()) {
skinManager.setTheme(userSettingsInstance.theme());
}

View File

@@ -1,82 +1,71 @@
<form style="margin: 0 auto;">
<h2 class="sectionTitle">
${Localization}
${Display}
</h2>
<div class="selectContainer languageSection hide">
<select id="selectLanguage" is="emby-select" label="${LabelDisplayLanguage}">
<option value="">${Auto}</option>
<option value="af">Afrikaans</option>
<option value="ar">العربية</option>
<option value="be-BY">Беларуская</option>
<option value="bg-BG">Български</option>
<option value="bn_BD">বাংলা (বাংলাদেশ)</option>
<option value="ca">Català</option>
<option value="cs">Čeština</option>
<option value="cy">Cymraeg</option>
<option value="da">Dansk</option>
<option value="de">Deutsch</option>
<option value="el">Ελληνικά</option>
<option value="en-GB">English (United Kingdom)</option>
<option value="sq">Albanian</option>
<option value="ar">Arabic</option>
<option value="be-BY">Belarusian</option>
<option value="bn_BD">Bengali (Bangladesh)</option>
<option value="bg-BG">Bulgarian</option>
<option value="ca">Catalan</option>
<option value="zh-HK">Chinese (Hong Kong)</option>
<option value="zh-CN">Chinese (Simplified)</option>
<option value="zh-TW">Chinese (Traditional)</option>
<option value="hr">Croatian</option>
<option value="cs">Czech</option>
<option value="da">Danish</option>
<option value="nl">Dutch</option>
<option value="en-US">English</option>
<option value="en-GB">English (United Kingdom)</option>
<option value="eo">Esperanto</option>
<option value="es">Español</option>
<option value="es_419">Español americano</option>
<option value="es-AR">Español (Argentina)</option>
<option value="es_DO">Español (Dominicana)</option>
<option value="es-MX">Español (México)</option>
<option value="et">Eesti</option>
<option value="eu">Euskara</option>
<option value="fa">فارسی</option>
<option value="fi">Suomi</option>
<option value="fil">Filipino</option>
<option value="fr">Français</option>
<option value="fr-CA">Français (Canada)</option>
<option value="gl">Galego</option>
<option value="gsw">Schwiizerdütsch</option>
<option value="he">עִבְרִית</option>
<option value="hi-IN">हिन्दी</option>
<option value="hr">Hrvatski </option>
<option value="hu">Magyar</option>
<option value="id">Bahasa Indonesia</option>
<option value="is-IS">Íslenska</option>
<option value="it">Italiano</option>
<option value="ja">日本語</option>
<option value="kk">Qazaqşa</option>
<option value="ko">한국어</option>
<option value="lt-LT">Lietuvių</option>
<option value="lv">Latviešu</option>
<option value="mk">Македонски</option>
<option value="ml">മലയാളം</option>
<option value="mr">मराठी</option>
<option value="ms">Bahasa Melayu</option>
<option value="nb">Norsk bokmål</option>
<option value="ne">नेपाली</option>
<option value="nl">Nederlands</option>
<option value="nn">Norsk nynorsk</option>
<option value="pa">ਪੰਜਾਬੀ</option>
<option value="pl">Polski</option>
<option value="fi">Finnish</option>
<option value="fr">French</option>
<option value="fr-CA">French (Canada)</option>
<option value="gl">Galician</option>
<option value="de">German</option>
<option value="gsw">German (Swiss)</option>
<option value="el">Greek</option>
<option value="he">Hebrew</option>
<option value="hi-IN">Hindi</option>
<option value="hu">Hungarian</option>
<option value="is">Icelandic</option>
<option value="id">Indonesian</option>
<option value="it">Italian</option>
<option value="ja">Japanese</option>
<option value="kk">Kazakh</option>
<option value="ko">Korean</option>
<option value="lt-LT">Lithuanian</option>
<option value="ms">Malay</option>
<option value="mr">Marathi</option>
<option value="nb">Norwegian Bokmål</option>
<option value="fa">Persian</option>
<option value="pr">Pirate</option>
<option value="pt">Português</option>
<option value="pt-BR">Português (Brasil)</option>
<option value="pt-PT">Português (Portugal)</option>
<option value="ro">Românește</option>
<option value="ru">Русский</option>
<option value="sk">Slovenčina</option>
<option value="sl-SI">Slovenščina</option>
<option value="sq">Shqip</option>
<option value="sr">Српски</option>
<option value="sv">Svenska</option>
<option value="ta">தமிழ்</option>
<option value="te">తెలుగు</option>
<option value="th">ภาษาไทย</option>
<option value="tr">Türkçe</option>
<option value="uk">Українська</option>
<option value="ur_PK">اُردُو</option>
<option value="vi">Tiếng Việt</option>
<option value="zh-CN">汉语 (简化字)</option>
<option value="zh-TW">漢語 (繁体字)</option>
<option value="zh-HK">廣東話 (香港)</option>
<option value="pl">Polish</option>
<option value="pt">Portuguese</option>
<option value="pt-BR">Portuguese (Brazil)</option>
<option value="pt-PT">Portuguese (Portugal)</option>
<option value="ro">Romanian</option>
<option value="ru">Russian</option>
<option value="sk">Slovak</option>
<option value="sl-SI">Slovenian (Slovenia)</option>
<option value="es">Spanish</option>
<option value="es_AR">Spanish (Argentina)</option>
<option value="es_DO">Spanish (Dominican Republic)</option>
<option value="es-419">Spanish (Latin America)</option>
<option value="es-MX">Spanish (Mexico)</option>
<option value="sv">Swedish</option>
<option value="ta">Tamil</option>
<option value="th">Thai</option>
<option value="tr">Turkish</option>
<option value="uk">Ukrainian</option>
<option value="ur_PK">Urdu (Pakistan)</option>
<option value="vi">Vietnamese</option>
</select>
<div class="fieldDescription">
<div>${LabelDisplayLanguageHelp}</div>
@@ -90,82 +79,68 @@
<select is="emby-select" class="selectDateTimeLocale" label="${LabelDateTimeLocale}">
<option value="">${Auto}</option>
<option value="af">Afrikaans</option>
<option value="ar">العربية</option>
<option value="be-BY">Беларуская</option>
<option value="bg-BG">Български</option>
<option value="bn_BD">বাংলা (বাংলাদেশ)</option>
<option value="ca">Català</option>
<option value="cs">Čeština</option>
<option value="cy">Cymraeg</option>
<option value="da">Dansk</option>
<option value="de">Deutsch</option>
<option value="el">Ελληνικά</option>
<option value="en-GB">English (United Kingdom)</option>
<option value="sq">Albanian</option>
<option value="ar">Arabic</option>
<option value="be-BY">Belarusian</option>
<option value="bn_BD">Bengali (Bangladesh)</option>
<option value="bg-BG">Bulgarian</option>
<option value="ca">Catalan</option>
<option value="zh-HK">Chinese (Hong Kong)</option>
<option value="zh-CN">Chinese (Simplified)</option>
<option value="zh-TW">Chinese (Traditional)</option>
<option value="hr">Croatian</option>
<option value="cs">Czech</option>
<option value="da">Danish</option>
<option value="nl">Dutch</option>
<option value="en-US">English</option>
<option value="en-GB">English (United Kingdom)</option>
<option value="eo">Esperanto</option>
<option value="es">Español</option>
<option value="es_419">Español americano</option>
<option value="es-AR">Español (Argentina)</option>
<option value="es_DO">Español (Dominicana)</option>
<option value="es-MX">Español (México)</option>
<option value="et">Eesti</option>
<option value="fa">فارسی</option>
<option value="fi">Suomi</option>
<option value="fil">Filipino</option>
<option value="fr">Français</option>
<option value="fr-CA">Français (Canada)</option>
<option value="gl">Galego</option>
<option value="gsw">Schwiizerdütsch</option>
<option value="he">עִבְרִית</option>
<option value="hi-IN">हिन्दी</option>
<option value="hr">Hrvatski </option>
<option value="hu">Magyar</option>
<option value="id">Bahasa Indonesia</option>
<option value="is-IS">Íslenska</option>
<option value="it">Italiano</option>
<option value="ja">日本語</option>
<option value="kk">Qazaqşa</option>
<option value="ko">한국어</option>
<option value="lt-LT">Lietuvių</option>
<option value="lv">Latviešu</option>
<option value="mk">Македонски</option>
<option value="ml">മലയാളം</option>
<option value="mr">मराठी</option>
<option value="ms">Bahasa Melayu</option>
<option value="nb">Norsk bokmål</option>
<option value="ne">नेपाली</option>
<option value="nl">Nederlands</option>
<option value="nn">Norsk nynorsk</option>
<option value="pa">ਪੰਜਾਬੀ</option>
<option value="pl">Polski</option>
<option value="fi">Finnish</option>
<option value="fr">French</option>
<option value="fr-CA">French (Canada)</option>
<option value="gl">Galician</option>
<option value="de">German</option>
<option value="gsw">German (Swiss)</option>
<option value="el">Greek</option>
<option value="he">Hebrew</option>
<option value="hi-IN">Hindi</option>
<option value="hu">Hungarian</option>
<option value="is">Icelandic</option>
<option value="id">Indonesian</option>
<option value="it">Italian</option>
<option value="ja">Japanese</option>
<option value="kk">Kazakh</option>
<option value="ko">Korean</option>
<option value="lt-LT">Lithuanian</option>
<option value="ms">Malay</option>
<option value="mr">Marathi</option>
<option value="nb">Norwegian Bokmål</option>
<option value="fa">Persian</option>
<option value="pr">Pirate</option>
<option value="pt">Português</option>
<option value="pt-BR">Português (Brasil)</option>
<option value="pt-PT">Português (Portugal)</option>
<option value="ro">Românește</option>
<option value="ru">Русский</option>
<option value="sk">Slovenčina</option>
<option value="sl-SI">Slovenščina</option>
<option value="sq">Shqip</option>
<option value="sr">Српски</option>
<option value="sv">Svenska</option>
<option value="ta">தமிழ்</option>
<option value="te">తెలుగు</option>
<option value="th">ภาษาไทย</option>
<option value="tr">Türkçe</option>
<option value="uk">Українська</option>
<option value="ur_PK">اُردُو</option>
<option value="vi">Tiếng Việt</option>
<option value="zh-CN">汉语 (简化字)</option>
<option value="zh-TW">漢語 (繁体字)</option>
<option value="zh-HK">廣東話 (香港)</option>
<option value="pl">Polish</option>
<option value="pt">Portuguese</option>
<option value="pt-BR">Portuguese (Brazil)</option>
<option value="pt-PT">Portuguese (Portugal)</option>
<option value="ro">Romanian</option>
<option value="ru">Russian</option>
<option value="sk">Slovak</option>
<option value="sl-SI">Slovenian (Slovenia)</option>
<option value="es">Spanish</option>
<option value="es_AR">Spanish (Argentina)</option>
<option value="es_DO">Spanish (Dominican Republic)</option>
<option value="es-419">Spanish (Latin America)</option>
<option value="es-MX">Spanish (Mexico)</option>
<option value="sv">Swedish</option>
<option value="ta">Tamil</option>
<option value="th">Thai</option>
<option value="tr">Turkish</option>
<option value="uk">Ukrainian</option>
<option value="ur_PK">Urdu (Pakistan)</option>
<option value="vi">Vietnamese</option>
</select>
</div>
<h2 class="sectionTitle">
${Display}
</h2>
<div class="selectContainer fldDisplayMode hide">
<select is="emby-select" class="selectLayout" label="${LabelDisplayMode}">
<option value="">${Auto}</option>
@@ -181,19 +156,6 @@
<select id="selectTheme" is="emby-select" label="${LabelTheme}"></select>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkDisableCustomCss" />
<span>${DisableCustomCss}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelDisableCustomCss}</div>
</div>
<div class="inputContainer customCssContainer">
<textarea is="emby-textarea" id="txtLocalCustomCss" label="${LabelCustomCss}" class="textarea-mono"></textarea>
<div class="fieldDescription">${LabelLocalCustomCss}</div>
</div>
<div class="selectContainer selectDashboardThemeContainer hide">
<select id="selectDashboardTheme" is="emby-select" label="${LabelDashboardTheme}"></select>
</div>
@@ -202,6 +164,11 @@
<select is="emby-select" class="selectScreensaver" label="${LabelScreensaver}"></select>
</div>
<div class="inputContainer inputContainer-withDescription">
<input is="emby-input" type="number" id="txtLibraryPageSize" pattern="[0-9]*" required="required" min="0" max="1000" step="1" label="${LabelLibraryPageSize}" />
<div class="fieldDescription">${LabelLibraryPageSizeHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkFadein" />
@@ -218,16 +185,15 @@
<div class="fieldDescription checkboxFieldDescription">${EnableBlurHashHelp}</div>
</div>
<h2 class="sectionTitle">
${HeaderLibraries}
</h2>
<div class="inputContainer inputContainer-withDescription">
<input is="emby-input" type="number" id="txtLibraryPageSize" pattern="[0-9]*" required="required" min="0" max="1000" step="1" label="${LabelLibraryPageSize}" />
<div class="fieldDescription">${LabelLibraryPageSizeHelp}</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkDetailsBanner" />
<span>${EnableDetailsBanner}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableDetailsBannerHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldBackdrops">
<div class="checkboxContainer checkboxContainer-withDescription fldBackdrops hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkBackdrops" />
<span>${Backdrops}</span>
@@ -235,7 +201,7 @@
<div class="fieldDescription checkboxFieldDescription">${EnableBackdropsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldThemeSong">
<div class="checkboxContainer checkboxContainer-withDescription fldThemeSong hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkThemeSong" />
<span>${ThemeSongs}</span>
@@ -243,7 +209,7 @@
<div class="fieldDescription checkboxFieldDescription">${EnableThemeSongsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo">
<div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkThemeVideo" />
<span>${ThemeVideos}</span>
@@ -259,43 +225,6 @@
<div class="fieldDescription checkboxFieldDescription">${DisplayMissingEpisodesWithinSeasonsHelp}</div>
</div>
<h2 class="sectionTitle">
${NextUp}
</h2>
<div class="inputContainer inputContainer-withDescription">
<input is="emby-input" type="number" id="txtMaxDaysForNextUp" pattern="[0-9]*" required="required" min="0" max="1000" step="1" label="${LabelMaxDaysForNextUp}" />
<div class="fieldDescription">${LabelMaxDaysForNextUpHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkRewatchingNextUp" />
<span>${EnableRewatchingNextUp}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableRewatchingNextUpHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldUseEpisodeImagesInNextUp">
<label>
<input type="checkbox" is="emby-checkbox" id="chkUseEpisodeImagesInNextUp" />
<span>${UseEpisodeImagesInNextUp}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${UseEpisodeImagesInNextUpHelp}</div>
</div>
<h2 class="sectionTitle">
${ItemDetails}
</h2>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkDetailsBanner" />
<span>${EnableDetailsBanner}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableDetailsBannerHelp}</div>
</div>
<button is="emby-button" type="submit" class="raised button-submit block btnSave hide">
<span>${Save}</span>
</button>

View File

@@ -5,7 +5,7 @@ import { appHost } from './apphost';
import imageLoader from './images/imageLoader';
import globalize from '../scripts/globalize';
import layoutManager from './layoutManager';
import '../assets/css/scrollstyles.scss';
import '../assets/css/scrollstyles.css';
import '../elements/emby-itemscontainer/emby-itemscontainer';
/* eslint-disable indent */
@@ -145,7 +145,7 @@ import '../elements/emby-itemscontainer/emby-itemscontainer';
html += '<h2 class="sectionTitle sectionTitle-cards">';
html += globalize.translate(section.name);
html += '</h2>';
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
html += '<span class="material-icons chevron_right"></span>';
html += '</a>';
} else {
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate(section.name) + '</h2>';

View File

@@ -4,7 +4,7 @@ import globalize from '../../scripts/globalize';
import { Events } from 'jellyfin-apiclient';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-collapse/emby-collapse';
import './style.scss';
import './style.css';
import ServerConnections from '../ServerConnections';
import template from './filterdialog.template.html';
@@ -32,6 +32,9 @@ import template from './filterdialog.template.html';
}
function renderFilters(context, result, query) {
if (result.Tags) {
result.Tags.length = Math.min(result.Tags.length, 50);
}
renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) {
const delimeter = '|';
return (delimeter + (query.Genres || '') + delimeter).includes(delimeter + i + delimeter);
@@ -87,7 +90,7 @@ import template from './filterdialog.template.html';
context.querySelector('.chk3DFilter').checked = query.Is3D === true;
context.querySelector('.chkHDFilter').checked = query.IsHD === true;
context.querySelector('.chk4KFilter').checked = query.Is4K === true;
context.querySelector('.chkSDFilter').checked = query.IsHD === false;
context.querySelector('.chkSDFilter').checked = query.IsHD === true;
context.querySelector('#chkSubtitle').checked = query.HasSubtitles === true;
context.querySelector('#chkTrailer').checked = query.HasTrailer === true;
context.querySelector('#chkThemeSong').checked = query.HasThemeSong === true;
@@ -272,25 +275,15 @@ import template from './filterdialog.template.html';
triggerChange(this);
});
const chkHDFilter = context.querySelector('.chkHDFilter');
const chkSDFilter = context.querySelector('.chkSDFilter');
chkHDFilter.addEventListener('change', () => {
query.StartIndex = 0;
if (chkHDFilter.checked) {
chkSDFilter.checked = false;
query.IsHD = true;
} else {
query.IsHD = null;
}
query.IsHD = chkHDFilter.checked ? true : null;
triggerChange(this);
});
const chkSDFilter = context.querySelector('.chkSDFilter');
chkSDFilter.addEventListener('change', () => {
query.StartIndex = 0;
if (chkSDFilter.checked) {
chkHDFilter.checked = false;
query.IsHD = false;
} else {
query.IsHD = null;
}
query.IsHD = chkSDFilter.checked ? false : null;
triggerChange(this);
});
for (const elem of context.querySelectorAll('.chkStatus')) {

View File

@@ -1,4 +1,3 @@
import escapeHtml from 'escape-html';
import dom from '../../scripts/dom';
import focusManager from '../focusManager';
import dialogHelper from '../dialogHelper/dialogHelper';
@@ -12,7 +11,7 @@ import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
import '../formdialog.css';
import '../../assets/css/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import template from './filtermenu.template.html';
@@ -38,7 +37,7 @@ function renderOptions(context, selector, cssClass, items, isCheckedFn) {
const checkedHtml = isCheckedFn(filter) ? ' checked' : '';
itemHtml += '<label>';
itemHtml += '<input is="emby-checkbox" type="checkbox"' + checkedHtml + ' data-filter="' + filter.Id + '" class="' + cssClass + '"/>';
itemHtml += '<span>' + escapeHtml(filter.Name) + '</span>';
itemHtml += '<span>' + filter.Name + '</span>';
itemHtml += '</label>';
return itemHtml;
@@ -211,7 +210,7 @@ function loadDynamicFilters(context, options) {
}
class FilterMenu {
show(options) {
return new Promise( (resolve) => {
return new Promise( (resolve, reject) => {
const dialogOptions = {
removeOnClose: true,
scrollY: false
@@ -229,7 +228,7 @@ class FilterMenu {
let html = '';
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCancel hide-mouse-idle-tv" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<button is="paper-icon-button-light" class="btnCancel hide-mouse-idle-tv" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">${Filters}</h3>';
html += '</div>';

View File

@@ -8,7 +8,7 @@ import scrollManager from './scrollManager';
scopes.push(elem);
}
function popScope() {
function popScope(elem) {
if (scopes.length) {
scopes.length -= 1;
}

View File

@@ -60,7 +60,6 @@
}
.layout-tv .formDialogFooter {
position: relative;
align-items: center;
justify-content: center;
flex-wrap: wrap;
@@ -84,6 +83,11 @@
flex-basis: 12em;
}
.layout-tv .formDialogFooterItem {
flex-grow: 1;
flex-basis: 0;
}
.formDialogFooterItem-vertical {
max-width: none !important;
width: 100%;

View File

@@ -5,7 +5,7 @@ import layoutManager from '../layoutManager';
import scrollHelper from '../../scripts/scrollHelper';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-radio/emby-radio';
import '../formdialog.scss';
import '../formdialog.css';
import 'material-design-icons-iconfont';
import template from './guide-settings.template.html';

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