-
+
diff --git a/src/renderer/components/playlist-info/playlist-info.js b/src/renderer/components/playlist-info/playlist-info.js
index 3afaeea488ae1..d1d1f2e9066ed 100644
--- a/src/renderer/components/playlist-info/playlist-info.js
+++ b/src/renderer/components/playlist-info/playlist-info.js
@@ -40,6 +40,10 @@ export default defineComponent({
type: String,
required: true,
},
+ theme: {
+ type: String,
+ default: 'base'
+ },
title: {
type: String,
required: true,
diff --git a/src/renderer/components/playlist-info/playlist-info.scss b/src/renderer/components/playlist-info/playlist-info.scss
index 10bf1e6d749d8..a429360c0c355 100644
--- a/src/renderer/components/playlist-info/playlist-info.scss
+++ b/src/renderer/components/playlist-info/playlist-info.scss
@@ -6,6 +6,7 @@
cursor: pointer;
}
+
.playlistThumbnail img {
inline-size: 100%;
// Ensure placeholder image displayed at same aspect ratio as most other images
@@ -33,6 +34,7 @@
max-block-size: 20vh;
overflow-y: auto;
white-space: break-spaces;
+ margin-inline: auto;
@media only screen and (width <= 500px) {
max-block-size: 10vh;
@@ -77,6 +79,53 @@
margin-block-start: 8px;
}
+.top-bar {
+ .playlistThumbnail, .playlistInfoSeparator {
+ display: none;
+ }
+
+ .playlistStats {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ }
+
+ .playlistTitle {
+ margin-block-start: 0.2em;
+ }
+
+ .playlistTitle, .playlistDescription {
+ overflow-x: hidden;
+ text-align: center;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ max-inline-size: 750px;
+ inline-size: 100%;
+ margin-block: 8px;
+ }
+
+ .descriptionInput {
+ margin-block-start: 8px;
+ }
+
+ .playlistOptionsAndSearch {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .channelShareWrapper, .searchInputsRow, .playlistStats {
+ column-gap: 0;
+ inline-size: 100%;
+ }
+
+ .inputElement {
+ inline-size: 100%;
+ }
+}
+
@media only screen and (width <= 1250px) {
:deep(.sharePlaylistIcon .iconDropdown) {
inset-inline: auto auto;
diff --git a/src/renderer/components/playlist-info/playlist-info.vue b/src/renderer/components/playlist-info/playlist-info.vue
index 384882041c9d8..4359a53442f65 100644
--- a/src/renderer/components/playlist-info/playlist-info.vue
+++ b/src/renderer/components/playlist-info/playlist-info.vue
@@ -1,5 +1,8 @@
-
+
@@ -34,6 +37,7 @@
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ updateQueryDebounce(input)"
+ @clear="updateQueryDebounce('')"
+ />
+
-
-
-
- updateQueryDebounce(input)"
- @clear="updateQueryDebounce('')"
- />
-
diff --git a/src/renderer/components/top-nav/top-nav.js b/src/renderer/components/top-nav/top-nav.js
index 8e9d2433c4f8e..29d891ec21ec6 100644
--- a/src/renderer/components/top-nav/top-nav.js
+++ b/src/renderer/components/top-nav/top-nav.js
@@ -4,7 +4,7 @@ import FtInput from '../ft-input/ft-input.vue'
import FtProfileSelector from '../ft-profile-selector/ft-profile-selector.vue'
import debounce from 'lodash.debounce'
-import { IpcChannels } from '../../../constants'
+import { IpcChannels, MOBILE_WIDTH_THRESHOLD } from '../../../constants'
import { openInternalPath } from '../../helpers/utils'
import { translateWindowTitle } from '../../helpers/strings'
import { clearLocalSearchSuggestionsSession, getLocalSearchSuggestions } from '../../helpers/api/local'
@@ -98,7 +98,7 @@ export default defineComponent({
},
mounted: function () {
let previousWidth = window.innerWidth
- if (window.innerWidth <= 680) {
+ if (window.innerWidth <= MOBILE_WIDTH_THRESHOLD) {
this.showSearchContainer = false
}
@@ -113,7 +113,7 @@ export default defineComponent({
// Don't change the status of showSearchContainer if only the height of the window changes
// Opening the virtual keyboard can trigger this resize event, but it won't change the width
if (previousWidth !== window.innerWidth) {
- this.showSearchContainer = window.innerWidth > 680
+ this.showSearchContainer = window.innerWidth > MOBILE_WIDTH_THRESHOLD
previousWidth = window.innerWidth
}
})
@@ -124,7 +124,7 @@ export default defineComponent({
goToSearch: async function (query, { event }) {
const doCreateNewWindow = event && event.shiftKey
- if (window.innerWidth <= 680) {
+ if (window.innerWidth <= MOBILE_WIDTH_THRESHOLD) {
this.$refs.searchContainer.blur()
this.showSearchContainer = false
} else {
diff --git a/src/renderer/scss-partials/_utils.scss b/src/renderer/scss-partials/_utils.scss
new file mode 100644
index 0000000000000..0bbe0adfa8d1d
--- /dev/null
+++ b/src/renderer/scss-partials/_utils.scss
@@ -0,0 +1,46 @@
+@mixin is-side-nav-open {
+ @at-root {
+ .isSideNavOpen &,
+ .isSideNavOpen#{&} {
+ @content;
+ }
+ }
+}
+
+@mixin are-side-bar-labels-hidden {
+ @at-root {
+ .hideLabelsSideBar &,
+ .hideLabelsSideBar#{&} {
+ @content;
+ }
+ }
+}
+
+@mixin fixed-top-bar {
+ position: fixed;
+ inset-block-start: 60px;
+ inset-inline-end: 0;
+ inline-size: calc(100% - 80px);
+ margin-inline: 0;
+ z-index: 3;
+
+ @include is-side-nav-open {
+ inline-size: calc(100% - 200px);
+ }
+
+ @include are-side-bar-labels-hidden {
+ inline-size: calc(100% - 60px);
+ }
+
+ @media only screen and (width <= 680px) {
+ inline-size: 100%;
+
+ @include is-side-nav-open {
+ inline-size: 100%;
+ }
+
+ @include are-side-bar-labels-hidden {
+ inline-size: 100%;
+ }
+ }
+}
diff --git a/src/renderer/themes.css b/src/renderer/themes.css
index c088bddaa2bd6..6d7387ba6e156 100644
--- a/src/renderer/themes.css
+++ b/src/renderer/themes.css
@@ -10,6 +10,14 @@
.solarizedDark,
.solarizedLight {
--primary-input-color: rgb(0 0 0 / 50%);
+ --top-bar-push-down-adjustment-default: -35px;
+
+ /* Value of 0 without 'px' does not work inside calc() */
+ /* stylelint-disable length-zero-no-unit */
+ --top-bar-push-down-adjustment-no-description: 0px;
+ --top-bar-push-down-adjustment-edit-mode: 0px;
+ --top-bar-push-down-adjustment-one-or-fewer: 0px;
+ /* stylelint-enable length-zero-no-unit */
--destructive-color: #f44336;
--destructive-text-color: #000;
--destructive-hover-color: #e53935;
diff --git a/src/renderer/views/Playlist/Playlist.js b/src/renderer/views/Playlist/Playlist.js
index 925b7cf858004..18095b5e917e0 100644
--- a/src/renderer/views/Playlist/Playlist.js
+++ b/src/renderer/views/Playlist/Playlist.js
@@ -7,6 +7,7 @@ import PlaylistInfo from '../../components/playlist-info/playlist-info.vue'
import FtListVideoNumbered from '../../components/ft-list-video-numbered/ft-list-video-numbered.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import FtButton from '../../components/ft-button/ft-button.vue'
+import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
import FtSelect from '../../components/ft-select/ft-select.vue'
import FtAutoLoadNextPageWrapper from '../../components/ft-auto-load-next-page-wrapper/ft-auto-load-next-page-wrapper.vue'
import {
@@ -21,6 +22,7 @@ import {
showToast,
} from '../../helpers/utils'
import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
+import { MOBILE_WIDTH_THRESHOLD, PLAYLIST_HEIGHT_FORCE_LIST_THRESHOLD } from '../../../constants'
const SORT_BY_VALUES = {
DateAddedNewest: 'date_added_descending',
@@ -41,6 +43,7 @@ export default defineComponent({
'ft-list-video-numbered': FtListVideoNumbered,
'ft-flex-box': FtFlexBox,
'ft-button': FtButton,
+ 'ft-element-list': FtElementList,
'ft-select': FtSelect,
'ft-auto-load-next-page-wrapper': FtAutoLoadNextPageWrapper,
},
@@ -78,6 +81,7 @@ export default defineComponent({
isLoadingMore: false,
getPlaylistInfoDebounce: function() {},
playlistInEditMode: false,
+ forceListView: false,
videoSearchQuery: '',
@@ -106,6 +110,9 @@ export default defineComponent({
playlistId: function() {
return this.$route.params.id
},
+ listType: function () {
+ return this.isUserPlaylistRequested && !this.forceListView ? this.$store.getters.getListType : 'list'
+ },
userPlaylistsReady: function () {
return this.$store.getters.getPlaylistsReady
},
@@ -285,6 +292,11 @@ export default defineComponent({
},
mounted: function () {
this.getPlaylistInfoDebounce()
+ this.handleResize()
+ window.addEventListener('resize', this.handleResize)
+ },
+ beforeDestroy: function () {
+ window.removeEventListener('resize', this.handleResize)
},
methods: {
getPlaylistInfo: function () {
@@ -562,6 +574,10 @@ export default defineComponent({
}
},
+ handleResize: function () {
+ this.forceListView = window.innerWidth <= MOBILE_WIDTH_THRESHOLD || window.innerHeight <= PLAYLIST_HEIGHT_FORCE_LIST_THRESHOLD
+ },
+
getIconForSortPreference: (s) => getIconForSortPreference(s),
...mapActions([
diff --git a/src/renderer/views/Playlist/Playlist.scss b/src/renderer/views/Playlist/Playlist.scss
index 1bf2cc61366a0..21f9db966e9d6 100644
--- a/src/renderer/views/Playlist/Playlist.scss
+++ b/src/renderer/views/Playlist/Playlist.scss
@@ -1,37 +1,90 @@
-.routerView {
+@use '../../scss-partials/utils';
+
+.playlistItemsCard {
display: flex;
+ flex-direction: column;
+ grid-gap: 10px;
+ margin: 0;
}
-.playlistInfo {
- background-color: var(--card-bg-color);
+.playlistInfoContainer {
box-sizing: border-box;
- block-size: calc(100vh - 132px);
- margin-inline-end: 1em;
- padding: 10px;
- position: sticky;
- inset-block-start: 96px;
-
- /* This is needed to make prompt always above video entries */
- /* Value being too high would block search suggestions */
+ /* This is needed to make prompt always above video entries. Value being too high would block search suggestions */
z-index: 1;
- inline-size: 30%;
&.promptOpen {
- // Otherwise sidebar would be above the prompt
z-index: 200;
+ // Otherwise sidebar would be above the prompt
}
}
-.playlistItems {
+.routerView {
display: flex;
- flex-direction: column;
- grid-gap: 10px;
- margin: 0;
- padding: 10px;
- inline-size: 60%;
+
+ &.grid {
+ flex-direction: column;
+
+ &.hasNoPlaylistDescription {
+ --top-bar-push-down-adjustment-no-description: 19px;
+ }
+
+ &.playlistInEditMode {
+ --top-bar-push-down-adjustment-edit-mode: 85px;
+ --top-bar-push-down-adjustment-no-description: 11px;
+ }
+
+ &.oneOrFewer {
+ --top-bar-push-down-adjustment-one-or-fewer: -62px;
+ }
+
+ .playlistInfoContainer {
+ position: sticky;
+ margin-block: -35px 16px;
+ inset-block-start: calc(var(--top-bar-push-down-adjustment-default) + var(--top-bar-push-down-adjustment-edit-mode) + var(--top-bar-push-down-adjustment-no-description) + var(--top-bar-push-down-adjustment-one-or-fewer));
+ padding-block: 0;
+ margin-inline: auto;
+ padding-inline: 16px;
+ box-sizing: content-box;
+ inline-size: 85%;
+ background-color: var(--card-bg-color);
+ box-shadow: 0 2px 1px 0 var(--primary-shadow-color);
+
+ .playlistInfo {
+ padding-block: 10px;
+ padding-inline: 16px;
+ }
+ }
+
+ .playlistItemsCard {
+ inline-size: 85%;
+ margin-block: 0 60px;
+ margin-inline: auto;
+ }
+ }
+
+ &.list {
+ .playlistInfoContainer {
+ background-color: var(--card-bg-color);
+ block-size: calc(100vh - 132px);
+ inline-size: 30%;
+ inset-block-start: 96px;
+ margin-inline-end: 1em;
+ position: sticky;
+
+ .playlistInfo {
+ padding: 10px;
+ }
+ }
+
+ .playlistItemsCard {
+ inline-size: 60%;
+ padding: 10px;
+ }
+ }
}
+
.playlistItem {
display: grid;
grid-template-columns: 30px 1fr;
@@ -39,13 +92,12 @@
align-items: center;
}
-.playlistItem-move ,
+.playlistItem-move,
.playlistItem-enter-active,
.playlistItem-leave-active {
transition: all 0.2s ease;
// Hide action buttons during transitions
- //
// The class for icon container is mainly styled in `ft-list-item.scss`
// But the transition related classes are all on container elements
// So `:deep` is used
@@ -81,20 +133,21 @@
@media only screen and (width <= 850px) {
.routerView {
flex-direction: column;
- }
-
- .playlistInfo {
- box-sizing: border-box;
- position: relative;
- inset-block-start: 0;
- z-index: 1;
- block-size: auto;
- inline-size: 100%;
- }
- .playlistItems {
- box-sizing: border-box;
- inline-size: 100%;
+ &.list {
+ .playlistInfoContainer {
+ box-sizing: border-box;
+ position: relative;
+ inset-block-start: 0;
+ block-size: auto;
+ inline-size: 100%;
+ }
+
+ .playlistItemsCard {
+ box-sizing: border-box;
+ inline-size: 100%;
+ }
+ }
}
}
diff --git a/src/renderer/views/Playlist/Playlist.vue b/src/renderer/views/Playlist/Playlist.vue
index 8cb3fc59273e0..c7e6efb8b30af 100644
--- a/src/renderer/views/Playlist/Playlist.vue
+++ b/src/renderer/views/Playlist/Playlist.vue
@@ -1,44 +1,51 @@
-
+
-
-
videoSearchQuery = v"
- @prompt-open="promptOpen = true"
- @prompt-close="promptOpen = false"
- />
+ >
+ videoSearchQuery = v"
+ @prompt-open="promptOpen = true"
+ @prompt-close="promptOpen = false"
+ />
+
+