Still converting to bootstrap 5. Moved away from Wt's boostrap5 theme to better stick to vanilla bootstrap

This commit is contained in:
emeric
2022-05-06 13:22:08 +02:00
parent 582e6ed7ae
commit 147e174315
30 changed files with 437 additions and 98 deletions
+6 -6
View File
@@ -10,7 +10,7 @@
<label class="form-label" for="${id:media-directory}"> <label class="form-label" for="${id:media-directory}">
${tr:Lms.Admin.Database.path} ${tr:Lms.Admin.Database.path}
</label> </label>
${media-directory} ${media-directory class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${media-directory-info} ${media-directory-info}
</div> </div>
@@ -19,7 +19,7 @@
<label class="form-label" for="${id:update-period}"> <label class="form-label" for="${id:update-period}">
${tr:Lms.Admin.Database.update-period} ${tr:Lms.Admin.Database.update-period}
</label> </label>
${update-period} ${update-period class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${update-period-info"} ${update-period-info"}
</div> </div>
@@ -28,7 +28,7 @@
<label class="form-label" for="${id:update-start-time}"> <label class="form-label" for="${id:update-start-time}">
${tr:Lms.Admin.Database.update-start-time} ${tr:Lms.Admin.Database.update-start-time}
</label> </label>
${update-start-time} ${update-start-time class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${update-start-time-info} ${update-start-time-info}
</div> </div>
@@ -38,7 +38,7 @@
<label class="form-label" for="${id:recommendation-engine-type}"> <label class="form-label" for="${id:recommendation-engine-type}">
${tr:Lms.Admin.Database.recommendation-engine-type} ${tr:Lms.Admin.Database.recommendation-engine-type}
</label> </label>
${recommendation-engine-type} ${recommendation-engine-type class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${recommendation-engine-type-info} ${recommendation-engine-type-info}
</div> </div>
@@ -47,13 +47,13 @@
<label class="form-label" for="${id:tags}"> <label class="form-label" for="${id:tags}">
${tr:Lms.Admin.Database.tags} ${tr:Lms.Admin.Database.tags}
</label> </label>
${tags} ${tags class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${tags-info} ${tags-info}
</div> </div>
</div> </div>
<div class="col-12"> <div class="col-12">
${apply-btn class="btn-primary me-1"}${discard-btn} ${apply-btn class="btn btn-primary me-1"}${discard-btn class="btn btn-secondary"}
</div> </div>
</form> </form>
<br/> <br/>
+3 -3
View File
@@ -17,7 +17,7 @@
<label class="form-label" for="${id:login}"> <label class="form-label" for="${id:login}">
${tr:Lms.login} ${tr:Lms.login}
</label> </label>
${login} ${login class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${login-info} ${login-info}
</div> </div>
@@ -28,7 +28,7 @@
<label class="form-label" for="${id:password}"> <label class="form-label" for="${id:password}">
${tr:Lms.password-new} ${tr:Lms.password-new}
</label> </label>
${password} ${password class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${password-info} ${password-info}
</div> </div>
@@ -48,7 +48,7 @@
</div> </div>
${</if-demo>} ${</if-demo>}
<div class="col-12"> <div class="col-12">
${save-btn class="btn-primary"} ${save-btn class="btn btn-primary"}
</div> </div>
</form> </form>
</message> </message>
+22 -3
View File
@@ -5,17 +5,36 @@
<h2>${tr:Lms.Admin.Users.users}</h2> <h2>${tr:Lms.Admin.Users.users}</h2>
${users} ${users}
${<if-can-create-user>} ${<if-can-create-user>}
${add-btn class="btn-primary Lms-admin-users-add-btn"} ${add-btn class="btn btn-primary"}
${</if-can-create-user>} ${</if-can-create-user>}
</message> </message>
<message id="Lms.Admin.Users.template.entry"> <message id="Lms.Admin.Users.template.entry">
<div class="row align-items-center mb-3"> <div class="row align-items-center mb-3">
<div class="col-auto me-auto"> <div class="col-auto me-auto">
${name}${<if-tag>} <span class="badge bg-info">${tag}</span>${</if-tag>} ${name}${<if-tag>}<span class="badge bg-info ms-2">${tag}</span>${</if-tag>}
</div> </div>
<div class="col-auto"> <div class="col-auto">
${<if-edit>}${edit-btn class="btn-warning btn-sm"} ${del-btn class="btn-danger btn-sm"}${</if-edit>} ${<if-edit>}${edit-btn class="btn btn-sm btn-warning me-1"} ${del-btn class="btn btn-sm btn-danger"}${</if-edit>}
</div>
</div>
</message>
<message id="Lms.Admin.Users.template.delete-user">
<div class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${tr:Lms.Admin.Users.del-user}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<div class="modal-footer">
${del-btn class="btn btn-danger me-1"}${cancel-btn class="btn btn-secondary"}
</div>
</div>
</div> </div>
</div> </div>
</message> </message>
+1 -1
View File
@@ -4,7 +4,7 @@
<message id="Lms.Explore.Artist.template"> <message id="Lms.Explore.Artist.template">
<div class="mb-3"> <div class="mb-3">
${name} <h3>${name}</h3>
${clusters class="mb-2"} ${clusters class="mb-2"}
${play-btn class="btn btn-outline-primary me-2"}${more-btn class="btn btn-outline-primary"} ${play-btn class="btn btn-outline-primary me-2"}${more-btn class="btn btn-outline-primary"}
</div> </div>
+2 -2
View File
@@ -9,7 +9,7 @@
<message id="Lms.Explore.template.play-btn"><i class="fa fa-fw fa-play"></i></message> <message id="Lms.Explore.template.play-btn"><i class="fa fa-fw fa-play"></i></message>
<message id="Lms.Explore.template.filters"> <message id="Lms.Explore.template.filters">
${add-filter class="btn-sm btn-outline-primary me-2"}${clusters class="d-inline-block align-items-center"} ${add-filter class="btn btn-sm btn-outline-primary me-2"}${clusters class="d-inline-block align-items-center"}
</message> </message>
<message id="Lms.Explore.template.add-filter"> <message id="Lms.Explore.template.add-filter">
@@ -28,7 +28,7 @@
</div> </div>
<div class="col-12"> <div class="col-12">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
${add-btn class="btn-primary me-1"}${cancel-btn} ${add-btn class="btn btn-primary me-1"}${cancel-btn class="btn btn-secondary"}
</div> </div>
</div> </div>
</form> </form>
+3 -3
View File
@@ -11,7 +11,7 @@
<label class="form-label" for="${id:login-name}"> <label class="form-label" for="${id:login-name}">
${tr:Lms.login} ${tr:Lms.login}
</label> </label>
${login-name} ${login-name class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${login-name-info} ${login-name-info}
</div> </div>
@@ -20,7 +20,7 @@
<label class="form-label" for="${id:password}"> <label class="form-label" for="${id:password}">
${tr:Lms.password} ${tr:Lms.password}
</label> </label>
${password} ${password class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${password-info} ${password-info}
</div> </div>
@@ -34,7 +34,7 @@
</div> </div>
</div> </div>
<div class="col-12"> <div class="col-12">
${login-btn class="btn-primary"} ${login-btn class="btn btn-primary"}
</div> </div>
</div> </div>
</form> </form>
+2 -1
View File
@@ -5,7 +5,8 @@
${navbar} ${navbar}
<div class="container"> <div class="container">
${contents} ${contents}
${notifications class="toast-container position-fixed top-0 end-0 p-3"} ${notifications class="toast-container Lms-notification-container p-3"}
${modal}
</div> </div>
${player class="fixed-bottom bg-dark Lms-player"} ${player class="fixed-bottom bg-dark Lms-player"}
</message> </message>
-1
View File
@@ -117,7 +117,6 @@
<message id="Lms.Explore.download">Download</message> <message id="Lms.Explore.download">Download</message>
<message id="Lms.Explore.filter-added">Filter added</message> <message id="Lms.Explore.filter-added">Filter added</message>
<message id="Lms.Explore.filters">Filters</message> <message id="Lms.Explore.filters">Filters</message>
<message id="Lms.Explore.links">Links</message>
<message id="Lms.Explore.most-played">Most played</message> <message id="Lms.Explore.most-played">Most played</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message> <message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message> <message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message>
-1
View File
@@ -117,7 +117,6 @@
<message id="Lms.Explore.download">Télécharger</message> <message id="Lms.Explore.download">Télécharger</message>
<message id="Lms.Explore.filter-added">Filtre ajouté</message> <message id="Lms.Explore.filter-added">Filtre ajouté</message>
<message id="Lms.Explore.filters">Filtres</message> <message id="Lms.Explore.filters">Filtres</message>
<message id="Lms.Explore.links">Liens</message>
<message id="Lms.Explore.most-played">Plus joués</message> <message id="Lms.Explore.most-played">Plus joués</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message> <message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message> <message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message>
-1
View File
@@ -116,7 +116,6 @@
<message id="Lms.Explore.download">Download</message> <message id="Lms.Explore.download">Download</message>
<message id="Lms.Explore.filter-added">Filtro aggiunto</message> <message id="Lms.Explore.filter-added">Filtro aggiunto</message>
<message id="Lms.Explore.filters">Filtri</message> <message id="Lms.Explore.filters">Filtri</message>
<message id="Lms.Explore.links">Collegamenti</message>
<message id="Lms.Explore.most-played">Più ascoltati</message> <message id="Lms.Explore.most-played">Più ascoltati</message>
<message id="Lms.Explore.musicbrainz-artist">Artista MusicBrainz</message> <message id="Lms.Explore.musicbrainz-artist">Artista MusicBrainz</message>
<message id="Lms.Explore.musicbrainz-release">Album MusicBrainz</message> <message id="Lms.Explore.musicbrainz-release">Album MusicBrainz</message>
-1
View File
@@ -117,7 +117,6 @@
<message id="Lms.Explore.download">下载</message> <message id="Lms.Explore.download">下载</message>
<message id="Lms.Explore.filter-added">筛选已添加</message> <message id="Lms.Explore.filter-added">筛选已添加</message>
<message id="Lms.Explore.filters">筛选</message> <message id="Lms.Explore.filters">筛选</message>
<message id="Lms.Explore.links">链接</message>
<message id="Lms.Explore.most-played">播放最多</message> <message id="Lms.Explore.most-played">播放最多</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz 歌手</message> <message id="Lms.Explore.musicbrainz-artist">MusicBrainz 歌手</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz 专辑</message> <message id="Lms.Explore.musicbrainz-release">MusicBrainz 专辑</message>
+11 -8
View File
@@ -16,11 +16,20 @@
${<if-has-release-artists>}${artists}${</if-has-release-artists>} ${<if-has-release-artists>}${artists}${</if-has-release-artists>}
${<if-has-various-release-artists>}${tr:Lms.Explore.various-artists}${</if-has-various-release-artists>} ${<if-has-various-release-artists>}${tr:Lms.Explore.various-artists}${</if-has-various-release-artists>}
<div class="small text-muted">${<if-has-year>}${year}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>} · ${</if-has-year>}${duration}</div> <div class="small text-muted">${<if-has-year>}${year}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>} · ${</if-has-year>}${duration}</div>
${clusters} ${clusters class="mb-2"}
</div> </div>
<div class="col-12"> <div class="col-12">
<div class="mb-3"> <div class="mb-3">
${play-btn class="btn btn-outline-primary me-2"}${more-btn class="btn btn-outline-primary"} ${play-btn class="btn btn-outline-primary me-2"}
<span class="dropdown">
<button class="btn btn-outline-primary" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">...</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<li>${play-shuffled class="btn dropdown-item"}</li>
${<if-has-mbid>}
<li><a href="${mbid-link}" target="_blank" class="dropdown-item">${tr:Lms.Explore.musicbrainz-release}</a></li>
${</if-has-mbid>}
</ul>
</span>
</div> </div>
${container class="d-grid gap-3"} ${container class="d-grid gap-3"}
</div> </div>
@@ -38,12 +47,6 @@
${copyright class="text-muted"} ${copyright class="text-muted"}
</div> </div>
${</if-has-copyright>} ${</if-has-copyright>}
${<if-has-mbid>}
<div class="col-12 col-md-4">
<h3>${tr:Lms.Explore.links}</h3>
${mbid-link class="text-muted"}
</div>
${</if-has-mbid>}
</div> </div>
</message> </message>
+20 -1
View File
@@ -4,7 +4,26 @@
<message id="Lms.Explore.Releases.template"> <message id="Lms.Explore.Releases.template">
<div class="mb-3"> <div class="mb-3">
${mode class="nav nav-pills mb-2"} <ul class="nav nav-pills mb-2">
<li class="nav-item">
${random class="nav-link"}
</li>
<li class="nav-item">
${starred class="nav-link"}
</li>
<li class="nav-item">
${recently-played class="nav-link"}
</li>
<li class="nav-item">
${most-played class="nav-link"}
</li>
<li class="nav-item">
${recently-added class="nav-link"}
</li>
<li class="nav-item">
${all class="nav-link"}
</li>
</ul>
${play-btn class="btn btn-outline-primary me-2"}${more-btn class="btn btn-outline-primary"} ${play-btn class="btn btn-outline-primary me-2"}${more-btn class="btn btn-outline-primary"}
</div> </div>
${releases} ${releases}
+16 -16
View File
@@ -13,7 +13,7 @@
<label class="form-label" for="${id:transcode-mode}"> <label class="form-label" for="${id:transcode-mode}">
${tr:Lms.Settings.transcode-mode} ${tr:Lms.Settings.transcode-mode}
</label> </label>
${transcode-mode} ${transcode-mode class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${transcode-mode-info} ${transcode-mode-info}
</div> </div>
@@ -22,7 +22,7 @@
<label class="form-label" for="${id:transcode-format}"> <label class="form-label" for="${id:transcode-format}">
${tr:Lms.Settings.transcode-format} ${tr:Lms.Settings.transcode-format}
</label> </label>
${transcode-format} ${transcode-format class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${transcode-format-info} ${transcode-format-info}
</div> </div>
@@ -32,7 +32,7 @@
${tr:Lms.Settings.transcode-bitrate} ${tr:Lms.Settings.transcode-bitrate}
</label> </label>
<div class="input-group mb-3"> <div class="input-group mb-3">
${transcode-bitrate} ${transcode-bitrate class="form-control"}
<span class="input-group-text">kbps</span> <span class="input-group-text">kbps</span>
</div> </div>
<div class="invalid-feedback"> <div class="invalid-feedback">
@@ -43,7 +43,7 @@
<label class="form-label" for="${id:replaygain-mode}"> <label class="form-label" for="${id:replaygain-mode}">
${tr:Lms.Settings.replaygain-mode} ${tr:Lms.Settings.replaygain-mode}
</label> </label>
${replaygain-mode} ${replaygain-mode class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${replaygain-mode-info} ${replaygain-mode-info}
</div> </div>
@@ -53,7 +53,7 @@
${tr:Lms.Settings.replaygain-preamp} ${tr:Lms.Settings.replaygain-preamp}
</label> </label>
<div class="input-group mb-3"> <div class="input-group mb-3">
${replaygain-preamp} ${replaygain-preamp class="form-control"}
<span class="input-group-text">dB</span> <span class="input-group-text">dB</span>
</div> </div>
<div class="invalid-feedback"> <div class="invalid-feedback">
@@ -65,7 +65,7 @@
${tr:Lms.Settings.replaygain-preamp-no-rg-info} ${tr:Lms.Settings.replaygain-preamp-no-rg-info}
</label> </label>
<div class="input-group mb-3"> <div class="input-group mb-3">
${replaygain-preamp-no-rg-info} ${replaygain-preamp-no-rg-info class="form-control"}
<span class="input-group-text">dB</span> <span class="input-group-text">dB</span>
</div> </div>
<div class="invalid-feedback"> <div class="invalid-feedback">
@@ -78,7 +78,7 @@
<div class="form-check"> <div class="form-check">
${subsonic-transcode-enable class="form-check-input"} ${subsonic-transcode-enable class="form-check-input"}
<label class="form-check-label" for="${id:subsonic-transcode-enable}"> <label class="form-check-label" for="${id:subsonic-transcode-enable}">
${tr:Lms.Settings.transcode-enable} ${tr:Lms.Settings.transcode-enable class="form-control"}
</label> </label>
<div class="invalid-feedback"> <div class="invalid-feedback">
${subsonic-transcode-enable-info} ${subsonic-transcode-enable-info}
@@ -89,7 +89,7 @@
<label class="form-label" for="${id:subsonic-transcode-format}"> <label class="form-label" for="${id:subsonic-transcode-format}">
${tr:Lms.Settings.transcode-format} ${tr:Lms.Settings.transcode-format}
</label> </label>
${subsonic-transcode-format} ${subsonic-transcode-format class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${subsonic-transcode-format-info class="help-block"} ${subsonic-transcode-format-info class="help-block"}
</div> </div>
@@ -99,7 +99,7 @@
${tr:Lms.Settings.transcode-bitrate} ${tr:Lms.Settings.transcode-bitrate}
</label> </label>
<div class="input-group mb-3"> <div class="input-group mb-3">
${subsonic-transcode-bitrate} ${subsonic-transcode-bitrate class="form-control"}
<span class="input-group-text">kbps</span> <span class="input-group-text">kbps</span>
</div> </div>
<div class="invalid-feedback"> <div class="invalid-feedback">
@@ -110,7 +110,7 @@
<label class="form-label" for="${id:subsonic-artist-list-mode}"> <label class="form-label" for="${id:subsonic-artist-list-mode}">
${tr:Lms.Settings.subsonic-artist-list-mode} ${tr:Lms.Settings.subsonic-artist-list-mode}
</label> </label>
${subsonic-artist-list-mode} ${subsonic-artist-list-mode class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${subsonic-artist-list-mode-info class="help-block"} ${subsonic-artist-list-mode-info class="help-block"}
</div> </div>
@@ -121,7 +121,7 @@
<label class="form-label" for="${id:scrobbler}"> <label class="form-label" for="${id:scrobbler}">
${tr:Lms.Settings.scrobbling.scrobbler} ${tr:Lms.Settings.scrobbling.scrobbler}
</label> </label>
${scrobbler} ${scrobbler class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${scrobbler-info class="help-block"} ${scrobbler-info class="help-block"}
</div> </div>
@@ -130,7 +130,7 @@
<label class="form-label" for="${id:listenbrainz-token}"> <label class="form-label" for="${id:listenbrainz-token}">
${tr:Lms.Settings.scrobbling.listenbrainz-token} ${tr:Lms.Settings.scrobbling.listenbrainz-token}
</label> </label>
${listenbrainz-token} ${listenbrainz-token class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${listenbrainz-token-info class="help-block"} ${listenbrainz-token-info class="help-block"}
</div> </div>
@@ -142,7 +142,7 @@
<label class="form-label" for="${id:password-old}"> <label class="form-label" for="${id:password-old}">
${tr:Lms.password-old} ${tr:Lms.password-old}
</label> </label>
${password-old} ${password-old class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${password-old-info class="help-block"} ${password-old-info class="help-block"}
</div> </div>
@@ -152,7 +152,7 @@
<label class="col-lg-3 control-label" for="${id:password}"> <label class="col-lg-3 control-label" for="${id:password}">
${tr:Lms.password-new} ${tr:Lms.password-new}
</label> </label>
${password} ${password class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${password-info class="help-block"} ${password-info class="help-block"}
</div> </div>
@@ -161,14 +161,14 @@
<label class="col-lg-3 control-label" for="${id:password-confirm}"> <label class="col-lg-3 control-label" for="${id:password-confirm}">
${tr:Lms.password-confirm} ${tr:Lms.password-confirm}
</label> </label>
${password-confirm} ${password-confirm class="form-control"}
<div class="invalid-feedback"> <div class="invalid-feedback">
${password-confirm-info class="help-block"} ${password-confirm-info class="help-block"}
</div> </div>
</div> </div>
${</if-has-change-password>} ${</if-has-change-password>}
<div class="col-12"> <div class="col-12">
${apply-btn class="btn-primary me-1"}${discard-btn} ${apply-btn class="btn btn-primary me-1"}${discard-btn class="btn btn-secondary"}
</div> </div>
</form> </form>
</message> </message>
+6
View File
@@ -12,6 +12,12 @@ body {
padding-bottom: 78px; padding-bottom: 78px;
} }
.Lms-notification-container {
position: fixed;
bottom: 78px;
right: 0px;
}
.Lms-player { .Lms-player {
height: 70px; height: 70px;
} }
+2
View File
@@ -2,10 +2,12 @@
add_executable(lms add_executable(lms
main.cpp main.cpp
ui/Auth.cpp ui/Auth.cpp
ui/Bootstrap5Theme.cpp
ui/LmsApplication.cpp ui/LmsApplication.cpp
ui/LmsApplicationManager.cpp ui/LmsApplicationManager.cpp
ui/LmsTheme.cpp ui/LmsTheme.cpp
ui/MediaPlayer.cpp ui/MediaPlayer.cpp
ui/ModalManager.cpp
ui/NotificationContainer.cpp ui/NotificationContainer.cpp
ui/PlayQueue.cpp ui/PlayQueue.cpp
ui/SettingsView.cpp ui/SettingsView.cpp
+77
View File
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2020 Emweb bv, Herent, Belgium.
*
* See the LICENSE file for terms of use.
*/
#include "Bootstrap5Theme.h"
#include <Wt/WApplication.h>
#include <Wt/WLinkedCssStyleSheet.h>
#include <Wt/WText.h>
namespace UserInterface
{
void
Bootstrap5Theme::init(Wt::WApplication* app) const
{
app->require(resourcesUrl() + "js/bootstrap.bundle.min.js");
Wt::WString v = app->metaHeader(Wt::MetaHeaderType::Meta, "viewport");
if (v.empty())
app->addMetaHeader("viewport", "width=device-width, initial-scale=1");
}
std::string
Bootstrap5Theme::name() const
{
return "bootstrap5";
}
std::string
Bootstrap5Theme::resourcesUrl() const
{
return Wt::WApplication::relativeResourcesUrl() + "themes/bootstrap/5/";
}
std::vector<Wt::WLinkedCssStyleSheet>
Bootstrap5Theme::styleSheets() const
{
std::vector<Wt::WLinkedCssStyleSheet> result;
const std::string themeDir {resourcesUrl()};
result.emplace_back(Wt::WLink {themeDir + "css/bootstrap.min.css"});
result.emplace_back(Wt::WLink {themeDir + "wt.css"});
return result;
}
std::string
Bootstrap5Theme::utilityCssClass(int utilityCssClassRole) const
{
switch (utilityCssClassRole) {
case Wt::ToolTipInner:
return "tooltip-inner";
case Wt::ToolTipOuter:
return "tooltip fade top in";
default:
return "";
}
}
void Bootstrap5Theme::applyValidationStyle(Wt::WWidget* widget, const Wt::WValidator::Result& validation, Wt::WFlags<Wt::ValidationStyleFlag> styles) const
{
{
const bool validStyle {(validation.state() == Wt::ValidationState::Valid) && styles.test(Wt::ValidationStyleFlag::ValidStyle)};
widget->toggleStyleClass("is-valid", validStyle);
}
{
const bool invalidStyle {(validation.state() != Wt::ValidationState::Valid) && styles.test(Wt::ValidationStyleFlag::InvalidStyle)};
widget->toggleStyleClass("is-invalid", invalidStyle);
}
}
} // namespace UserInterface
+45
View File
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2022 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WTheme.h>
namespace UserInterface
{
class Bootstrap5Theme : public Wt::WTheme
{
private:
void init(Wt::WApplication *app) const override;
std::string name() const override;
std::string resourcesUrl() const override;
std::vector<Wt::WLinkedCssStyleSheet> styleSheets() const override;
void apply(Wt::WWidget*, Wt::WWidget*, int) const override {};
void apply(Wt::WWidget*, Wt::DomElement&, int) const override {};
std::string disabledClass() const override { return "disabled"; }
std::string activeClass() const override { return "active"; };
std::string utilityCssClass(int utilityCssClassRole) const override;
bool canStyleAnchorAsButton() const override { return true; };
void applyValidationStyle(Wt::WWidget* widget,
const Wt::WValidator::Result& validation,
Wt::WFlags<Wt::ValidationStyleFlag> flags) const override;
bool canBorderBoxElement(const Wt::DomElement&) const override { return true; }
};
}
+2
View File
@@ -58,6 +58,7 @@
#include "LmsApplicationManager.hpp" #include "LmsApplicationManager.hpp"
#include "LmsTheme.hpp" #include "LmsTheme.hpp"
#include "MediaPlayer.hpp" #include "MediaPlayer.hpp"
#include "ModalManager.hpp"
#include "NotificationContainer.hpp" #include "NotificationContainer.hpp"
#include "PlayQueue.hpp" #include "PlayQueue.hpp"
#include "SettingsView.hpp" #include "SettingsView.hpp"
@@ -462,6 +463,7 @@ LmsApplication::createHome()
Wt::WTemplate* navbar {main->bindNew<Wt::WTemplate>("navbar", Wt::WString::tr("Lms.main.template.navbar"))}; Wt::WTemplate* navbar {main->bindNew<Wt::WTemplate>("navbar", Wt::WString::tr("Lms.main.template.navbar"))};
_notificationContainer = main->bindNew<NotificationContainer>("notifications"); _notificationContainer = main->bindNew<NotificationContainer>("notifications");
_modalManager = main->bindNew<ModalManager>("modal");
// MediaPlayer // MediaPlayer
_mediaPlayer = main->bindNew<MediaPlayer>("player"); _mediaPlayer = main->bindNew<MediaPlayer>("player");
+3
View File
@@ -51,6 +51,7 @@ class MediaPlayer;
class PlayQueue; class PlayQueue;
class LmsApplicationManager; class LmsApplicationManager;
class NotificationContainer; class NotificationContainer;
class ModalManager;
class LmsApplication : public Wt::WApplication class LmsApplication : public Wt::WApplication
{ {
@@ -91,6 +92,7 @@ class LmsApplication : public Wt::WApplication
MediaPlayer& getMediaPlayer() const { return *_mediaPlayer; } MediaPlayer& getMediaPlayer() const { return *_mediaPlayer; }
PlayQueue& getPlayQueue() const { return *_playQueue; } PlayQueue& getPlayQueue() const { return *_playQueue; }
ModalManager& getModalManager() const { return *_modalManager; }
// Signal emitted just before the session ends (user may already be logged out) // Signal emitted just before the session ends (user may already be logged out)
Wt::Signal<>& preQuit() { return _preQuit; } Wt::Signal<>& preQuit() { return _preQuit; }
@@ -125,6 +127,7 @@ class LmsApplication : public Wt::WApplication
PlayQueue* _playQueue {}; PlayQueue* _playQueue {};
std::unique_ptr<Wt::WPopupMenu> _popupMenu; std::unique_ptr<Wt::WPopupMenu> _popupMenu;
NotificationContainer* _notificationContainer {}; NotificationContainer* _notificationContainer {};
ModalManager* _modalManager {};
}; };
-1
View File
@@ -30,7 +30,6 @@ namespace UserInterface
static const std::vector<Wt::WLinkedCssStyleSheet> files static const std::vector<Wt::WLinkedCssStyleSheet> files
{ {
{"css/bootstrap.solar.min.css"}, {"css/bootstrap.solar.min.css"},
{"resources/themes/bootstrap/5/wt.min.css"},
{"css/lms.css"}, {"css/lms.css"},
}; };
+3 -3
View File
@@ -20,13 +20,13 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include <Wt/WBootstrap5Theme.h>
#include <Wt/WLinkedCssStyleSheet.h> #include <Wt/WLinkedCssStyleSheet.h>
#include "Bootstrap5Theme.h"
namespace UserInterface namespace UserInterface
{ {
class LmsTheme : public Wt::WBootstrap5Theme class LmsTheme : public Bootstrap5Theme
{ {
private: private:
std::vector<Wt::WLinkedCssStyleSheet> styleSheets() const override; std::vector<Wt::WLinkedCssStyleSheet> styleSheets() const override;
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2022 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ModalManager.hpp"
#include "utils/Logger.hpp"
namespace UserInterface
{
ModalManager::ModalManager()
: _closed {this, "closed"}
{
_closed.connect([=](const std::string& id)
{
LMS_LOG(UI, DEBUG) << "Received closed for id '" << id << "'";
for (int i {}; i < count(); ++i)
{
Wt::WWidget* widget {this->widget(i)};
LMS_LOG(UI, DEBUG) << "Widget " << i << ", id = '" << widget->id();
if (widget->id() == id)
{
removeWidget(widget);
break;
}
}
});
}
void
ModalManager::show(std::unique_ptr<Wt::WWidget> modalWidget)
{
assert(count() == 0);
LMS_LOG(UI, DEBUG) << "Want to show, id = " << modalWidget->id();
std::ostringstream oss;
oss
<< R"({const modalElement = )" << jsRef() << R"(.getElementsByClassName('modal')[0];)"
<< R"(const modal = bootstrap.Modal.getOrCreateInstance(modalElement);)"
<< R"(modal.show();)"
<< R"(modalElement.addEventListener('hidden.bs.modal', function () {)"
<< _closed.createCall({"'" + modalWidget->id() + "'"})
<< R"(modal.dispose();)"
<< R"(});})";
LMS_LOG(UI, DEBUG) << "Running JS '" << oss.str() << "'";
doJavaScript(oss.str());
addWidget(move(modalWidget));
}
void
ModalManager::dispose(Wt::WWidget* modalWidget)
{
std::ostringstream oss;
oss
<< R"({const modalElementParent = document.getElementById(')" << modalWidget->id() << R"(');)"
<< R"(const modalElement = modalElementParent.getElementsByClassName('modal')[0];)"
<< R"(const modal = bootstrap.Modal.getInstance(modalElement);)"
<< R"(modal.hide();)"
<< R"(})";
LMS_LOG(UI, DEBUG) << "Running JS '" << oss.str() << "'";
doJavaScript(oss.str());
}
}
+41
View File
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2022 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WContainerWidget.h>
#include <Wt/WSignal.h>
#include <Wt/WWidget.h>
namespace UserInterface
{
class ModalManager : public Wt::WContainerWidget
{
public:
ModalManager();
// should handle only one modal at a time
// Widget must contain a "modal" element
void show(std::unique_ptr<Wt::WWidget> modalWidget);
void dispose(Wt::WWidget* modalWidget);
private:
Wt::JSignal<std::string> _closed;
};
}
+19
View File
@@ -1,3 +1,22 @@
/*
* Copyright (C) 2022 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NotificationContainer.hpp" #include "NotificationContainer.hpp"
#include <sstream> #include <sstream>
+19
View File
@@ -1,3 +1,22 @@
/*
* Copyright (C) 2022 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once #pragma once
#include <chrono> #include <chrono>
+15 -13
View File
@@ -30,6 +30,7 @@
#include "utils/Service.hpp" #include "utils/Service.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
#include "ModalManager.hpp"
namespace UserInterface { namespace UserInterface {
@@ -101,14 +102,12 @@ UsersView::refreshView()
Wt::WPushButton* delBtn = entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.Admin.Users.del")); Wt::WPushButton* delBtn = entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.Admin.Users.del"));
delBtn->clicked().connect([=] delBtn->clicked().connect([=]
{ {
auto msgBox = delBtn->addChild(std::make_unique<Wt::WMessageBox>(Wt::WString::tr("Lms.Admin.Users.del-user"), auto modal {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.delete-user"))};
Wt::WString::tr("Lms.Admin.Users.del-user-name"), modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
Wt::Icon::Warning, Wt::StandardButton::Yes | Wt::StandardButton::No)); Wt::WWidget* modalPtr {modal.get()};
msgBox->setModal(true); auto* delBtn {modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.Admin.Users.del"))};
msgBox->buttonClicked().connect([=] (Wt::StandardButton btn) delBtn->clicked().connect([=]
{
if (btn == Wt::StandardButton::Yes)
{ {
auto transaction {LmsApp->getDbSession().createUniqueTransaction()}; auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
@@ -117,14 +116,17 @@ UsersView::refreshView()
user.remove(); user.remove();
_container->removeWidget(entry); _container->removeWidget(entry);
}
else LmsApp->getModalManager().dispose(modalPtr);
{
delBtn->removeChild(msgBox);
}
}); });
msgBox->show(); auto* cancelBtn {modal->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel"))};
cancelBtn->clicked().connect([=]
{
LmsApp->getModalManager().dispose(modalPtr);
});
LmsApp->getModalManager().show(std::move(modal));
}); });
} }
} }
+5 -8
View File
@@ -168,10 +168,11 @@ Release::refreshView()
} }
{ {
Wt::WPushButton* moreBtn {bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML)}; Wt::WPushButton* playShuffled {bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)};
moreBtn->clicked().connect([=] playShuffled->setDefault(false);
playShuffled->clicked().connect([=]
{ {
displayReleasePopupMenu(*moreBtn, *releaseId, releasesAction); releasesAction.emit(PlayQueueAction::PlayShuffled, {*releaseId});
}); });
} }
@@ -351,11 +352,7 @@ Release::refreshLinks(const Database::Release::pointer& release)
if (mbid) if (mbid)
{ {
setCondition("if-has-mbid", true); setCondition("if-has-mbid", true);
bindString("mbid-link", std::string {"https://musicbrainz.org/release/"} + std::string {mbid->getAsString()});
Wt::WLink link {"https://musicbrainz.org/release/" + std::string {mbid->getAsString()}};
link.setTarget(Wt::LinkTarget::NewWindow);
bindNew<Wt::WAnchor>("mbid-link", link, Wt::WString::tr("Lms.Explore.musicbrainz-release"));
} }
} }
+19 -14
View File
@@ -41,25 +41,30 @@ Releases::Releases(Filters& filters)
{ {
addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("tr", &Wt::WTemplate::Functions::tr);
auto bindMenuItem {[this](const std::string& var, const Wt::WString& title, ReleaseCollector::Mode mode)
{ {
auto* menu {bindNew<Wt::WMenu>("mode")}; auto *menuItem {bindNew<Wt::WPushButton>(var, title)};
menuItem->clicked().connect([=]
auto addItem = [this](Wt::WMenu& menu,const Wt::WString& str, ReleaseCollector::Mode mode)
{ {
auto* item {menu.addItem(str)}; refreshView(mode);
item->clicked().connect([this, mode] { refreshView(mode); }); _currentActiveItem->removeStyleClass("active");
menuItem->addStyleClass("active");
_currentActiveItem = menuItem;
});
if (mode == _defaultMode) if (mode == _defaultMode)
item->renderSelected(true); {
}; _currentActiveItem = menuItem;
_currentActiveItem->addStyleClass("active");
addItem(*menu, Wt::WString::tr("Lms.Explore.random"), ReleaseCollector::Mode::Random);
addItem(*menu, Wt::WString::tr("Lms.Explore.starred"), ReleaseCollector::Mode::Starred);
addItem(*menu, Wt::WString::tr("Lms.Explore.recently-played"), ReleaseCollector::Mode::RecentlyPlayed);
addItem(*menu, Wt::WString::tr("Lms.Explore.most-played"), ReleaseCollector::Mode::MostPlayed);
addItem(*menu, Wt::WString::tr("Lms.Explore.recently-added"), ReleaseCollector::Mode::RecentlyAdded);
addItem(*menu, Wt::WString::tr("Lms.Explore.all"), ReleaseCollector::Mode::All);
} }
}};
bindMenuItem("random", Wt::WString::tr("Lms.Explore.random"), ReleaseCollector::Mode::Random);
bindMenuItem("starred", Wt::WString::tr("Lms.Explore.starred"), ReleaseCollector::Mode::Starred);
bindMenuItem("recently-played", Wt::WString::tr("Lms.Explore.recently-played"), ReleaseCollector::Mode::RecentlyPlayed);
bindMenuItem("most-played", Wt::WString::tr("Lms.Explore.most-played"), ReleaseCollector::Mode::MostPlayed);
bindMenuItem("recently-added", Wt::WString::tr("Lms.Explore.recently-added"), ReleaseCollector::Mode::RecentlyAdded);
bindMenuItem("all", Wt::WString::tr("Lms.Explore.all"), ReleaseCollector::Mode::All);
Wt::WPushButton* playBtn {bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)}; Wt::WPushButton* playBtn {bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
playBtn->clicked().connect([this] playBtn->clicked().connect([this]
+1
View File
@@ -51,6 +51,7 @@ namespace UserInterface
static constexpr std::size_t _batchSize {_maxItemsPerLine}; static constexpr std::size_t _batchSize {_maxItemsPerLine};
static constexpr std::size_t _maxCount {_maxItemsPerLine * 32}; static constexpr std::size_t _maxCount {_maxItemsPerLine * 32};
Wt::WWidget* _currentActiveItem {};
InfiniteScrollingContainer* _container {}; InfiniteScrollingContainer* _container {};
ReleaseCollector _releaseCollector; ReleaseCollector _releaseCollector;
static constexpr ReleaseCollector::Mode _defaultMode {ReleaseCollector::Mode::Random}; static constexpr ReleaseCollector::Mode _defaultMode {ReleaseCollector::Mode::Random};