soiz1's picture
Upload folder using huggingface_hub
4d70170 verified
import type { Ref } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import type { ComponentTreeNode, EditStatePayload, InspectedComponentData } from '@vue/devtools-api'
import groupBy from 'lodash/groupBy'
import {
BridgeEvents,
BridgeSubscriptions,
isChrome,
openInEditor,
searchDeepInObject,
setStorage,
sortByKey,
} from '@vue-devtools/shared-utils'
import { getBridge, useBridge } from '@front/features/bridge'
import type { AppRecord } from '@front/features/apps'
import { useCurrentApp, waitForAppSelect } from '@front/features/apps'
import { useRoute, useRouter } from 'vue-router'
export const rootInstances = ref<ComponentTreeNode[]>([])
export const componentsMap = ref<Record<ComponentTreeNode['id'], ComponentTreeNode>>({})
let componentsParent: Record<ComponentTreeNode['id'], ComponentTreeNode['id']> = {}
const treeFilter = ref('')
export const selectedComponentId = ref<ComponentTreeNode['id'] | null>(null)
export const selectedComponentData = ref<InspectedComponentData | null>(null)
const selectedComponentStateFilter = ref('')
export const selectedComponentPendingId = ref<ComponentTreeNode['id'] | null>(null)
let lastSelectedApp: AppRecord = null
export const lastSelectedComponentId: Record<AppRecord['id'], ComponentTreeNode['id']> = {}
export const expandedMap = ref<Record<ComponentTreeNode['id'], boolean>>({})
export function useComponentRequests() {
const router = useRouter()
function selectComponent(id: ComponentTreeNode['id'], replace = false) {
if (selectedComponentId.value !== id) {
router[replace ? 'replace' : 'push']({
params: {
appId: getAppIdFromComponentId(id),
componentId: id,
},
})
}
else {
loadComponent(id)
}
}
return {
requestComponentTree,
selectComponent,
}
}
export function useComponents() {
const { onBridge, subscribe } = useBridge()
const route = useRoute()
const {
requestComponentTree,
selectComponent,
} = useComponentRequests()
const { currentAppId } = useCurrentApp()
watch(treeFilter, () => {
requestComponentTree()
})
watch(() => route.params.componentId, () => {
const value = route.params.componentId as string
if (value && getAppIdFromComponentId(value) === currentAppId.value) {
selectedComponentId.value = value
loadComponent(value)
}
}, {
immediate: true,
})
function subscribeToSelectedData() {
let unsub
watch(selectedComponentId, (value) => {
if (unsub) {
unsub()
unsub = null
}
if (value != null) {
unsub = subscribe(BridgeSubscriptions.SELECTED_COMPONENT_DATA, value)
}
}, {
immediate: true,
})
}
// We watch for the tree data so that we can auto load the current selected component
watch(componentsMap, () => {
if (selectedComponentId.value && selectedComponentPendingId.value !== selectedComponentId.value && !selectedComponentData.value) {
selectComponent(selectedComponentId.value)
}
}, {
immediate: true,
deep: true,
})
onBridge(BridgeEvents.TO_FRONT_APP_SELECTED, async ({ id }) => {
await waitForAppSelect()
requestComponentTree()
selectedComponentData.value = null
if (lastSelectedApp !== null) {
selectLastComponent()
}
lastSelectedApp = id
})
// Re-select last selected component when switching back to inspector component tab
function selectLastComponent() {
const id = lastSelectedComponentId[currentAppId.value]
if (id) {
selectComponent(id, true)
}
}
return {
rootInstances: computed(() => rootInstances.value),
treeFilter,
selectedComponentId: computed(() => selectedComponentId.value),
requestComponentTree,
selectComponent,
selectLastComponent,
subscribeToSelectedData,
}
}
export function useComponent(instance: Ref<ComponentTreeNode>) {
const { selectComponent, requestComponentTree } = useComponentRequests()
const { subscribe } = useBridge()
const isExpanded = computed(() => isComponentOpen(instance.value.id))
const isExpandedUndefined = computed(() => expandedMap.value[instance.value.id] == null)
function toggleExpand(recursively = false, value?, child?) {
const treeNode = child || instance.value
if (!treeNode.hasChildren) {
return
}
const isOpen = value === undefined ? !isExpanded.value : value
setComponentOpen(treeNode.id, isOpen)
if (isComponentOpen(treeNode.id)) {
requestComponentTree(treeNode.id, recursively)
}
else {
// stop expanding all treenode
treeNode.autoOpen = false
}
if (recursively) {
treeNode.children.forEach((child) => {
toggleExpand(recursively, value, child)
})
}
}
const isSelected = computed(() => selectedComponentId.value === instance.value.id)
function select(id = instance.value.id) {
selectComponent(id)
}
function subscribeToComponentTree() {
let unsub
watch(() => instance.value.id, (value) => {
if (unsub) {
unsub()
unsub = null
}
if (value != null) {
unsub = subscribe(BridgeSubscriptions.COMPONENT_TREE, value)
}
}, {
immediate: true,
})
}
onMounted(() => {
if (instance.value.autoOpen) {
toggleExpand(true, true)
}
else if (isExpanded.value) {
requestComponentTree(instance.value.id)
}
})
return {
isExpanded,
isExpandedUndefined,
isComponentOpen,
toggleExpand,
isSelected,
select,
subscribeToComponentTree,
}
}
export function setComponentOpen(id: ComponentTreeNode['id'], isOpen: boolean) {
expandedMap.value[id] = isOpen
}
export function isComponentOpen(id: ComponentTreeNode['id']) {
return !!expandedMap.value[id]
}
export function useSelectedComponent() {
const data = computed(() => selectedComponentData.value)
const state = computed(() => selectedComponentData.value
? groupBy(sortByKey(selectedComponentData.value.state.filter((el) => {
try {
return searchDeepInObject({
[el.key]: el.value,
}, selectedComponentStateFilter.value)
}
catch (e) {
return {
[el.key]: e,
}
}
})), 'type')
: ({}))
const fileIsPath = computed(() => data.value?.file && /[/\\]/.test(data.value.file))
function inspectDOM() {
if (!data.value) {
return
}
if (isChrome) {
getBridge().send(BridgeEvents.TO_BACK_COMPONENT_INSPECT_DOM, { instanceId: data.value.id })
}
else {
// eslint-disable-next-line no-alert
window.alert('DOM inspection is not supported in this shell.')
}
}
function openFile() {
if (!data.value) {
return
}
openInEditor(data.value.file)
}
const { bridge } = useBridge()
function editState(dotPath: string, payload: EditStatePayload, type?: string) {
bridge.send(BridgeEvents.TO_BACK_COMPONENT_EDIT_STATE, {
instanceId: data.value?.id,
dotPath,
type,
...payload,
})
}
function scrollToComponent() {
bridge.send(BridgeEvents.TO_BACK_COMPONENT_SCROLL_TO, {
instanceId: data.value?.id,
})
}
return {
data,
state,
stateFilter: selectedComponentStateFilter,
inspectDOM,
fileIsPath,
openFile,
editState,
scrollToComponent,
selectedComponentId,
}
}
export const updateTrackingEvents = ref<Record<string, ComponentUpdateTrackingEvent>>({})
export const updateTrackingLimit = ref(Date.now() + 5_000)
export function resetComponents() {
rootInstances.value = []
componentsMap.value = {}
componentsParent = {}
updateTrackingEvents.value = {}
updateTrackingLimit.value = Date.now() + 5_000
}
export const requestedComponentTree = new Set()
let requestComponentTreeRetryDelay = 500
export async function requestComponentTree(instanceId: ComponentTreeNode['id'] | null = null, recursively = false) {
if (!instanceId) {
instanceId = '_root'
}
if (requestedComponentTree.has(instanceId)) {
return
}
requestedComponentTree.add(instanceId)
await waitForAppSelect()
_sendTreeRequest(instanceId, recursively)
_queueRetryTree(instanceId, recursively)
}
function _sendTreeRequest(instanceId: ComponentTreeNode['id'], recursively = false) {
getBridge().send(BridgeEvents.TO_BACK_COMPONENT_TREE, {
instanceId,
filter: treeFilter.value,
recursively,
})
}
function _queueRetryTree(instanceId: ComponentTreeNode['id'], recursively = false) {
setTimeout(() => _retryRequestComponentTree(instanceId, recursively), requestComponentTreeRetryDelay)
requestComponentTreeRetryDelay *= 1.5
}
function _retryRequestComponentTree(instanceId: ComponentTreeNode['id'], recursively = false) {
if (rootInstances.value.length) {
requestComponentTreeRetryDelay = 500
return
}
_sendTreeRequest(instanceId, recursively)
_queueRetryTree(instanceId, recursively)
}
export function ensureComponentsMapData(data: ComponentTreeNode) {
let component = componentsMap.value[data.id]
if (!component) {
component = addToComponentsMap(data)
}
else {
component = updateComponentsMapData(data)
}
return component
}
function ensureComponentsMapChildren(id: string, children: ComponentTreeNode[]) {
const result = children.map(child => ensureComponentsMapData(child))
for (const child of children) {
componentsParent[child.id] = id
}
return result
}
function updateComponentsMapData(data: ComponentTreeNode) {
const component = componentsMap.value[data.id]
for (const key in data) {
if (key === 'children') {
if (!data.hasChildren || data.children.length) {
const children = ensureComponentsMapChildren(component.id, data.children)
component[key] = children
}
}
else {
component[key] = data[key]
}
}
return component
}
function addToComponentsMap(data: ComponentTreeNode) {
if (!data.hasChildren || data.children.length) {
data.children = ensureComponentsMapChildren(data.id, data.children)
}
componentsMap.value[data.id] = data
return data
}
export async function loadComponent(id: ComponentTreeNode['id']) {
if (!id || selectedComponentPendingId.value === id) {
return
}
lastSelectedComponentId[getAppIdFromComponentId(id)] = id
setStorage('lastSelectedComponentId', lastSelectedComponentId)
selectedComponentPendingId.value = id
await waitForAppSelect()
getBridge().send(BridgeEvents.TO_BACK_COMPONENT_SELECTED_DATA, id)
}
export function sortChildren(children: ComponentTreeNode[]) {
return children.slice().sort((a, b) => {
if (a.inactive && !b.inactive) {
return 1
}
else if (!a.inactive && b.inactive) {
return -1
}
const order = compareIndexLists(a.domOrder ?? [], b.domOrder ?? [])
if (order === 0) {
return a.id.localeCompare(b.id)
}
else {
return order
}
})
}
function compareIndexLists(a: number[], b: number[]): number {
if (!a.length || !b.length) {
return 0
}
else if (a[0] === b[0]) {
return compareIndexLists(a.slice(1), b.slice(1))
}
else {
return a[0] - b[0]
}
}
export function getAppIdFromComponentId(id: string) {
const index = id.indexOf(':')
const appId = id.substring(0, index)
return appId
}
export interface ComponentUpdateTrackingEvent {
instanceId: string
time: number
count: number
}
export function addUpdateTrackingEvent(instanceId: string, time: number) {
const event = updateTrackingEvents.value[instanceId]
if (event) {
event.count++
event.time = time
}
else {
updateTrackingEvents.value[instanceId] = {
instanceId,
time,
count: 1,
}
}
}