mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 12:36:57 +00:00
restore following feed deck UI
This commit is contained in:
parent
86bfafe27f
commit
b4e3062083
4 changed files with 40 additions and 35 deletions
|
@ -4,7 +4,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { notificationTypes } from 'misskey-js';
|
import { notificationTypes } from 'misskey-js';
|
||||||
import { ref } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { i18n } from './i18n.js';
|
import { i18n } from './i18n.js';
|
||||||
import type { BasicTimelineType } from '@/timelines.js';
|
import type { BasicTimelineType } from '@/timelines.js';
|
||||||
|
@ -320,6 +321,12 @@ export function updateColumn(id: Column['id'], column: Partial<Column>) {
|
||||||
saveCurrentDeckProfile();
|
saveCurrentDeckProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getColumn<TColumn extends Column>(id: Column['id']): Ref<TColumn> {
|
||||||
|
return computed(() => {
|
||||||
|
return columns.value.find(c => c.id === id) as TColumn;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function switchProfileMenu(ev: MouseEvent) {
|
export function switchProfileMenu(ev: MouseEvent) {
|
||||||
const items: MenuItem[] = prefer.s['deck.profile'] ? [{
|
const items: MenuItem[] = prefer.s['deck.profile'] ? [{
|
||||||
text: prefer.s['deck.profile'],
|
text: prefer.s['deck.profile'],
|
||||||
|
|
|
@ -18,25 +18,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, shallowRef } from 'vue';
|
import { computed, shallowRef } from 'vue';
|
||||||
import type { Column } from '@/ui/deck/deck-store.js';
|
import type { Column } from '@/deck.js';
|
||||||
import type { FollowingFeedState } from '@/utility/following-feed-utils.js';
|
import type { FollowingFeedState } from '@/utility/following-feed-utils.js';
|
||||||
export type FollowingColumn = Column & Partial<FollowingFeedState>;
|
export type FollowingColumn = Column & Partial<FollowingFeedState>;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getColumn, getReactiveColumn, updateColumn } from '@/ui/deck/deck-store.js';
|
import type { FollowingFeedTab } from '@/utility/following-feed-utils.js';
|
||||||
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
import { getColumn, updateColumn } from '@/deck.js';
|
||||||
import XColumn from '@/ui/deck/column.vue';
|
import XColumn from '@/ui/deck/column.vue';
|
||||||
import SkFollowingRecentNotes from '@/components/SkFollowingRecentNotes.vue';
|
import SkFollowingRecentNotes from '@/components/SkFollowingRecentNotes.vue';
|
||||||
import SkRemoteFollowersWarning from '@/components/SkRemoteFollowersWarning.vue';
|
import SkRemoteFollowersWarning from '@/components/SkRemoteFollowersWarning.vue';
|
||||||
import { createModel, createOptionsMenu, FollowingFeedTab, followingTab, followingTabName, followingTabIcon, followingFeedTabs } from '@/utility/following-feed-utils.js';
|
import { createModel, createOptionsMenu, followingTab, followingTabName, followingTabIcon, followingFeedTabs } from '@/utility/following-feed-utils.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { MenuItem } from '@/types/menu.js';
|
import { useRouter } from '@/router.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
column: FollowingColumn;
|
column: FollowingColumn;
|
||||||
isStacked: boolean;
|
isStacked: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const columnIcon = computed(() => followingTabIcon(props.column.userList));
|
const columnIcon = computed(() => followingTabIcon(props.column.userList));
|
||||||
|
@ -53,10 +54,10 @@ async function selectList(): Promise<void> {
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
await updateColumn(props.column.id, {
|
updateColumn(props.column.id, {
|
||||||
name: getNewColumnName(newList),
|
name: getNewColumnName(newList),
|
||||||
userList: newList,
|
userList: newList,
|
||||||
});
|
} as Partial<FollowingColumn>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNewColumnName(newList: FollowingFeedTab) {
|
function getNewColumnName(newList: FollowingFeedTab) {
|
||||||
|
@ -78,7 +79,6 @@ if (!props.column.userList) {
|
||||||
// This allows multiple columns to exist with different settings.
|
// This allows multiple columns to exist with different settings.
|
||||||
const columnStorage = computed(() => ({
|
const columnStorage = computed(() => ({
|
||||||
state: getColumn<FollowingColumn>(props.column.id),
|
state: getColumn<FollowingColumn>(props.column.id),
|
||||||
reactiveState: getReactiveColumn<FollowingColumn>(props.column.id),
|
|
||||||
save(updated: FollowingColumn) {
|
save(updated: FollowingColumn) {
|
||||||
updateColumn(props.column.id, updated);
|
updateColumn(props.column.id, updated);
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { computed, Ref, WritableComputedRef } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { defaultStore } from '@/store.js';
|
import type { Ref, WritableComputedRef } from 'vue';
|
||||||
|
import type { PageHeaderItem } from '@/types/page-header.js';
|
||||||
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import { deepMerge } from '@/utility/merge.js';
|
import { deepMerge } from '@/utility/merge.js';
|
||||||
import { PageHeaderItem } from '@/types/page-header.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { popupMenu } from '@/os.js';
|
import { popupMenu } from '@/os.js';
|
||||||
import { MenuItem } from '@/types/menu.js';
|
import { prefer } from '@/preferences';
|
||||||
|
|
||||||
export const followingTab = 'following' as const;
|
export const followingTab = 'following' as const;
|
||||||
export const mutualsTab = 'mutuals' as const;
|
export const mutualsTab = 'mutuals' as const;
|
||||||
|
@ -34,7 +35,7 @@ export function followingTabIcon(tab: FollowingFeedTab | null | undefined): stri
|
||||||
|
|
||||||
export type FollowingFeedModel = {
|
export type FollowingFeedModel = {
|
||||||
[Key in keyof FollowingFeedState]: WritableComputedRef<FollowingFeedState[Key]>;
|
[Key in keyof FollowingFeedState]: WritableComputedRef<FollowingFeedState[Key]>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface FollowingFeedState {
|
export interface FollowingFeedState {
|
||||||
withNonPublic: boolean,
|
withNonPublic: boolean,
|
||||||
|
@ -56,10 +57,9 @@ export const defaultFollowingFeedState: FollowingFeedState = {
|
||||||
remoteWarningDismissed: false,
|
remoteWarningDismissed: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface StorageInterface<T extends Partial<FollowingFeedState> = Partial<FollowingFeedState>> {
|
interface StorageInterface {
|
||||||
readonly state: Partial<T>;
|
readonly state: Ref<Partial<FollowingFeedState>>;
|
||||||
readonly reactiveState: Ref<Partial<T>>;
|
save(updated: Partial<FollowingFeedState>): void;
|
||||||
save(updated: T): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createHeaderItem(storage?: Ref<StorageInterface>): PageHeaderItem {
|
export function createHeaderItem(storage?: Ref<StorageInterface>): PageHeaderItem {
|
||||||
|
@ -117,45 +117,44 @@ export function createOptionsMenu(storage?: Ref<StorageInterface>): MenuItem[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createModel(storage?: Ref<StorageInterface>): FollowingFeedModel {
|
export function createModel(storage?: Ref<StorageInterface>): FollowingFeedModel {
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
storage ??= createDefaultStorage();
|
storage ??= createDefaultStorage();
|
||||||
|
|
||||||
// Based on timeline.saveTlFilter()
|
// Based on timeline.saveTlFilter()
|
||||||
const saveFollowingFilter = <K extends keyof FollowingFeedState>(key: K, value: FollowingFeedState[K]) => {
|
const saveFollowingFilter = <K extends keyof FollowingFeedState>(key: K, value: FollowingFeedState[K]) => {
|
||||||
const state = deepMerge(storage.value.state, defaultFollowingFeedState);
|
const state = deepMerge<FollowingFeedState>(storage.value.state.value, defaultFollowingFeedState);
|
||||||
const out = deepMerge({ [key]: value }, state);
|
const out = deepMerge<FollowingFeedState>({ [key]: value }, state);
|
||||||
storage.value.save(out);
|
storage.value.save(out);
|
||||||
};
|
};
|
||||||
|
|
||||||
const userList: WritableComputedRef<FollowingFeedTab> = computed({
|
const userList: WritableComputedRef<FollowingFeedTab> = computed({
|
||||||
get: () => storage.value.reactiveState.value.userList ?? defaultFollowingFeedState.userList,
|
get: () => storage.value.state.value.userList ?? defaultFollowingFeedState.userList,
|
||||||
set: value => saveFollowingFilter('userList', value),
|
set: value => saveFollowingFilter('userList', value),
|
||||||
});
|
});
|
||||||
const withNonPublic: WritableComputedRef<boolean> = computed({
|
const withNonPublic: WritableComputedRef<boolean> = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
if (userList.value === 'followers') return false;
|
if (userList.value === 'followers') return false;
|
||||||
return storage.value.reactiveState.value.withNonPublic ?? defaultFollowingFeedState.withNonPublic;
|
return storage.value.state.value.withNonPublic ?? defaultFollowingFeedState.withNonPublic;
|
||||||
},
|
},
|
||||||
set: value => saveFollowingFilter('withNonPublic', value),
|
set: value => saveFollowingFilter('withNonPublic', value),
|
||||||
});
|
});
|
||||||
const withQuotes: WritableComputedRef<boolean> = computed({
|
const withQuotes: WritableComputedRef<boolean> = computed({
|
||||||
get: () => storage.value.reactiveState.value.withQuotes ?? defaultFollowingFeedState.withQuotes,
|
get: () => storage.value.state.value.withQuotes ?? defaultFollowingFeedState.withQuotes,
|
||||||
set: value => saveFollowingFilter('withQuotes', value),
|
set: value => saveFollowingFilter('withQuotes', value),
|
||||||
});
|
});
|
||||||
const withBots: WritableComputedRef<boolean> = computed({
|
const withBots: WritableComputedRef<boolean> = computed({
|
||||||
get: () => storage.value.reactiveState.value.withBots ?? defaultFollowingFeedState.withBots,
|
get: () => storage.value.state.value.withBots ?? defaultFollowingFeedState.withBots,
|
||||||
set: value => saveFollowingFilter('withBots', value),
|
set: value => saveFollowingFilter('withBots', value),
|
||||||
});
|
});
|
||||||
const withReplies: WritableComputedRef<boolean> = computed({
|
const withReplies: WritableComputedRef<boolean> = computed({
|
||||||
get: () => storage.value.reactiveState.value.withReplies ?? defaultFollowingFeedState.withReplies,
|
get: () => storage.value.state.value.withReplies ?? defaultFollowingFeedState.withReplies,
|
||||||
set: value => saveFollowingFilter('withReplies', value),
|
set: value => saveFollowingFilter('withReplies', value),
|
||||||
});
|
});
|
||||||
const onlyFiles: WritableComputedRef<boolean> = computed({
|
const onlyFiles: WritableComputedRef<boolean> = computed({
|
||||||
get: () => storage.value.reactiveState.value.onlyFiles ?? defaultFollowingFeedState.onlyFiles,
|
get: () => storage.value.state.value.onlyFiles ?? defaultFollowingFeedState.onlyFiles,
|
||||||
set: value => saveFollowingFilter('onlyFiles', value),
|
set: value => saveFollowingFilter('onlyFiles', value),
|
||||||
});
|
});
|
||||||
const remoteWarningDismissed: WritableComputedRef<boolean> = computed({
|
const remoteWarningDismissed: WritableComputedRef<boolean> = computed({
|
||||||
get: () => storage.value.reactiveState.value.remoteWarningDismissed ?? defaultFollowingFeedState.remoteWarningDismissed,
|
get: () => storage.value.state.value.remoteWarningDismissed ?? defaultFollowingFeedState.remoteWarningDismissed,
|
||||||
set: value => saveFollowingFilter('remoteWarningDismissed', value),
|
set: value => saveFollowingFilter('remoteWarningDismissed', value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -172,10 +171,9 @@ export function createModel(storage?: Ref<StorageInterface>): FollowingFeedModel
|
||||||
|
|
||||||
function createDefaultStorage() {
|
function createDefaultStorage() {
|
||||||
return computed(() => ({
|
return computed(() => ({
|
||||||
state: defaultStore.state.followingFeed,
|
state: prefer.s.followingFeed,
|
||||||
reactiveState: defaultStore.reactiveState.followingFeed,
|
save(updated: typeof prefer.s.followingFeed) {
|
||||||
save(updated: typeof defaultStore.state.followingFeed) {
|
prefer.s.followingFeed = updated;
|
||||||
return defaultStore.set('followingFeed', updated);
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ function isPureObject(value: unknown): value is Record<PropertyKey, unknown> {
|
||||||
* valueにないキーをdefからもらう(再帰的)\
|
* valueにないキーをdefからもらう(再帰的)\
|
||||||
* nullはそのまま、undefinedはdefの値
|
* nullはそのまま、undefinedはdefの値
|
||||||
**/
|
**/
|
||||||
export function deepMerge<X extends Record<PropertyKey, unknown>>(value: DeepPartial<X>, def: X): X {
|
export function deepMerge<X extends object | Record<PropertyKey, unknown>>(value: DeepPartial<X>, def: X): X {
|
||||||
if (isPureObject(value) && isPureObject(def)) {
|
if (isPureObject(value) && isPureObject(def)) {
|
||||||
const result = deepClone(value as Cloneable) as X;
|
const result = deepClone(value as Cloneable) as X;
|
||||||
for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
|
for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue