|
| 1 | +<script lang="ts"> |
| 2 | +const itemSearchContent = ref('') |
| 3 | +</script> |
| 4 | + |
| 5 | +<script lang="ts" setup> |
| 6 | +const store = useNonNullRstore() |
| 7 | +const cache = useStoreCache() |
| 8 | +
|
| 9 | +const showRawCache = useLocalStorage('rstore-devtools-show-raw-cache', false) |
| 10 | +const selectedModel = useLocalStorage<string | null>('rstore-devtools-selected-cache-model', null) |
| 11 | +const cacheModelSearch = useLocalStorage('rstore-devtools-cache-model-search', '') |
| 12 | +const itemSearchKey = useLocalStorage('rstore-devtools-cache-item-search-key', '') |
| 13 | +
|
| 14 | +const filteredModels = computed(() => { |
| 15 | + if (!cacheModelSearch.value) { |
| 16 | + return store.value.$models |
| 17 | + } |
| 18 | + return store.value.$models.filter(m => m.name.toLowerCase().includes(cacheModelSearch.value.toLowerCase())) |
| 19 | +}) |
| 20 | +
|
| 21 | +const modelSearchEl = useTemplateRef('modelSearchEl') |
| 22 | +
|
| 23 | +// Selected cache |
| 24 | +
|
| 25 | +const selectedCache = computed(() => cache.value[selectedModel.value as keyof typeof cache.value] as Record<string, any>) |
| 26 | +
|
| 27 | +const filteredCache = computed(() => { |
| 28 | + function filteredByKey(cache: Record<string, any> | undefined) { |
| 29 | + if (!itemSearchKey.value || !cache) { |
| 30 | + return cache |
| 31 | + } |
| 32 | + const result: Record<string, string> = {} |
| 33 | + for (const key in cache) { |
| 34 | + if (key.includes(itemSearchKey.value)) { |
| 35 | + result[key] = cache[key] |
| 36 | + } |
| 37 | + } |
| 38 | + return result |
| 39 | + } |
| 40 | +
|
| 41 | + function filteredByContent(cache: Record<string, any> | undefined) { |
| 42 | + if (!itemSearchContent.value || !cache) { |
| 43 | + return cache |
| 44 | + } |
| 45 | + try { |
| 46 | + // eslint-disable-next-line no-eval |
| 47 | + const filter = eval(`(item) => { |
| 48 | + return ${itemSearchContent.value} |
| 49 | + }`) |
| 50 | + const result: Record<string, string> = {} |
| 51 | + for (const key in cache) { |
| 52 | + if (filter(cache[key])) { |
| 53 | + result[key] = cache[key] |
| 54 | + } |
| 55 | + } |
| 56 | + return result |
| 57 | + } |
| 58 | + catch (error) { |
| 59 | + console.error(error) |
| 60 | + return cache |
| 61 | + } |
| 62 | + } |
| 63 | +
|
| 64 | + let result = filteredByKey(selectedCache.value) |
| 65 | + result = filteredByContent(result) |
| 66 | + return result |
| 67 | +}) |
| 68 | +</script> |
| 69 | + |
| 70 | +<template> |
| 71 | + <div class="h-full"> |
| 72 | + <CodeSnippet |
| 73 | + v-if="showRawCache" |
| 74 | + :code="cache" |
| 75 | + class="text-xs p-2" |
| 76 | + /> |
| 77 | + |
| 78 | + <div |
| 79 | + v-else |
| 80 | + class="flex items-stretch h-full" |
| 81 | + > |
| 82 | + <!-- Models --> |
| 83 | + <div class="flex flex-col w-1/4 max-w-60"> |
| 84 | + <div class="p-1"> |
| 85 | + <UInput |
| 86 | + ref="modelSearchEl" |
| 87 | + v-model="cacheModelSearch" |
| 88 | + placeholder="Search models..." |
| 89 | + icon="lucide:search" |
| 90 | + size="xs" |
| 91 | + class="w-full" |
| 92 | + > |
| 93 | + <template v-if="cacheModelSearch" #trailing> |
| 94 | + <UButton |
| 95 | + icon="lucide:x" |
| 96 | + size="xs" |
| 97 | + variant="link" |
| 98 | + color="neutral" |
| 99 | + @click="cacheModelSearch = '';modelSearchEl?.inputRef?.focus()" |
| 100 | + /> |
| 101 | + </template> |
| 102 | + </UInput> |
| 103 | + </div> |
| 104 | + <div class="flex flex-col flex-1 overflow-auto p-1 gap-px"> |
| 105 | + <DevtoolsCacheModelItem |
| 106 | + v-for="model in filteredModels" |
| 107 | + :key="model.name" |
| 108 | + :model |
| 109 | + :selected="selectedModel === model.name" |
| 110 | + @click="selectedModel = model.name" |
| 111 | + /> |
| 112 | + |
| 113 | + <div v-if="!filteredModels.length" class="p-2 text-xs italic opacity-50 text-center"> |
| 114 | + No models found. |
| 115 | + </div> |
| 116 | + </div> |
| 117 | + </div> |
| 118 | + |
| 119 | + <!-- Items --> |
| 120 | + <div v-if="selectedModel" class="overflow-auto flex-1"> |
| 121 | + <Empty |
| 122 | + v-if="!filteredCache || !Object.keys(selectedCache).length" |
| 123 | + icon="lucide:database" |
| 124 | + title="No items for this model" |
| 125 | + class="h-full" |
| 126 | + /> |
| 127 | + <Empty |
| 128 | + v-else-if="!Object.keys(filteredCache).length" |
| 129 | + icon="lucide:search" |
| 130 | + title="No items match the search" |
| 131 | + class="h-full" |
| 132 | + /> |
| 133 | + <div v-else class="p-1 gap-1 flex flex-col"> |
| 134 | + <div |
| 135 | + v-for="(value, key) in filteredCache" |
| 136 | + :key |
| 137 | + class="border border-default rounded-lg group/cache-item hover:border-muted" |
| 138 | + > |
| 139 | + <div class="font-mono text-xs sticky top-px h-[25px]"> |
| 140 | + <div class="bg-gradient-to-b from-white via-white to-transparent dark:from-[rgb(21,21,21)] dark:via-[rgb(21,21,21)] dark:to-[rgba(21,21,21,0)] via-75% absolute -top-px -left-px -right-px"> |
| 141 | + <div class="p-2 border-t border-l border-r border-default group-hover/cache-item:border-muted rounded-t-lg"> |
| 142 | + {{ key }} |
| 143 | + </div> |
| 144 | + </div> |
| 145 | + </div> |
| 146 | + <CodeSnippet :code="value" class="text-xs p-2" /> |
| 147 | + </div> |
| 148 | + </div> |
| 149 | + </div> |
| 150 | + </div> |
| 151 | + |
| 152 | + <Teleport to="#devtools-toolbar" defer> |
| 153 | + <UPopover |
| 154 | + v-if="!showRawCache" |
| 155 | + arrow |
| 156 | + > |
| 157 | + <UButton |
| 158 | + icon="lucide:search" |
| 159 | + size="xs" |
| 160 | + :variant="itemSearchKey || itemSearchContent ? 'solid' : 'soft'" |
| 161 | + /> |
| 162 | + |
| 163 | + <template #content> |
| 164 | + <div class="p-2 w-80 flex flex-col gap-2"> |
| 165 | + <UFormField label="Search item by Key"> |
| 166 | + <UInput |
| 167 | + v-model="itemSearchKey" |
| 168 | + placeholder="Item Key" |
| 169 | + icon="lucide:id-card" |
| 170 | + autofocus |
| 171 | + class="w-full" |
| 172 | + /> |
| 173 | + </UFormField> |
| 174 | + <UFormField label="Filter items by data"> |
| 175 | + <UTextarea |
| 176 | + v-model="itemSearchContent" |
| 177 | + placeholder="item.isActive && item.age > 18" |
| 178 | + class="w-full font-mono" |
| 179 | + :rows="3" |
| 180 | + :ui="{ |
| 181 | + base: 'resize-none', |
| 182 | + }" |
| 183 | + /> |
| 184 | + </UFormField> |
| 185 | + </div> |
| 186 | + </template> |
| 187 | + </UPopover> |
| 188 | + |
| 189 | + <USwitch |
| 190 | + v-model="showRawCache" |
| 191 | + size="xs" |
| 192 | + label="Raw" |
| 193 | + class="text-xs" |
| 194 | + /> |
| 195 | + </Teleport> |
| 196 | + </div> |
| 197 | +</template> |
0 commit comments