split tree topology into its own composable
This commit is contained in:
parent
f788af6c28
commit
fe5c1f4d43
3 changed files with 126 additions and 91 deletions
|
|
@ -26,6 +26,7 @@ import { useStatusesStore } from 'src/stores/statuses.js'
|
||||||
import { useStreamingStore } from 'src/stores/streaming.js'
|
import { useStreamingStore } from 'src/stores/streaming.js'
|
||||||
|
|
||||||
import { useScrollPosition } from 'src/composables/useScrollPosition.js'
|
import { useScrollPosition } from 'src/composables/useScrollPosition.js'
|
||||||
|
import { useTreeConversationTopology } from 'src/composables/useTreeConversationTopology.js'
|
||||||
import { useWindowSize } from 'src/composables/useWindowSize.js'
|
import { useWindowSize } from 'src/composables/useWindowSize.js'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -155,7 +156,7 @@ export default {
|
||||||
}
|
}
|
||||||
const resetDisplayState = () => {
|
const resetDisplayState = () => {
|
||||||
setFocused(statusId.value)
|
setFocused(statusId.value)
|
||||||
threadDisplay.value = new Map()
|
resetThreadDisplay()
|
||||||
}
|
}
|
||||||
watch(statusId, (newVal, oldVal) => {
|
watch(statusId, (newVal, oldVal) => {
|
||||||
const newConversationId = getConversationId(newVal)
|
const newConversationId = getConversationId(newVal)
|
||||||
|
|
@ -415,98 +416,25 @@ export default {
|
||||||
const isTreeView = computed(() => displayStyle.value === 'tree')
|
const isTreeView = computed(() => displayStyle.value === 'tree')
|
||||||
|
|
||||||
// ## Tree state
|
// ## Tree state
|
||||||
// ### Topology
|
|
||||||
const ancestors = computed(() => {
|
|
||||||
// First we fill map with empty sets and add given id's parent
|
|
||||||
// as set's only element (if any)
|
|
||||||
const parentMap = conversation.value.reduce(
|
|
||||||
(result, { id, in_reply_to_status_id: irid }) => {
|
|
||||||
if (!result.has(id)) {
|
|
||||||
result.set(id, new Set())
|
|
||||||
}
|
|
||||||
if (irid) {
|
|
||||||
// Setting parent for current item
|
|
||||||
result.get(id).add(irid)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
new Map(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Next we iterate over each entry and fill the whole ancestry chain
|
|
||||||
parentMap.entries().forEach(([originId, originSet]) => {
|
|
||||||
let current = originSet.values().next().value
|
|
||||||
while (current) {
|
|
||||||
originSet.add(current)
|
|
||||||
|
|
||||||
const parent = parentMap.get(current) ?? new Set()
|
|
||||||
|
|
||||||
current = parent.values().next().value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return parentMap
|
|
||||||
})
|
|
||||||
const topLevel = computed(() =>
|
|
||||||
[...ancestors.value.entries()]
|
|
||||||
.filter(([id, ancestors]) => ancestors.size === 0)
|
|
||||||
.map(([id]) => getStatusObject(id)),
|
|
||||||
)
|
|
||||||
const getAncestorIds = (id) => ancestors.value.get(id) ?? new Set()
|
|
||||||
const getAncestors = (id) =>
|
|
||||||
[...getAncestorIds(id)].map(getStatusObject).filter(Boolean)
|
|
||||||
const currentAncestors = computed(() =>
|
|
||||||
getAncestors(focusedId.value).reverse(),
|
|
||||||
)
|
|
||||||
const currentDepth = computed(() => currentAncestors.value.length)
|
|
||||||
|
|
||||||
// ### Thread Display
|
|
||||||
const threadDisplay = ref(new Map()) // id => 'showing' | 'hidden'
|
|
||||||
const threadDisplayDefault = computed(() => {
|
|
||||||
return conversation.value.reduce((map, status) => {
|
|
||||||
const { id } = status
|
|
||||||
const depth = ancestors.value.get(id).size
|
|
||||||
|
|
||||||
const state = (() => {
|
|
||||||
if (depth - currentDepth.value <= maxDepthToShowByDefault.value) {
|
|
||||||
return 'showing'
|
|
||||||
} else {
|
|
||||||
return 'hidden'
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
map.set(id, state)
|
|
||||||
return map
|
|
||||||
}, new Map())
|
|
||||||
})
|
|
||||||
provide('threadDisplay', threadDisplay)
|
|
||||||
provide('threadDisplayDefault', threadDisplayDefault)
|
|
||||||
|
|
||||||
const setThreadDisplayRecursively = (id, value) => {
|
|
||||||
threadDisplay.value.set(id, value)
|
|
||||||
;[...getReplies(id)]
|
|
||||||
.map((k) => k.id)
|
|
||||||
.map((id) => setThreadDisplayRecursively(id, value))
|
|
||||||
}
|
|
||||||
const showThreadRecursively = (id) => {
|
|
||||||
setThreadDisplayRecursively(id, 'showing')
|
|
||||||
}
|
|
||||||
|
|
||||||
// ## Derived values and config
|
|
||||||
const treeViewIsSimple = computed(
|
const treeViewIsSimple = computed(
|
||||||
() => !mergedConfig.value.conversationTreeAdvanced,
|
() => !mergedConfig.value.conversationTreeAdvanced,
|
||||||
)
|
)
|
||||||
const maxDepthToShowByDefault = computed(() => {
|
|
||||||
// maxDepthInThread = max number of depths that is *visible*
|
// ### Topology
|
||||||
// since our depth starts with 0 and "showing" means "showing children"
|
const {
|
||||||
// there is a -2 here
|
topLevel,
|
||||||
const maxDepth = mergedConfig.value.maxDepthInThread - 2
|
currentAncestors,
|
||||||
return Math.min(1, maxDepth)
|
threadDisplay,
|
||||||
})
|
showThreadRecursively,
|
||||||
|
resetThreadDisplay,
|
||||||
|
} = useTreeConversationTopology(conversation, replies, focusedId)
|
||||||
|
provide('threadDisplay', threadDisplay)
|
||||||
|
|
||||||
const shouldShowAllConversationButton = computed(
|
const shouldShowAllConversationButton = computed(
|
||||||
() => currentAncestors.value.length > 0 && topLevel.value.length > 1,
|
() => currentAncestors.value.length > 0 && topLevel.value.length > 1,
|
||||||
)
|
)
|
||||||
const shouldShowAncestors = computed(
|
const shouldShowAncestors = computed(
|
||||||
() => isExpanded.value && ancestors.value.get(focusedId.value) != null,
|
() => isExpanded.value && currentAncestors.value.size > 0,
|
||||||
)
|
)
|
||||||
const shouldFadeAncestors = computed(
|
const shouldFadeAncestors = computed(
|
||||||
() => mergedConfig.value.conversationTreeFadeAncestors,
|
() => mergedConfig.value.conversationTreeFadeAncestors,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ const ThreadTree = {
|
||||||
'focused',
|
'focused',
|
||||||
'replies',
|
'replies',
|
||||||
'threadDisplay',
|
'threadDisplay',
|
||||||
'threadDisplayDefault',
|
|
||||||
'isExpanded',
|
'isExpanded',
|
||||||
'isPage',
|
'isPage',
|
||||||
],
|
],
|
||||||
|
|
@ -39,10 +38,7 @@ const ThreadTree = {
|
||||||
return !useMergedConfigStore().mergedConfig.conversationTreeAdvanced
|
return !useMergedConfigStore().mergedConfig.conversationTreeAdvanced
|
||||||
},
|
},
|
||||||
threadShowing() {
|
threadShowing() {
|
||||||
const result =
|
return this.threadDisplay.get(this.statusId) === 'showing'
|
||||||
this.threadDisplay.get(this.statusId) ??
|
|
||||||
this.threadDisplayDefault.get(this.statusId)
|
|
||||||
return result === 'showing'
|
|
||||||
},
|
},
|
||||||
canDive() {
|
canDive() {
|
||||||
return this.isExpanded
|
return this.isExpanded
|
||||||
|
|
|
||||||
111
src/composables/useTreeConversationTopology.js
Normal file
111
src/composables/useTreeConversationTopology.js
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||||
|
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||||
|
|
||||||
|
export function useTreeConversationTopology(conversation, replies, current) {
|
||||||
|
const getStatusObject = (id) => useStatusesStore().allStatuses.get(id)
|
||||||
|
const getReplies = (id) => replies.value.get(id) ?? new Set()
|
||||||
|
|
||||||
|
const { mergedConfig } = storeToRefs(useMergedConfigStore())
|
||||||
|
|
||||||
|
const maxDepthToShowByDefault = computed(() => {
|
||||||
|
// maxDepthInThread = max number of depths that is *visible*
|
||||||
|
// since our depth starts with 0 and "showing" means "showing children"
|
||||||
|
// there is a -2 here
|
||||||
|
const maxDepth = mergedConfig.value.maxDepthInThread - 2
|
||||||
|
return Math.min(1, maxDepth)
|
||||||
|
})
|
||||||
|
|
||||||
|
const ancestors = computed(() => {
|
||||||
|
// First we fill map with empty sets and add given id's parent
|
||||||
|
// as set's only element (if any)
|
||||||
|
const parentMap = conversation.value.reduce(
|
||||||
|
(result, { id, in_reply_to_status_id: irid }) => {
|
||||||
|
if (!result.has(id)) {
|
||||||
|
result.set(id, new Set())
|
||||||
|
}
|
||||||
|
if (irid) {
|
||||||
|
// Setting parent for current item
|
||||||
|
result.get(id).add(irid)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
new Map(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Next we iterate over each entry and fill the whole ancestry chain
|
||||||
|
parentMap.entries().forEach(([originId, originSet]) => {
|
||||||
|
let current = originSet.values().next().value
|
||||||
|
while (current) {
|
||||||
|
originSet.add(current)
|
||||||
|
|
||||||
|
const parent = parentMap.get(current) ?? new Set()
|
||||||
|
|
||||||
|
current = parent.values().next().value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return parentMap
|
||||||
|
})
|
||||||
|
const topLevel = computed(() =>
|
||||||
|
[...ancestors.value.entries()]
|
||||||
|
.filter(([id, ancestors]) => ancestors.size === 0)
|
||||||
|
.map(([id]) => getStatusObject(id)),
|
||||||
|
)
|
||||||
|
const getAncestorIds = (id) => ancestors.value.get(id) ?? new Set()
|
||||||
|
const getAncestors = (id) =>
|
||||||
|
[...getAncestorIds(id)].map(getStatusObject).filter(Boolean)
|
||||||
|
const currentAncestors = computed(() => getAncestors(current.value).reverse())
|
||||||
|
const currentDepth = computed(() => currentAncestors.value.length)
|
||||||
|
|
||||||
|
// Thread Display, for collapsing/expanding tree branches
|
||||||
|
// Map of id => 'showing' | 'hidden'
|
||||||
|
const threadDisplayOverride = ref(new Map())
|
||||||
|
const threadDisplayDefault = computed(() => {
|
||||||
|
return conversation.value.reduce((map, status) => {
|
||||||
|
const { id } = status
|
||||||
|
const depth = ancestors.value.get(id).size
|
||||||
|
|
||||||
|
const state = (() => {
|
||||||
|
if (depth - currentDepth.value <= maxDepthToShowByDefault.value) {
|
||||||
|
return 'showing'
|
||||||
|
} else {
|
||||||
|
return 'hidden'
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
map.set(id, state)
|
||||||
|
return map
|
||||||
|
}, new Map())
|
||||||
|
})
|
||||||
|
const threadDisplay = computed(() => {
|
||||||
|
return new Map(
|
||||||
|
[...threadDisplayOverride.value.entries()].map(([k, v]) => [
|
||||||
|
k,
|
||||||
|
threadDisplayOverride.value.get(k) ?? threadDisplayDefault.value.get(k),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const setThreadDisplayRecursively = (id, value) => {
|
||||||
|
threadDisplayOverride.value.set(id, value)
|
||||||
|
;[...getReplies(id)]
|
||||||
|
.map((k) => k.id)
|
||||||
|
.map((id) => setThreadDisplayRecursively(id, value))
|
||||||
|
}
|
||||||
|
const showThreadRecursively = (id) => {
|
||||||
|
setThreadDisplayRecursively(id, 'showing')
|
||||||
|
}
|
||||||
|
const resetThreadDisplay = () => {
|
||||||
|
threadDisplayOverride.value = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
topLevel,
|
||||||
|
currentAncestors,
|
||||||
|
threadDisplay,
|
||||||
|
showThreadRecursively,
|
||||||
|
resetThreadDisplay,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue