Skip to content

Commit

Permalink
feat: add search/filter data panel (#8)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <[email protected]>
  • Loading branch information
libondev and antfu authored May 24, 2024
1 parent bc70023 commit ceb1edd
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 3 deletions.
222 changes: 222 additions & 0 deletions ui/components/FilterData.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
<script lang="ts" setup>
import OverlayPanel from 'primevue/overlaypanel'
import Dropdown from 'primevue/dropdown'
import type { RegexInfo } from 'regex-doctor'
export interface Condition {
property: keyof RegexInfo
condition: string
value: string
}
export type ConditionGetter = (item: RegexInfo) => boolean
const emits = defineEmits<{
(event: 'filter', conditions: ConditionGetter[]): void
}>()
const overlayRef = shallowRef<InstanceType<typeof OverlayPanel>>()
const conditions = ref<Condition[]>([])
// Last screening condition
let prevConditions: Condition[] = []
const propertyOptions = [
{ label: 'Regex', value: 'pattern' },
{ label: 'Calls', value: 'calls' },
{ label: 'Copies', value: 'copies' },
{ label: 'Groups', value: 'groups' },
{ label: 'Total', value: 'sum' },
{ label: 'Avg', value: 'avg' },
{ label: 'Max', value: 'max' },
{ label: 'Min', value: 'min' },
{ label: 'Dpk', value: 'dpk' },
]
const conditionOptions = [
{ label: 'Equal', value: 'EQ' },
{ label: 'Greater', value: 'GT' },
{ label: 'Less', value: 'LT' },
{ label: 'Greater Equal', value: 'GE' },
{ label: 'Less Equal', value: 'LE' },
{ label: 'Includes', value: 'IN' },
{ label: 'Excludes', value: 'EX' },
]
function onValueInputTypeGetter({ condition }: Condition) {
return ['GT', 'LT', 'GE', 'LE'].includes(condition) ? 'number' : 'text'
}
function hasConditionDifference() {
if (conditions.value.length !== prevConditions.length)
return true
for (let i = 0; i < conditions.value.length; i++) {
const condition = conditions.value[i]
const prevCondition = prevConditions[i]
if (
condition.property !== prevCondition.property
|| condition.condition !== prevCondition.condition
|| condition.value !== prevCondition.value
)
return true
}
}
function onFilterClick(ev: MouseEvent) {
overlayRef.value!.toggle(ev)
}
function generateGetter({ property, condition, value }: Condition) {
return (rowData: RegexInfo) => {
const rowDataValue = rowData[property] as any
if (condition === 'IN')
return rowDataValue.includes(value)
else if (condition === 'EX')
return !rowDataValue.includes(value)
else if (condition === 'EQ')
return rowDataValue === value
else if (condition === 'GT')
return rowDataValue > value
else if (condition === 'LT')
return rowDataValue < value
else if (condition === 'GE')
return rowDataValue >= value
else if (condition === 'LE')
return rowDataValue <= value
else
return true
}
}
function onOverlayShow() {
// If conditions is empty, add a default condition
if (!conditions.value.length)
conditions.value = [{ property: 'pattern', condition: 'IN', value: '' }]
prevConditions = conditions.value.map(v => structuredClone(toRaw(v)))
}
function onOverlayHide() {
conditions.value = conditions.value.filter(v => v.value)
if (hasConditionDifference())
emits('filter', conditions.value.map(generateGetter))
}
function onCreateCondition() {
conditions.value.push({
property: propertyOptions[0].value as keyof RegexInfo,
condition: 'EQ',
value: '',
})
}
function onRemoveCondition(conditionIndex: number) {
conditions.value.splice(conditionIndex, 1)
}
</script>

<template>
<button op80 flex="~ gap-2 items-center" @click="onFilterClick">
<div :class="[conditions.length ? 'i-ph-magnifying-glass-fill' : 'i-ph-magnifying-glass']" />
Regex
</button>

<OverlayPanel
ref="overlayRef"
trigger="click"
b="~ solid gray/20"
bg-base rounded-md m-1 p-1 min-w-407px
shadow-md
max-h-300px
of-auto
@show="onOverlayShow"
@hide="onOverlayHide"
>
<div flex="~ items-center justify-between" mb-1 sticky top-0 z-1 bg-base>
<span text-sm op-50 pl-1>
Filter Data
</span>

<div inline-flex p-1 hover="bg-gray/15" rounded @click="onCreateCondition">
<button i-ph-plus-bold cursor-default op-50 />
</div>
</div>

<div v-for="(row, idx) of conditions" :key="idx" flex="~ items-center justify-between gap-1" text-sm not="last-mb-1">
<Dropdown
v-model="row.property"
:options="propertyOptions" option-label="label"
option-value="value"
flex="~ items-center"
b="~ solid gray/20"
py-1 px-2
flex-shrink-0
w-30
min-w-30
rounded
cursor-default
placeholder="Property"
/>
<Dropdown
v-model="row.condition"
:options="conditionOptions"
option-label="label"
option-value="value"
flex="~ items-center"
b="~ solid gray/20"
py-1 px-2
flex-shrink-0
w-30
min-w-30
rounded
cursor-default
placeholder="Condition"
/>

<input
v-model="row.value"
:type="onValueInputTypeGetter(row)"
bg="transparent"
b="~ solid gray/20"
py-1 px-2
flex-shrink-0
w-30
rounded
outline-none
class="focus:!b-gray/80"
cursor-default
>

<div flex="~ items-center justify-center" p-1 flex-shrink-0 rounded hover="bg-gray/15">
<button i-ph-trash text-red-500 cursor-default @click="onRemoveCondition(idx)" />
</div>
</div>
</OverlayPanel>
</template>

<style>
.p-dropdown {
@apply relative pr-6;
}
.p-dropdown-items-wrapper {
@apply bg-base mt-0.5 p-1 b-1 b-solid b-gray/20 rounded-md shadow-md text-sm of-auto;
}
.p-dropdown-item {
@apply relative py-1 px-2 hover:bg-gray/10 rounded cursor-default;
}
.p-placeholder {
@apply text-gray/60;
}
.p-dropdown-trigger {
@apply absolute op-50 right-1.5;
}
</style>
2 changes: 1 addition & 1 deletion ui/components/RegexDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const RenderInput = defineComponent({
<NumberDisplay :number="callInfo.inputLength" /> chars
</div>

<div i-ph-speedometer-duotone op50 />
<div i-ph-speedometer-duotone op50 flex-shrink-0 />
<div>
<DurationDisplay :ms="callInfo.dpk" /> <span op50 text-sm>/ 1K chars</span>
</div>
Expand Down
19 changes: 17 additions & 2 deletions ui/components/RegexTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DataTable from 'primevue/datatable'
import Column from 'primevue/column'
import { Dropdown } from 'floating-vue'
import Dialog from 'primevue/dialog'
import type { ConditionGetter } from './FilterData.vue'
const props = defineProps<{
payload: RegexDoctorResult
Expand All @@ -13,16 +14,26 @@ const filters = reactive({
package: '',
})
const filterConditions = shallowRef<ConditionGetter[]>([])
const filtered = computed(() => {
let list = props.payload.regexInfos
if (filters.package)
list = list.filter(item => item.packages?.includes(filters.package))
// apply all filters
list = list.filter(item => filterConditions.value.every(condition => condition(item)))
return list
})
const currentRegex = shallowRef<RegexInfo | null>(null)
function onFilterConfirm(conditions: ConditionGetter[]) {
filterConditions.value = conditions
}
const visible = ref(false)
function showCurrentRegex(info: RegexInfo) {
currentRegex.value = info
Expand All @@ -37,7 +48,11 @@ function showCurrentRegex(info: RegexInfo) {
<PackageNameDisplay v-if="data.dynamic" name="new" />
</template>
</Column>
<Column field="regex" header="Regex" class="text-left" header-class="pl4 [&>*]:justify-start">
<Column field="regex" class="text-left" header-class="pl4 [&>*]:justify-start">
<template #header>
<FilterData @filter="onFilterConfirm" />
</template>

<template #body="{ data }">
<button flex h-full px2 my1 border="~ base rounded" bg-gray:2 @click="showCurrentRegex(data)">
<ShikiInline
Expand Down Expand Up @@ -127,7 +142,7 @@ function showCurrentRegex(info: RegexInfo) {
v-if="data.filesCreated?.length" i-ph-file-code-duotone text-purple
title="Regex creation source"
/>
<div op50 i-ph-files-duotone />
<div op50 i-ph-files-duotone flex-shrink-0 />
{{ data.filesCalled?.length }}
</button>
<template #popper>
Expand Down

0 comments on commit ceb1edd

Please sign in to comment.