From 503309890fd13941c2fef3f5d8032d870e550060 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 8 Jun 2026 04:13:17 +0300 Subject: [PATCH] integrate withLoadMore HOC into List --- src/components/list/list.css | 58 ++++++ src/components/list/list.js | 152 ++++++++++++++ src/components/list/list.vue | 116 +++++++---- .../selectable_list/selectable_list.vue | 2 +- .../helpers/vertical_tab_switcher.scss | 7 + .../tabs/mutes_and_blocks_tab.js | 45 ++-- .../tabs/mutes_and_blocks_tab.scss | 10 +- .../tabs/mutes_and_blocks_tab.vue | 195 +++++++++--------- src/components/tab_switcher/tab_switcher.scss | 1 + src/components/user_profile/user_profile.js | 40 ++-- src/components/user_profile/user_profile.vue | 14 +- src/hocs/with_load_more/with_load_more.jsx | 119 ----------- src/hocs/with_load_more/with_load_more.scss | 16 -- .../with_subscription/with_subscription.jsx | 94 --------- .../with_subscription/with_subscription.scss | 10 - src/i18n/en.json | 4 + 16 files changed, 449 insertions(+), 434 deletions(-) create mode 100644 src/components/list/list.css create mode 100644 src/components/list/list.js delete mode 100644 src/hocs/with_load_more/with_load_more.jsx delete mode 100644 src/hocs/with_load_more/with_load_more.scss delete mode 100644 src/hocs/with_subscription/with_subscription.jsx delete mode 100644 src/hocs/with_subscription/with_subscription.scss diff --git a/src/components/list/list.css b/src/components/list/list.css new file mode 100644 index 000000000..84b737ed2 --- /dev/null +++ b/src/components/list/list.css @@ -0,0 +1,58 @@ +.List { + --__line-height: 1.5em; + --__horizontal-gap: 0.75em; + --__vertical-gap: 0.5em; + + display: flex; + flex-direction: column; + + .list { + flex: 1; + } + + .list-item { + display: flex; + align-items: center; + + &:not(:last-child) { + border-bottom: 1px dotted var(--border); + } + } + + .header { + display: flex; + align-items: center; + padding: var(--__vertical-gap) var(--__horizontal-gap); + border-bottom: 1px solid; + border-bottom-color: var(--border); + + .actions { + flex: 1; + } + } + + .footer { + padding: 0.9em; + text-align: center; + + a { + cursor: pointer; + } + } + + .checkbox-wrapper { + padding-right: var(--__horizontal-gap); + flex: none; + } + + &.-scrollable { + overflow-y: hidden; + display: flex; + flex-direction: column; + + .list { + overflow-y: auto; + flex: 1 1 auto; + } + } +} diff --git a/src/components/list/list.js b/src/components/list/list.js new file mode 100644 index 000000000..600ad8802 --- /dev/null +++ b/src/components/list/list.js @@ -0,0 +1,152 @@ +import { isEmpty } from 'lodash' + +import Checkbox from 'src/components/checkbox/checkbox.vue' + +const List = { + props: { + boxOnly: { + type: Boolean, + default: false, + }, + items: { + type: Array, + default: () => [], + }, + fetchFunction: { + type: Function, + default: null, + }, + itemsFunction: { + type: Function, + default: null, + }, + getKey: { + type: Function, + default: (item) => item.id, + }, + getClass: { + type: Function, + default: () => '', + }, + nonInteractive: { + type: Boolean, + default: false, + }, + scrollable: { + type: Boolean, + default: false, + }, + selectable: { + type: Boolean, + default: false, + }, + }, + components: { + Checkbox, + }, + data() { + return { + loading: false, + bottomedOut: false, + error: false, + dynamicItems: this.itemsFunction ? [] : null, + selected: [], + } + }, + computed: { + allKeys() { + return this.actualItems.map(this.getKey) + }, + filteredSelected() { + return this.allKeys.filter((key) => this.selected.indexOf(key) !== -1) + }, + allSelected() { + return this.filteredSelected.length === this.actualItems.length + }, + noneSelected() { + return this.filteredSelected.length === 0 + }, + someSelected() { + return !this.allSelected && !this.noneSelected + }, + actualItems() { + return this.dynamicItems || this.actualItems + }, + }, + created() { + if (this.fetchFunction) { + window.addEventListener('scroll', this.scrollLoad) + + if (this.dynamicItems.length === 0) { + this.fetchEntries() + } + } + }, + unmounted() { + window.removeEventListener('scroll', this.scrollLoad) + }, + methods: { + // Entries is not a computed because computed can't track the dynamic + // selector for changes and won't trigger after fetch. + updateEntries(newEntries) { + this.dynamicItems = this.itemsFunction(newEntries) + }, + fetchEntries() { + if (!this.loading) { + this.loading = true + this.error = false + this.fetchFunction() + .then((newEntries) => { + this.loading = false + this.bottomedOut = isEmpty(newEntries) + return newEntries + }) + .catch((error) => { + this.loading = false + this.error = error + }) + .finally((newEntries) => { + this.updateEntries(newEntries) + }) + } + }, + scrollLoad(e) { + if (this.fetchFunction) { + const bodyBRect = document.body.getBoundingClientRect() + const height = Math.max(bodyBRect.height, -bodyBRect.y) + if ( + this.loading === false && + this.bottomedOut === false && + this.$el.offsetHeight > 0 && + window.innerHeight + window.pageYOffset >= height - 750 + ) { + this.fetchEntries() + } + } + }, + isSelected(item) { + return this.filteredSelected.indexOf(this.getKey(item)) !== -1 + }, + toggle(checked, item) { + console.log('TOGGLE', checked, item) + const key = this.getKey(item) + const oldChecked = this.isSelected(key) + if (checked !== oldChecked) { + if (checked) { + this.selected.push(key) + } else { + this.selected.splice(this.selected.indexOf(key), 1) + } + } + }, + toggleAll(value) { + if (value) { + this.selected = this.allKeys.slice(0) + } else { + this.selected = [] + } + }, + }, +} + +export default List diff --git a/src/components/list/list.vue b/src/components/list/list.vue index 64f6520d3..5703e2b0d 100644 --- a/src/components/list/list.vue +++ b/src/components/list/list.vue @@ -1,49 +1,91 @@ - + + + diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue index 80e35efb5..b760508d1 100644 --- a/src/components/selectable_list/selectable_list.vue +++ b/src/components/selectable_list/selectable_list.vue @@ -87,7 +87,7 @@ flex-direction: column; .list { - flex: 1 1 0; + flex: 1; } &-item-inner { diff --git a/src/components/settings_modal/helpers/vertical_tab_switcher.scss b/src/components/settings_modal/helpers/vertical_tab_switcher.scss index 67da9305b..4c0e94dff 100644 --- a/src/components/settings_modal/helpers/vertical_tab_switcher.scss +++ b/src/components/settings_modal/helpers/vertical_tab_switcher.scss @@ -40,6 +40,7 @@ .tab-slot-wrapper { flex: 1 1 auto; + position: relative; height: 100%; padding: 0 1em; overflow-y: auto; @@ -56,10 +57,16 @@ } &.-full-height { + height: 100%; > * { height: 100%; } } + + &.-full-width.-full-height { + position: absolute; + inset: 0; + } } } diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js index 3b82bbb4c..0f1dafee5 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -1,11 +1,11 @@ import { get, map, reject } from 'lodash' -import withLoadMore from 'src/components/../hocs/with_load_more/with_load_more' import withSubscription from 'src/components/../hocs/with_subscription/with_subscription' import Autosuggest from 'src/components/autosuggest/autosuggest.vue' import BlockCard from 'src/components/block_card/block_card.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' import DomainMuteCard from 'src/components/domain_mute_card/domain_mute_card.vue' +import List from 'src/components/list/list.vue' import MuteCard from 'src/components/mute_card/mute_card.vue' import ProgressButton from 'src/components/progress_button/progress_button.vue' import SelectableList from 'src/components/selectable_list/selectable_list.vue' @@ -14,32 +14,6 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js' -const BlockList = withLoadMore({ - fetch: (props, $store) => $store.dispatch('fetchBlocks'), - select: (props, $store) => - get($store.state.users.currentUser, 'blockIds', []), - destroy: () => { - /* no-op */ - }, - childPropName: 'items', -})(SelectableList) - -const MuteList = withLoadMore({ - fetch: (props, $store) => $store.dispatch('fetchMutes'), - select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []), - destroy: () => { - /* no-op */ - }, - childPropName: 'items', -})(SelectableList) - -const DomainMuteList = withSubscription({ - fetch: (props, $store) => $store.dispatch('fetchDomainMutes'), - select: (props, $store) => - get($store.state.users.currentUser, 'domainMutes', []), - childPropName: 'items', -})(SelectableList) - const MutesAndBlocks = { data() { return { @@ -52,12 +26,10 @@ const MutesAndBlocks = { }, components: { TabSwitcher, - BlockList, - MuteList, - DomainMuteList, - BlockCard, - MuteCard, DomainMuteCard, + BlockCard, + List, + MuteCard, ProgressButton, Autosuggest, Checkbox, @@ -102,6 +74,15 @@ const MutesAndBlocks = { }) .join('\n') }, + getBlocks() { + return get(this.$store.state.users.currentUser, 'blockIds', []) + }, + getMutes() { + return get(this.$store.state.users.currentUser, 'muteIds', []) + }, + getDomainMutes() { + return get(this.$store.state.users.currentUser, 'domainMutes', []) + }, activateTab(tabName) { this.activeTab = tabName }, diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss index 5fa3a27b1..b179c11a4 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss @@ -1,5 +1,5 @@ .mutes-and-blocks-tab { - height: 100%; + min-height: 100%; .usersearch-wrapper { padding: 1em; @@ -26,4 +26,12 @@ margin-top: 1em; width: 10em; } + + .blocks, .mutes { + display: flex; + flex-direction: column; + min-height: 100%; + position: absolute; + inset: 0; + } } diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue index ed4b15a49..992c68007 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue @@ -1,9 +1,12 @@