Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions src/background/database/cate-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ type Item = {
* Name
*/
n: string
/**
* Auto rules
*/
a?: string[]
}

type Items = Record<number, Item>
Expand All @@ -36,43 +40,59 @@ class CateDatabase extends BaseDatabase {

async listAll(): Promise<timer.site.Cate[]> {
const items = await this.getItems()
return Object.entries(items).map(([id, { n = '' } = {}]) => {
return Object.entries(items).map(([id, { n = '', a } = {}]) => {
return {
id: parseInt(id),
name: n,
autoRules: a ?? [],
} satisfies timer.site.Cate
})
}

async add(name: string): Promise<timer.site.Cate> {
async add(toAdd: Omit<timer.site.Cate, 'id'>): Promise<timer.site.Cate> {
const { name, autoRules } = toAdd
const items = await this.getItems()
const existId = Object.entries(items).find(([_, v]) => v.n === name)?.[0]
if (existId) {
// Exist already
return { id: parseInt(existId), name }
return { id: parseInt(existId), name, autoRules }
}

const id = (Object.keys(items || {}).map(k => parseInt(k)).sort().reverse()?.[0] ?? 0) + 1
items[id] = { n: name ?? items[id]?.n }
items[id] = { n: name ?? items[id]?.n, a: autoRules }

await this.saveItems(items)
return { name, id }
return { name, id, autoRules }
}

async update(id: number, name: string): Promise<void> {
if (!name) return

private async updateWithReplacer(id: number, replacer: (exist: Item) => Item): Promise<void> {
const items = await this.getItems()
const existId = Object.entries(items).find(([_, v]) => v.n === name)?.[0]
const exist = items[id]
if (!exist) return

if (existId) {
const replaced = replacer(exist)

if (Object.entries(items).some(([vid, v]) => v.n === replaced.n && parseInt(vid) !== id)) {
// Name exist already
return
}

items[id] = { ...items[id] || {}, n: name }
items[id] = replaced
await this.saveItems(items)
}

async updateName(id: number, name: string): Promise<void> {
await this.updateWithReplacer(id, exist => ({ ...exist, n: name }))
}

async update(cate: timer.site.Cate): Promise<void> {
await this.updateWithReplacer(cate.id, exist => ({
...exist,
n: cate.name,
a: cate.autoRules,
}))
}

async delete(id: number): Promise<void> {
const items = await this.getItems()

Expand Down
2 changes: 1 addition & 1 deletion src/background/install-handler/version/cate-initializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const DEMO_ITEMS: InitialCate[] = [

async function initItem(item: InitialCate) {
const { name, hosts } = item
const cate = await cateDatabase.add(name)
const cate = await cateDatabase.add({ name, autoRules: [] })
const cateId = cate.id
const siteKeys = hosts.map(host => ({ host, type: 'normal' } satisfies timer.site.SiteKey))
await batchChangeCate(cateId, siteKeys)
Expand Down
4 changes: 2 additions & 2 deletions src/background/message-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ class MessageDispatcher {
.register('option.weekStartTime', getWeekStartTime)
// Category
.register('cate.all', () => cateDatabase.listAll())
.register('cate.add', name => cateDatabase.add(name))
.register('cate.change', ({ id, name }) => cateDatabase.update(id, name))
.register('cate.add', data => cateDatabase.add(data))
.register('cate.change', data => cateDatabase.update(data))
.register('cate.delete', id => cateDatabase.delete(id))
// Meta information
.register('meta.installTs', getInstallTime)
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/message/app/site-manage-resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@
"relatedMsg": "This category has been associated with {siteCount} sites and cannot be deleted",
"removeConfirm": "Confirm to delete category: {category}?",
"batchChange": "Change categories",
"batchDisassociate": "Disassociate categories"
"batchDisassociate": "Disassociate categories",
"autoRules": "Auto-categorization rules"
},
"form": {
"emptyAlias": "Please enter site name",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/message/app/site-manage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type SiteManageMessage = {
batchChange: string
batchDisassociate: string
removeConfirm: string
autoRules: string
}
form: {
emptyAlias: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ const SiteOption = defineComponent<{ value: timer.site.SiteInfo }>(props => {
)
}, { props: ['value'] })

const notSetCate = (): timer.site.Cate => ({
id: CATE_NOT_SET_ID,
name: t(msg => msg.shared.cate.notSet),
autoRules: [],
})

const TargetSelect = defineComponent(() => {
const cate = useCategory()

Expand All @@ -90,7 +96,7 @@ const TargetSelect = defineComponent(() => {
})

const { data: allItems } = useRequest(
() => fetchItems([...cate.all, { id: CATE_NOT_SET_ID, name: t(msg => msg.shared.cate.notSet) }]),
() => fetchItems([...cate.all, notSetCate()]),
{ defaultValue: [[], []], deps: [() => cate.all] },
)

Expand Down
9 changes: 8 additions & 1 deletion src/pages/app/components/Report/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ const _default = defineComponent((_, ctx) => {
() => filter.siteMerge,
], () => table.value?.doLayout?.())

const handleCateChange = (key: timer.site.SiteKey, newCateId: number | undefined) => {
data.value?.list
?.filter(isSite)
?.filter(item => siteEqual(item.siteKey, key))
?.forEach(i => i.cateId = newCateId)
}

return () => (
<ContentCard>
<Flex gap={23} width="100%" height="100%" column>
Expand Down Expand Up @@ -135,7 +142,7 @@ const _default = defineComponent((_, ctx) => {
/>
</>}
{visible.value.group && <GroupColumn />}
{visible.value.cate && <CateColumn onChange={refresh} />}
{visible.value.cate && <CateColumn onChange={handleCateChange} />}
<TimeColumn dimension="focus" />
{runColVisible.value && <TimeColumn dimension="run" />}
<VisitColumn />
Expand Down
106 changes: 106 additions & 0 deletions src/pages/app/components/common/Category/CategoryDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@

import { t } from '@app/locale'
import { ElButton, ElDialog, ElForm, ElFormItem, ElInput, ElInputTag, ElMessage } from 'element-plus'
import { createVNode, defineComponent, reactive, ref, render } from 'vue'

type Props = {
cate?: timer.site.Cate
onClose?: NoArgCallback
onSave?: (cate: timer.site.Cate) => void
}

const Component = defineComponent<Props>(props => {
const visible = ref(true)
const formData = reactive<Partial<timer.site.Cate>>({ ...props.cate })

const handleConfirm = () => {

}
Comment on lines +16 to +18

const handleRulesChange = (rules: string[] | undefined = []) => {
if (new Set(rules).size < rules.length) {
ElMessage.warning('Rules contain duplicated items')
} else {
formData.autoRules = rules
}
}

return () => (
<ElDialog
modelValue={visible.value}
title={props.cate ? t(msg => msg.button.modify) : t(msg => msg.button.create)}
width={400}
destroyOnClose
beforeClose={props.onClose}
closeOnClickModal={false}
v-slots={{
footer: () => <>
<ElButton onClick={props.onClose}>{t(msg => msg.button.cancel)}</ElButton>
<ElButton type="primary" onClick={handleConfirm}>
{t(msg => msg.button.confirm)}
</ElButton>
</>
}}
>
<ElForm model={formData} labelPosition='top'>
<ElFormItem label={t(msg => msg.siteManage.cate.name)} prop="name" required>
<ElInput v-model={formData.name} />
</ElFormItem>
<ElFormItem label={t(msg => msg.siteManage.cate.autoRules)} prop="autoRules">
<ElInputTag
placeholder='**.google.com, *.example.com'
modelValue={formData.autoRules}
trigger='Space'
onChange={handleRulesChange}
/>
</ElFormItem>
</ElForm>
</ElDialog>
)
}, { props: ['cate', 'onClose', 'onSave'] })

interface DialogOptions {
cate?: timer.site.Cate
}

function open(options: DialogOptions = {}): Promise<timer.site.Cate> {
const { cate } = options

return new Promise<timer.site.Cate>((resolve, reject) => {
const container = document.createElement('div')
document.body.appendChild(container)

const cleanup = () => {
render(null, container)
document.body.removeChild(container)
}

const vnode = createVNode(Component, {
cate,
onClose: async (data: timer.site.Cate) => {
try {
resolve(data)
} catch (error) {
reject(error)
} finally {
cleanup()
}
},
onCancel: () => {
reject()
cleanup()
}
})

render(vnode, container)
})
}

type CategoryDialogDef = typeof Component & {
open: typeof open
}

const CategoryDialog = Component as CategoryDialogDef
CategoryDialog.open = open

export default CategoryDialog
9 changes: 4 additions & 5 deletions src/pages/app/components/common/Category/Editable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useManualRequest, useSwitch } from "@hooks"
import Flex from "@pages/components/Flex"
import { supportCategory } from "@util/site"
import { ElIcon, ElTag } from "element-plus"
import { computed, defineComponent, nextTick, ref } from "vue"
import { computed, defineComponent, ref } from "vue"
import Select, { type Instance } from "./Select"

type Props = ModelValue<number | undefined> & {
Expand Down Expand Up @@ -34,10 +34,9 @@ const CategoryEditable = defineComponent<Props>(props => {
},
})

const handleEditClick = (ev: MouseEvent) => {
ev.stopImmediatePropagation()
const handleEdit = () => {
openEditing()
nextTick(() => selectRef.value?.openOptions?.())
setTimeout(() => selectRef.value?.openOptions(), 100)
}

const selectRef = ref<Instance>()
Expand All @@ -64,7 +63,7 @@ const CategoryEditable = defineComponent<Props>(props => {
{current.value.name}
</ElTag>
}
<Flex align="center" onClick={handleEditClick}>
<Flex align="center" onClick={handleEdit}>
<ElIcon style={{ cursor: 'pointer' }}>
<Edit />
</ElIcon>
Expand Down
Loading
Loading