diff --git a/src/app/components/Limit/LimitFilter.tsx b/src/app/components/Limit/LimitFilter.tsx new file mode 100644 index 00000000..351332c1 --- /dev/null +++ b/src/app/components/Limit/LimitFilter.tsx @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2021 Hengyang Zhang + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +import { Operation, Plus, SetUp } from "@element-plus/icons-vue" +import { Ref, defineComponent, watch, ref } from "vue" +import InputFilterItem from "@app/components/common/input-filter-item" +import SwitchFilterItem from "@app/components/common/switch-filter-item" +import ButtonFilterItem from "@app/components/common/button-filter-item" +import { t } from "@app/locale" +import { getAppPageUrl } from "@util/constant/url" +import { OPTION_ROUTE } from "@app/router/constants" +import { createTabAfterCurrent } from "@api/chrome/tab" + +const optionPageUrl = getAppPageUrl(false, OPTION_ROUTE, { i: 'dailyLimit' }) + +const _default = defineComponent({ + props: { + url: String, + onlyEnabled: Boolean + }, + emits: { + create: () => true, + change: (_option: LimitFilterOption) => true, + test: () => true, + }, + setup(props, ctx) { + const url: Ref = ref(props.url) + const onlyEnabled: Ref = ref(props.onlyEnabled) + watch([url, onlyEnabled], () => ctx.emit("change", { + url: url.value, + onlyEnabled: onlyEnabled.value + })) + return () => <> + msg.limit.conditionFilter)} + onSearch={val => url.value = val} + /> + msg.limit.filterDisabled)} + defaultValue={onlyEnabled.value} + onChange={val => onlyEnabled.value = val} + /> + msg.limit.button.test)} + type="primary" + icon={} + onClick={() => ctx.emit("test")} + /> + msg.limit.button.option)} + icon={} + type="primary" + onClick={() => createTabAfterCurrent(optionPageUrl)} + /> + msg.button.create)} + type="success" + icon={} + onClick={() => ctx.emit("create")} + /> + + } +}) + +export default _default \ No newline at end of file diff --git a/src/app/components/limit/modify/sop/path-edit.ts b/src/app/components/Limit/LimitModify/Sop/LimitPathEdit.tsx similarity index 58% rename from src/app/components/limit/modify/sop/path-edit.ts rename to src/app/components/Limit/LimitModify/Sop/LimitPathEdit.tsx index 2deefcb8..5405f4f0 100644 --- a/src/app/components/limit/modify/sop/path-edit.ts +++ b/src/app/components/Limit/LimitModify/Sop/LimitPathEdit.tsx @@ -1,18 +1,18 @@ /** * Copyright (c) 2021 Hengyang Zhang - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ import { ElSwitch, ElTag, ElTooltip } from "element-plus" -import { defineComponent, h, PropType, reactive, ref, VNode, watch } from "vue" +import { defineComponent, PropType, reactive, ref, StyleValue, VNode, watch } from "vue" import { t } from "@app/locale" import { UrlPart } from "./common" -const switchStyle: Partial = { marginRight: '2px' } +const switchStyle: StyleValue = { marginRight: '2px' } -const tabStyle: Partial = { +const tabStyle: StyleValue = { marginBottom: '5px', marginRight: '0', } @@ -29,42 +29,29 @@ const ItemTag = defineComponent({ const myIgnored = ref(ignored) watch(myIgnored, () => ctx.emit("change", { ignored: myIgnored.value, origin })) - - return () => h(ElTag, { - type: 'info', - closable: true, - onClose: () => ctx.emit("close"), - style: tabStyle - }, () => [ - h(ElTooltip, { - content: t(msg => msg.limit.useWildcard) - }, { - default: () => h(ElSwitch, { - style: switchStyle, - modelValue: myIgnored.value, - onChange: (newVal: boolean) => myIgnored.value = newVal - }) - }), - h('span', {}, origin), - ]) + return () => ( + ctx.emit("close")} style={tabStyle}> + msg.limit.useWildcard)}> + myIgnored.value = !!val} + /> + + {origin} + + ) } }) -const item2Tag = (item: UrlPart, index: number, arr: UrlPart[]) => { - const isNotHost: boolean = !!index - return isNotHost - ? h(ItemTag, { part: item, onClose: () => arr.splice(index), onChange: p => arr[index] = p }) - : h(ElTag, { style: tabStyle }, () => h('span', item.origin)) -} - -const combineStyle = { +const combineStyle: StyleValue = { fontSize: '14px', margin: '0 2px', ...tabStyle } const combineTags = (arr: VNode[], current: VNode) => { - arr.length && arr.push(h('span', { style: combineStyle }, '/')) + arr.length && arr.push(/) arr.push(current) return arr } @@ -82,7 +69,6 @@ export type PathEditInstance = { } const _default = defineComponent({ - name: 'LimitPathEdit', props: { url: { type: String, @@ -107,9 +93,15 @@ const _default = defineComponent({ ctx.expose(instance) - return () => h('div', {}, items - .map((item, index, arr) => item2Tag(item, index, arr)) - .reduce(combineTags, []) + return () => ( +
+ { + items.map((item, idx, arr) => idx + ? arr.splice(idx)} onChange={p => arr[idx] = p} /> + : {item.origin} + ).reduce(combineTags, []) + } +
) } }) diff --git a/src/app/components/Limit/LimitModify/Sop/LimitPeriodFormItem.tsx b/src/app/components/Limit/LimitModify/Sop/LimitPeriodFormItem.tsx new file mode 100644 index 00000000..8803d088 --- /dev/null +++ b/src/app/components/Limit/LimitModify/Sop/LimitPeriodFormItem.tsx @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2021 Hengyang Zhang + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +import { t } from "@app/locale" +import { Check, Close, Plus } from "@element-plus/icons-vue" +import { ElButton, ElFormItem, ElTag, ElTimePicker } from "element-plus" +import { PropType, defineComponent, reactive, ref } from "vue" +import { checkImpact, dateMinute2Idx, mergePeriod, period2Str } from "@util/limit" + +const range2Period = (range: [Date, Date]): [number, number] => { + const start = range?.[0] + const end = range?.[1] + if (start === undefined || end === undefined) { + return undefined + } + const startIdx = dateMinute2Idx(start) + const endIdx = dateMinute2Idx(end) + return [Math.min(startIdx, endIdx), Math.max(startIdx, endIdx)] +} + +const PeriodInput = defineComponent({ + emits: { + close: () => true, + save: (_p: timer.limit.Period) => true, + }, + setup(_, ctx) { + const range = ref<[Date, Date]>() + const handleSave = () => { + const val = range2Period(range.value) + val && ctx.emit("save", val) + } + return () => ( +
+ range.value = val} + popperClass="limit-period-time-picker-popper" + isRange + rangeSeparator="-" + format="HH:mm" + clearable={false} + /> + } onClick={() => ctx.emit("close")} /> + } onClick={handleSave} /> +
+ ) + } +}) + +const insertPeriods = (periods: timer.limit.Period[], toInsert: timer.limit.Period) => { + if (!toInsert || !periods) return + let len = periods.length + if (!len) { + periods.push(toInsert) + return + } + for (let i = 0; i < len; i++) { + const pre = periods[i] + const next = periods[i + 1] + if (checkImpact(pre, toInsert)) { + mergePeriod(pre, toInsert) + if (checkImpact(pre, next)) { + mergePeriod(pre, next) + periods.splice(i + 1, 1) + } + return + } + if (checkImpact(toInsert, next)) { + mergePeriod(next, toInsert) + return + } + } + // Append + periods.push(toInsert) + periods.sort((a, b) => a[0] - b[0]) +} + +const _default = defineComponent({ + props: { + modelValue: Array as PropType + }, + setup({ modelValue }) { + const periods = reactive(modelValue || []) + const periodEditing = ref(false) + + return () => ( + msg.limit.item.period)}> +
+ {periods?.map((p, idx) => + periods.splice(idx, 1)} + > + {period2Str(p)} + + )} + {periodEditing.value + ? periodEditing.value = false} + onSave={p => { + insertPeriods(periods, p) + periodEditing.value = false + }} + /> + : } size="small" onClick={() => periodEditing.value = true}> + {t(msg => msg.button.create)} + + } +
+
+ ) + } +}) + +export default _default \ No newline at end of file diff --git a/src/app/components/limit/modify/sop/time-limit.ts b/src/app/components/Limit/LimitModify/Sop/LimitTimeFormItem.tsx similarity index 99% rename from src/app/components/limit/modify/sop/time-limit.ts rename to src/app/components/Limit/LimitModify/Sop/LimitTimeFormItem.tsx index 33568008..dd58f92b 100644 --- a/src/app/components/limit/modify/sop/time-limit.ts +++ b/src/app/components/Limit/LimitModify/Sop/LimitTimeFormItem.tsx @@ -1,6 +1,6 @@ /** * Copyright (c) 2021 Hengyang Zhang - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ diff --git a/src/app/components/Limit/LimitModify/Sop/Step1.tsx b/src/app/components/Limit/LimitModify/Sop/Step1.tsx new file mode 100644 index 00000000..d1632426 --- /dev/null +++ b/src/app/components/Limit/LimitModify/Sop/Step1.tsx @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2021 Hengyang Zhang + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +import { t } from "@app/locale" +import { Close, Right } from "@element-plus/icons-vue" +import { ElButton, ElForm, ElMessage } from "element-plus" +import { Ref, defineComponent, ref, watch } from "vue" +import { Protocol, parseUrl } from "./common" +import LimitUrlFormItem from "./url" +import LimitPathEdit, { PathEditInstance } from "./LimitPathEdit" + +const _default = defineComponent({ + props: { + defaultValue: { + type: String, + required: true, + }, + disabled: Boolean, + }, + emits: { + cancel: () => true, + next: (_cond: string) => true, + }, + setup(props, ctx) { + const { protocol: defaultProtocol, url: defaultUrl } = parseUrl(props.defaultValue) + const protocol: Ref = ref(defaultProtocol) + const pathEdit: Ref = ref() + const url: Ref = ref(defaultUrl) + + watch(() => props.defaultValue, () => { + const { protocol: newProtocol, url: newUrl } = parseUrl(props.defaultValue) + protocol.value = newProtocol + url.value = newUrl + }) + + const handleNext = () => { + let cond = props.defaultValue + if (!props.disabled) { + const urlVal = url.value?.trim?.() + if (!urlVal) { + return ElMessage.error(t(msg => msg.limit.message.noUrl)) + } + cond = urlVal ? protocol.value + urlVal : '' + } + ctx.emit("next", cond) + } + + return () => <> + + { + url.value = val + pathEdit.value?.updateUrl?.(val) + }} + onProtocolChange={val => protocol.value = val} + /> + + url.value = val} + /> + + + } +}) + +export default _default diff --git a/src/app/components/limit/modify/sop/step2.ts b/src/app/components/Limit/LimitModify/Sop/Step2.tsx similarity index 95% rename from src/app/components/limit/modify/sop/step2.ts rename to src/app/components/Limit/LimitModify/Sop/Step2.tsx index 1cdd1570..dc5971a2 100644 --- a/src/app/components/limit/modify/sop/step2.ts +++ b/src/app/components/Limit/LimitModify/Sop/Step2.tsx @@ -1,6 +1,6 @@ /** * Copyright (c) 2021 Hengyang Zhang - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -9,8 +9,8 @@ import { t } from "@app/locale" import { Back, Check } from "@element-plus/icons-vue" import { ElButton, ElForm, ElMessage } from "element-plus" import { PropType, Ref, computed, defineComponent, h, ref } from "vue" -import LimitTimeFormItem from "./time-limit" -import LimitPeriodFormItem from "./period" +import LimitTimeFormItem from "./LimitTimeFormItem" +import LimitPeriodFormItem from "./LimitPeriodFormItem" export type RuleFormData = Pick diff --git a/src/app/components/limit/modify/sop/common.ts b/src/app/components/Limit/LimitModify/Sop/common.ts similarity index 99% rename from src/app/components/limit/modify/sop/common.ts rename to src/app/components/Limit/LimitModify/Sop/common.ts index 8170040b..3be8e47d 100644 --- a/src/app/components/limit/modify/sop/common.ts +++ b/src/app/components/Limit/LimitModify/Sop/common.ts @@ -1,6 +1,6 @@ /** * Copyright (c) 2021 Hengyang Zhang - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ diff --git a/src/app/components/limit/modify/sop/index.ts b/src/app/components/Limit/LimitModify/Sop/index.tsx similarity index 50% rename from src/app/components/limit/modify/sop/index.ts rename to src/app/components/Limit/LimitModify/Sop/index.tsx index b4f6dc52..fe45742f 100644 --- a/src/app/components/limit/modify/sop/index.ts +++ b/src/app/components/Limit/LimitModify/Sop/index.tsx @@ -1,15 +1,15 @@ /** * Copyright (c) 2021 Hengyang Zhang - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ import { t } from "@app/locale" import { ElStep, ElSteps } from "element-plus" -import { Ref, defineComponent, h, nextTick, reactive, ref, toRaw } from "vue" -import Step1 from "./step1" -import Step2, { RuleFormData } from "./step2" +import { Ref, defineComponent, nextTick, reactive, ref, toRaw } from "vue" +import Step1 from "./Step1" +import Step2, { RuleFormData } from "./Step2" type Step = 0 | 1 @@ -33,7 +33,7 @@ const _default = defineComponent({ cancel: () => true, save: (_rule: timer.limit.Rule) => true, }, - setup({ }, ctx) { + setup(_, ctx) { const step: Ref = ref(0) const data = reactive(createInitial()) const condDisabled: Ref = ref(false) @@ -61,38 +61,40 @@ const _default = defineComponent({ data.visitTime = visitTime ? visitTime : undefined } - return () => h('div', { class: 'sop-dialog-container' }, [ - h('div', { class: 'step-container' }, h(ElSteps, { - space: 200, - finishStatus: 'success', - active: step.value, - }, () => [ - h(ElStep, { title: t(msg => msg.limit.step1) }), - h(ElStep, { title: t(msg => msg.limit.step2) }), - ])), - h('div', { class: 'operation-container' }, step.value === 0 - ? h(Step1, { - defaultValue: data?.cond, - disabled: condDisabled.value, - onCancel: () => ctx.emit('cancel'), - onNext: (cond: string) => { - step.value = 1 - !condDisabled.value && (data.cond = cond) - }, - }) - : h(Step2, { - rule: data, - onBack: f => { - restoreData(f) - step.value = 0 - }, - onSave: f => { - restoreData(f) - nextTick(() => ctx.emit('save', toRaw(data))) - }, - }) - ), - ]) + return () => ( +
+
+ + msg.limit.step1)} /> + msg.limit.step2)} /> + +
+
+ {step.value === 0 + ? ctx.emit("cancel")} + onNext={cond => { + step.value = 1 + !condDisabled.value && (data.cond = cond) + }} + /> + : { + restoreData(f) + step.value = 0 + }} + onSave={f => { + restoreData(f) + nextTick(() => ctx.emit('save', toRaw(data))) + }} + /> + } +
+
+ ) } }) diff --git a/src/app/components/limit/modify/sop/url.ts b/src/app/components/Limit/LimitModify/Sop/url.ts similarity index 100% rename from src/app/components/limit/modify/sop/url.ts rename to src/app/components/Limit/LimitModify/Sop/url.ts diff --git a/src/app/components/limit/modify/index.ts b/src/app/components/Limit/LimitModify/index.tsx similarity index 84% rename from src/app/components/limit/modify/index.ts rename to src/app/components/Limit/LimitModify/index.tsx index 45e3a549..6b8c2143 100644 --- a/src/app/components/limit/modify/index.ts +++ b/src/app/components/Limit/LimitModify/index.tsx @@ -1,13 +1,13 @@ /** * Copyright (c) 2021 Hengyang Zhang - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ import { ElDialog, ElMessage } from "element-plus" -import { computed, defineComponent, h, nextTick, ref, Ref } from "vue" -import Sop, { SopInstance } from "./sop" +import { computed, defineComponent, nextTick, ref, Ref } from "vue" +import Sop, { SopInstance } from "./Sop" import limitService from "@service/limit-service" import { t } from "@app/locale" import "./style/el-input.sass" @@ -69,16 +69,16 @@ const _default = defineComponent({ ctx.expose(instance) - return () => h(ElDialog, { - title: title.value, - modelValue: visible.value, - closeOnClickModal: false, - onClose - }, () => h(Sop, { - ref: sop, - onSave, - onCancel: onClose, - })) + return () => ( + + + + ) } }) diff --git a/src/app/components/limit/modify/style/el-input.sass b/src/app/components/Limit/LimitModify/style/el-input.sass similarity index 100% rename from src/app/components/limit/modify/style/el-input.sass rename to src/app/components/Limit/LimitModify/style/el-input.sass diff --git a/src/app/components/limit/modify/style/sop.sass b/src/app/components/Limit/LimitModify/style/sop.sass similarity index 100% rename from src/app/components/limit/modify/style/sop.sass rename to src/app/components/Limit/LimitModify/style/sop.sass diff --git a/src/app/components/Limit/LimitTable/column/LimitDelayColumn.tsx b/src/app/components/Limit/LimitTable/column/LimitDelayColumn.tsx new file mode 100644 index 00000000..e98236da --- /dev/null +++ b/src/app/components/Limit/LimitTable/column/LimitDelayColumn.tsx @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2021 Hengyang Zhang + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +import { InfoFilled } from "@element-plus/icons-vue" +import { ElIcon, ElSwitch, ElTableColumn, ElTooltip } from "element-plus" +import { defineComponent, toRaw } from "vue" +import { t } from "@app/locale" +import optionService from "@service/option-service" +import { ElTableRowScope } from "@src/element-ui/table" +import { judgeVerificationRequired, processVerification } from "@app/util/limit" + +const label = t(msg => msg.limit.item.delayAllowed) +const tooltip = t(msg => msg.limit.item.delayAllowedInfo) + +async function handleChange(row: timer.limit.Item, newVal: boolean): Promise { + if (newVal && await judgeVerificationRequired(row)) { + // Open delay for limited rules, so verification is required + const option = await optionService.getAllOption() + await processVerification(option) + } +} + +const _default = defineComponent({ + emits: { + rowChange: (_row: timer.limit.Rule, _val: boolean) => true, + }, + setup(_, ctx) { + return () => ( +
+ {`${label} `} + + + + + +
, + default: ({ row }: ElTableRowScope) => handleChange(row, val) + .then(() => { + row.allowDelay = val + ctx.emit("rowChange", toRaw(row), val) + }) + .catch(console.log) + } + /> + }} + /> + ) + } +}) + +export default _default diff --git a/src/app/components/Limit/LimitTable/column/LimitEnabledColumn.tsx b/src/app/components/Limit/LimitTable/column/LimitEnabledColumn.tsx new file mode 100644 index 00000000..962189e4 --- /dev/null +++ b/src/app/components/Limit/LimitTable/column/LimitEnabledColumn.tsx @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2021 Hengyang Zhang + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +import { ElSwitch, ElTableColumn } from "element-plus" +import { defineComponent, toRaw } from "vue" +import { t } from "@app/locale" +import optionService from "@service/option-service" +import { ElTableRowScope } from "@src/element-ui/table" +import { judgeVerificationRequired, processVerification } from "@app/util/limit" + +async function handleChange(row: timer.limit.Item, newVal: boolean): Promise { + if (!newVal && await judgeVerificationRequired(row)) { + // Disable limited rules, so verification is required + const option = await optionService.getAllOption() + await processVerification(option) + } +} + +const _default = defineComponent({ + emits: { + rowChange: (_row: timer.limit.Item, _val: boolean) => true + }, + setup(_, ctx) { + return () => ( + msg.limit.item.enabled)} + minWidth={80} + align="center" + > + {({ row }: ElTableRowScope) => handleChange(row, val) + .then(() => { + row.enabled = val + ctx.emit("rowChange", toRaw(row), val) + }) + .catch(console.log) + } + />} + + ) + } +}) + +export default _default \ No newline at end of file diff --git a/src/app/components/limit/table/column/operation.tsx b/src/app/components/Limit/LimitTable/column/LimitOperationColumn.tsx similarity index 99% rename from src/app/components/limit/table/column/operation.tsx rename to src/app/components/Limit/LimitTable/column/LimitOperationColumn.tsx index ed790e9a..301be8b3 100644 --- a/src/app/components/limit/table/column/operation.tsx +++ b/src/app/components/Limit/LimitTable/column/LimitOperationColumn.tsx @@ -10,9 +10,9 @@ import { ElButton, ElMessageBox, ElTableColumn } from "element-plus" import { defineComponent } from "vue" import { t } from "@app/locale" import optionService from "@service/option-service" -import { judgeVerificationRequired, processVerification } from "./common" import { locale } from "@i18n" import { ElTableRowScope } from "@src/element-ui/table" +import { judgeVerificationRequired, processVerification } from "@app/util/limit" const label = t((msg) => msg.limit.item.operation) const deleteButtonText = t((msg) => msg.button.delete) diff --git a/src/app/components/Limit/LimitTable/index.tsx b/src/app/components/Limit/LimitTable/index.tsx new file mode 100644 index 00000000..a7a1497b --- /dev/null +++ b/src/app/components/Limit/LimitTable/index.tsx @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2021 Hengyang Zhang + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +import { ElTable, ElTableColumn } from "element-plus" +import { defineComponent, PropType } from "vue" +import { t } from "@app/locale" +import { formatPeriodCommon } from "@util/time" +import { ElTableRowScope } from "@src/element-ui/table" +import { period2Str } from "@util/limit" +import LimitDelayColumn from "./column/LimitDelayColumn" +import LimitEnabledColumn from "./column/LimitEnabledColumn" +import LimitOperationColumn from "./column/LimitOperationColumn" + +const _default = defineComponent({ + props: { + data: Array as PropType + }, + emits: { + delayChange: (_row: timer.limit.Item) => true, + enabledChange: (_row: timer.limit.Item) => true, + delete: (_row: timer.limit.Item) => true, + modify: (_row: timer.limit.Item) => true, + }, + setup(props, ctx) { + return () => ( + + msg.limit.item.condition)} + minWidth={250} + align="center" + formatter={({ cond }: timer.limit.Item) => cond} + /> + msg.limit.item.time)} + minWidth={90} + align="center" + formatter={({ time }: timer.limit.Item) => time ? formatPeriodCommon(time * 1000) : '-'} + /> + msg.limit.item.waste)} + minWidth={90} + align="center" + formatter={({ waste }: timer.limit.Item) => formatPeriodCommon(waste)} + /> + msg.limit.item.visitTime)} + minWidth={90} + align="center" + formatter={({ visitTime }: timer.limit.Item) => visitTime ? formatPeriodCommon(visitTime * 1000) : '-'} + /> + msg.limit.item.period)} + minWidth={100} + align="center" + > + {({ row: { periods } }: ElTableRowScope) => + periods?.length + ?
+ {periods.map(p => {period2Str(p)})} +
+ : '-' + } +
+ ctx.emit("delayChange", row)} /> + ctx.emit("enabledChange", row)} /> + ctx.emit("delete", row)} + onRowModify={row => ctx.emit("modify", row)} + /> +
+ ) + } +}) + +export default _default + diff --git a/src/app/components/Limit/LimitTest.tsx b/src/app/components/Limit/LimitTest.tsx new file mode 100644 index 00000000..7ce73b96 --- /dev/null +++ b/src/app/components/Limit/LimitTest.tsx @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2023 Hengyang Zhang + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +import { t } from "@app/locale" +import limitService from "@service/limit-service" +import { ElAlert, AlertProps, ElButton, ElDialog, ElFormItem, ElInput } from "element-plus" +import { defineComponent, Ref, ref, h, ComputedRef, computed } from "vue" + +export type TestInstance = { + show(): void +} + +async function handleTest(url: string): Promise { + const items = await limitService.select({ url, filterDisabled: true }) + return items.map(v => v.cond) +} + +function computeResultTitle(url: string, inputting: boolean, matchedCondition: string[]): string { + if (!url) { + return t(msg => msg.limit.message.inputTestUrl) + } + if (inputting) { + return t(msg => msg.limit.message.clickTestButton, { buttonText: t(msg => msg.button.test) }) + } + if (!matchedCondition?.length) { + return t(msg => msg.limit.message.noRuleMatched) + } else { + return t(msg => msg.limit.message.rulesMatched) + } +} + +function computeResultDesc(url: string, inputting: boolean, matchedCondition: string[]): string[] { + if (!url || inputting || !matchedCondition?.length) { + return [] + } + return matchedCondition +} + +type _ResultType = AlertProps['type'] + +function computeResultType(url: string, inputting: boolean, matchedCondition: string[]): _ResultType { + if (!url || inputting) { + return 'info' + } + return matchedCondition?.length ? 'success' : 'warning' +} + +const _default = defineComponent({ + setup: (_props, ctx) => { + const url: Ref = ref() + const matchedCondition: Ref = ref([]) + const visible: Ref = ref(false) + const urlInputting: Ref = ref(true) + const resultTitle: ComputedRef = computed(() => computeResultTitle(url.value, urlInputting.value, matchedCondition.value)) + const resultType: ComputedRef<_ResultType> = computed(() => computeResultType(url.value, urlInputting.value, matchedCondition.value)) + const resultDesc: ComputedRef = computed(() => computeResultDesc(url.value, urlInputting.value, matchedCondition.value)) + + const changeInput = (newVal: string) => (urlInputting.value = true) && (url.value = newVal?.trim()) + const test = () => { + urlInputting.value = false + handleTest(url.value).then(matched => matchedCondition.value = matched) + } + + const instance: TestInstance = { + show() { + url.value = '' + visible.value = true + urlInputting.value = true + matchedCondition.value = [] + } + } + ctx.expose(instance) + return () => ( + msg.button.test)} + modelValue={visible.value} + closeOnClickModal={false} + onClose={() => visible.value = false} + > + msg.limit.button.test)}> + changeInput('')} + onKeydown={ev => (ev as KeyboardEvent).key === "Enter" && test()} + onInput={changeInput} + v-slots={{ + append: () => {t(msg => msg.button.test)} + }} + /> + + + {resultDesc.value.map(desc =>
  • {desc}
  • )} +
    +
    + ) + } +}) + +export default _default \ No newline at end of file diff --git a/src/app/components/Limit/index.tsx b/src/app/components/Limit/index.tsx new file mode 100644 index 00000000..f3955ca2 --- /dev/null +++ b/src/app/components/Limit/index.tsx @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2021 Hengyang Zhang + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +import { defineComponent, onMounted, ref, Ref } from "vue" +import ContentContainer from "../common/content-container" +import LimitFilter from "./LimitFilter" +import LimitTable from "./LimitTable" +import LimitModify, { ModifyInstance } from "./LimitModify" +import LimitTest, { TestInstance } from "./LimitTest" +import limitService from "@service/limit-service" +import { useRoute, useRouter } from "vue-router" +import { t } from "@app/locale" +import { ElMessage } from "element-plus" +import { handleWindowVisibleChange } from "@util/window" + +const initialUrl = () => { + // Init with url parameter + const urlParam = useRoute().query['url'] as string + useRouter().replace({ query: {} }) + return urlParam ? decodeURIComponent(urlParam) : '' +} + +const _default = defineComponent(() => { + const url: Ref = ref(initialUrl()) + const onlyEnabled: Ref = ref(false) + const data: Ref = ref([]) + // Init and query + const queryData = async () => { + const list = await limitService.select({ filterDisabled: onlyEnabled.value, url: url.value || '' }) + data.value = list + } + onMounted(queryData) + // Query data if the window become visible + handleWindowVisibleChange(queryData) + + const modify: Ref = ref() + const test: Ref = ref() + + return () => ( + ( + { + url.value = option.url + onlyEnabled.value = option.onlyEnabled + queryData() + }} + onCreate={() => modify.value?.create?.()} + onTest={() => test.value?.show?.()} + /> + ), + content: () => <> + limitService.updateDelay(row)} + onEnabledChange={row => limitService.updateEnabled(row)} + onDelete={async row => { + await limitService.remove(row) + ElMessage.success(t(msg => msg.limit.message.deleted)) + queryData() + }} + onModify={row => modify.value?.modify?.(row)} + /> + + + + }} + /> + ) +}) + +export default _default diff --git a/src/app/components/limit/limit.d.ts b/src/app/components/Limit/limit.d.ts similarity index 100% rename from src/app/components/limit/limit.d.ts rename to src/app/components/Limit/limit.d.ts diff --git a/src/app/components/option/common.ts b/src/app/components/Option/common.ts similarity index 97% rename from src/app/components/option/common.ts rename to src/app/components/Option/common.ts index 4318a9c3..b703213a 100644 --- a/src/app/components/option/common.ts +++ b/src/app/components/Option/common.ts @@ -1,6 +1,6 @@ /** * Copyright (c) 2021 Hengyang Zhang - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -13,7 +13,7 @@ import { OptionMessage } from "@i18n/message/app/option" /** * Render the option item - * + * * @param input input of this option, or param map * @param label label * @param defaultValue default value @@ -36,7 +36,7 @@ export function renderOptionItem( /** * Render text wrapped with tag - * + * * @param text text */ export function tagText(text: I18nKey): VNode { @@ -46,7 +46,7 @@ export function tagText(text: I18nKey): VNode { /** * Render the tooltip with message * - * @param content content + * @param content content * @since 0.5.0 */ export function tooltip(i18nKey: I18nKey): VNode { diff --git a/src/app/components/Option/components/LimitOption/index.tsx b/src/app/components/Option/components/LimitOption/index.tsx new file mode 100644 index 00000000..d25a57db --- /dev/null +++ b/src/app/components/Option/components/LimitOption/index.tsx @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2023 Hengyang Zhang + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +import { t } from "@app/locale" +import { locale } from "@i18n" +import optionService from "@service/option-service" +import { defaultDailyLimit } from "@util/constant/option" +import { ElDivider, ElInput, ElOption, ElSelect } from "element-plus" +import { defineComponent, reactive, unref, UnwrapRef, h, ref, Ref } from "vue" +import { renderOptionItem } from "../../common" +import "./limit-option.sass" +import { judgeVerificationRequired, processVerification } from "@app/util/limit" +import limitService from "@service/limit-service" + +const ALL_LIMIT_FILTER_TYPE: timer.limit.FilterType[] = [ + 'translucent', + 'groundGlass', +] + +const ALL_LEVEL: timer.limit.RestrictionLevel[] = [ + 'nothing', + 'verification', + 'password', +] + +const ALL_DIFF: timer.limit.VerificationDifficulty[] = [ + 'easy', + 'hard', + 'disgusting', +] + +const verifyTriggered = async (option: timer.option.DailyLimitOption, verified: Ref): Promise => { + if (verified.value) return + const items = await limitService.select({ filterDisabled: true, url: undefined }) + const triggerResults = await Promise.all((items || []).map(judgeVerificationRequired)) + const anyTrigger = triggerResults.some(t => !!t) + if (!anyTrigger) { + verified.value = true + return + } + return processVerification(option).then(() => { verified.value = true }) +} + +const filterSelect = (option: timer.option.DailyLimitOption) => + { + option.limitFilter = val + optionService.setDailyLimitOption(unref(option)) + }} + > + { + ALL_LIMIT_FILTER_TYPE.map(item => msg.option.dailyLimit.filter[item])} />) + } + + + +const levelSelect = (option: timer.option.DailyLimitOption, verified: Ref) => + verifyTriggered(option, verified) + .then(() => { + option.limitLevel = val + optionService.setDailyLimitOption(unref(option)) + }).catch(console.log) + } + > + {ALL_LEVEL.map(item => msg.option.dailyLimit.level[item])} />)} + + +const pswInput = (option: timer.option.DailyLimitOption, verified: Ref) => + verifyTriggered(option, verified) + .then(() => { + option.limitPassword = val?.trim() + optionService.setDailyLimitOption(unref(option)) + }).catch(console.log) + } + /> + +const veriDiffSelect = (option: timer.option.DailyLimitOption, verified: Ref) => + verifyTriggered(option, verified) + .then(() => { + option.limitVerifyDifficulty = val + optionService.setDailyLimitOption(unref(option)) + }).catch(console.log) + } + > + {ALL_DIFF.map(item => msg.option.dailyLimit.level.verificationDifficulty[item])} />)} + + +function copy(target: timer.option.DailyLimitOption, source: timer.option.DailyLimitOption) { + target.limitFilter = source.limitFilter + target.limitLevel = source.limitLevel + target.limitPassword = source.limitPassword + target.limitVerifyDifficulty = source.limitVerifyDifficulty +} + +function reset(target: timer.option.DailyLimitOption) { + const defaultValue = defaultDailyLimit() + // Not to reset limitPassword + delete defaultValue.limitPassword + // Not to reset difficulty + delete defaultValue.limitVerifyDifficulty + Object.entries(defaultValue).forEach(([key, val]) => target[key] = val) +} + +const _default = defineComponent((_, ctx) => { + const option: UnwrapRef = reactive(defaultDailyLimit()) + const verified = ref(false) + optionService.getAllOption().then(currentVal => { + copy(option, currentVal) + }) + ctx.expose({ reset: () => reset(option) }) + return () => <> + { + renderOptionItem( + filterSelect(option), + msg => msg.dailyLimit.filter.label, + t(msg => msg.option.dailyLimit.filter[defaultDailyLimit().limitFilter]), + ) + } + + { + renderOptionItem( + levelSelect(option, verified), + msg => msg.dailyLimit.level.label, + t(msg => msg.option.dailyLimit.level[defaultDailyLimit().limitLevel]), + ) + } + { + option.limitLevel === "password" && <> + + {renderOptionItem(pswInput(option, verified), msg => msg.dailyLimit.level.passwordLabel)} + + } + { + option.limitLevel === "verification" && <> + + { + renderOptionItem( + veriDiffSelect(option, verified), + msg => msg.dailyLimit.level.verificationLabel, + t(msg => msg.option.dailyLimit.level[defaultDailyLimit().limitVerifyDifficulty]) + ) + } + + } + +}) + +export default _default \ No newline at end of file diff --git a/src/app/components/option/components/daily-limit/daily-limit.sass b/src/app/components/Option/components/LimitOption/limit-option.sass similarity index 100% rename from src/app/components/option/components/daily-limit/daily-limit.sass rename to src/app/components/Option/components/LimitOption/limit-option.sass diff --git a/src/app/components/option/components/appearance/dark-mode-input.ts b/src/app/components/Option/components/appearance/dark-mode-input.ts similarity index 100% rename from src/app/components/option/components/appearance/dark-mode-input.ts rename to src/app/components/Option/components/appearance/dark-mode-input.ts diff --git a/src/app/components/option/components/appearance/index.ts b/src/app/components/Option/components/appearance/index.ts similarity index 100% rename from src/app/components/option/components/appearance/index.ts rename to src/app/components/Option/components/appearance/index.ts diff --git a/src/app/components/option/components/backup/auto-input.ts b/src/app/components/Option/components/backup/auto-input.ts similarity index 100% rename from src/app/components/option/components/backup/auto-input.ts rename to src/app/components/Option/components/backup/auto-input.ts diff --git a/src/app/components/option/components/backup/clear/index.ts b/src/app/components/Option/components/backup/clear/index.ts similarity index 100% rename from src/app/components/option/components/backup/clear/index.ts rename to src/app/components/Option/components/backup/clear/index.ts diff --git a/src/app/components/option/components/backup/clear/sop.ts b/src/app/components/Option/components/backup/clear/sop.ts similarity index 100% rename from src/app/components/option/components/backup/clear/sop.ts rename to src/app/components/Option/components/backup/clear/sop.ts diff --git a/src/app/components/option/components/backup/clear/step1.ts b/src/app/components/Option/components/backup/clear/step1.ts similarity index 100% rename from src/app/components/option/components/backup/clear/step1.ts rename to src/app/components/Option/components/backup/clear/step1.ts diff --git a/src/app/components/option/components/backup/clear/step2.ts b/src/app/components/Option/components/backup/clear/step2.ts similarity index 100% rename from src/app/components/option/components/backup/clear/step2.ts rename to src/app/components/Option/components/backup/clear/step2.ts diff --git a/src/app/components/option/components/backup/client-table.ts b/src/app/components/Option/components/backup/client-table.ts similarity index 100% rename from src/app/components/option/components/backup/client-table.ts rename to src/app/components/Option/components/backup/client-table.ts diff --git a/src/app/components/option/components/backup/download/index.ts b/src/app/components/Option/components/backup/download/index.ts similarity index 100% rename from src/app/components/option/components/backup/download/index.ts rename to src/app/components/Option/components/backup/download/index.ts diff --git a/src/app/components/option/components/backup/download/sop.ts b/src/app/components/Option/components/backup/download/sop.ts similarity index 100% rename from src/app/components/option/components/backup/download/sop.ts rename to src/app/components/Option/components/backup/download/sop.ts diff --git a/src/app/components/option/components/backup/download/step1.ts b/src/app/components/Option/components/backup/download/step1.ts similarity index 100% rename from src/app/components/option/components/backup/download/step1.ts rename to src/app/components/Option/components/backup/download/step1.ts diff --git a/src/app/components/option/components/backup/download/step2.ts b/src/app/components/Option/components/backup/download/step2.ts similarity index 100% rename from src/app/components/option/components/backup/download/step2.ts rename to src/app/components/Option/components/backup/download/step2.ts diff --git a/src/app/components/option/components/backup/footer.ts b/src/app/components/Option/components/backup/footer.ts similarity index 100% rename from src/app/components/option/components/backup/footer.ts rename to src/app/components/Option/components/backup/footer.ts diff --git a/src/app/components/option/components/backup/index.ts b/src/app/components/Option/components/backup/index.ts similarity index 100% rename from src/app/components/option/components/backup/index.ts rename to src/app/components/Option/components/backup/index.ts diff --git a/src/app/components/option/components/backup/style.sass b/src/app/components/Option/components/backup/style.sass similarity index 100% rename from src/app/components/option/components/backup/style.sass rename to src/app/components/Option/components/backup/style.sass diff --git a/src/app/components/option/components/popup.ts b/src/app/components/Option/components/popup.ts similarity index 99% rename from src/app/components/option/components/popup.ts rename to src/app/components/Option/components/popup.ts index 5f7bdcf9..7a09998d 100644 --- a/src/app/components/option/components/popup.ts +++ b/src/app/components/Option/components/popup.ts @@ -1,6 +1,6 @@ /** * Copyright (c) 2021 Hengyang Zhang - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ diff --git a/src/app/components/option/components/statistics.ts b/src/app/components/Option/components/statistics.ts similarity index 100% rename from src/app/components/option/components/statistics.ts rename to src/app/components/Option/components/statistics.ts diff --git a/src/app/components/option/index.ts b/src/app/components/Option/index.ts similarity index 97% rename from src/app/components/option/index.ts rename to src/app/components/Option/index.ts index 207ec108..b2762e54 100644 --- a/src/app/components/option/index.ts +++ b/src/app/components/Option/index.ts @@ -1,6 +1,6 @@ /** * Copyright (c) 2021 Hengyang Zhang - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -14,7 +14,7 @@ import Popup from "./components/popup" import Appearance from "./components/appearance" import Statistics from "./components/statistics" import Backup from './components/backup' -import DailyLimit from './components/daily-limit' +import LimitOption from './components/LimitOption' import './style' import { ElIcon, ElMessage, ElTabPane, ElTabs } from "element-plus" import { t } from "@app/locale" @@ -39,10 +39,10 @@ function initWithQuery(tab: Ref<_Category>) { /** * Handle before leave the option panel tabs - * + * * @param currentActiveNameAndOld - * @param paneRefMap - * @param router + * @param paneRefMap + * @param router * @returns promise to leave, or not */ function handleBeforeLeave( @@ -112,7 +112,7 @@ const _default = defineComponent({ h(ElTabPane, { label: t(msg => msg.menu.limit), name: "dailyLimit" as _Category - }, () => h(DailyLimit, { + }, () => h(LimitOption, { ref: paneRefMap.dailyLimit })), // Backup diff --git a/src/app/components/option/style/index.sass b/src/app/components/Option/style/index.sass similarity index 100% rename from src/app/components/option/style/index.sass rename to src/app/components/Option/style/index.sass diff --git a/src/app/components/limit/filter.ts b/src/app/components/limit/filter.ts deleted file mode 100644 index bde063e1..00000000 --- a/src/app/components/limit/filter.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) 2021 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { Operation, Plus, SetUp } from "@element-plus/icons-vue" -import { Ref, h, defineComponent, ref } from "vue" -import InputFilterItem from "@app/components/common/input-filter-item" -import SwitchFilterItem from "@app/components/common/switch-filter-item" -import ButtonFilterItem from "@app/components/common/button-filter-item" -import { t } from "@app/locale" -import { getAppPageUrl } from "@util/constant/url" -import { OPTION_ROUTE } from "@app/router/constants" -import { createTabAfterCurrent } from "@api/chrome/tab" - -const urlPlaceholder = t(msg => msg.limit.conditionFilter) -const onlyEnabledLabel = t(msg => msg.limit.filterDisabled) -const addButtonText = t(msg => msg.button.create) -const testButtonText = t(msg => msg.limit.button.test) -const optionButtonText = t(msg => msg.limit.button.option) -const optionPageUrl = getAppPageUrl(false, OPTION_ROUTE, { i: 'dailyLimit' }) - -const emits = { - create: () => true, - change: (_option: LimitFilterOption) => true, - test: () => true, -} - -const _default = defineComponent({ - name: "LimitFilter", - props: { - url: String, - onlyEnabled: Boolean - }, - emits, - setup(props, ctx) { - const url: Ref = ref(props.url) - const onlyEnabled: Ref = ref(props.onlyEnabled) - const handleChange = () => ctx.emit("change", { - url: url.value, - onlyEnabled: onlyEnabled.value - }) - return () => [ - h(InputFilterItem, { - defaultValue: props.url, - placeholder: urlPlaceholder, - onSearch(searchVal: string) { - url.value = searchVal - handleChange() - }, - }), - h(SwitchFilterItem, { - historyName: 'onlyEnabled', - label: onlyEnabledLabel, - defaultValue: onlyEnabled.value, - onChange(newVal: boolean) { - onlyEnabled.value = newVal - handleChange() - } - }), - h(ButtonFilterItem, { - text: testButtonText, - type: 'primary', - icon: Operation, - onClick: () => ctx.emit('test') - }), - h(ButtonFilterItem, { - text: optionButtonText, - icon: SetUp, - type: 'primary', - onClick: () => createTabAfterCurrent(optionPageUrl) - }), - h(ButtonFilterItem, { - text: addButtonText, - type: "success", - icon: Plus, - onClick: () => ctx.emit("create") - }) - ] - } -}) - -export default _default \ No newline at end of file diff --git a/src/app/components/limit/index.ts b/src/app/components/limit/index.ts deleted file mode 100644 index 6a1804d5..00000000 --- a/src/app/components/limit/index.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (c) 2021 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { defineComponent, h, ref, Ref } from "vue" -import ContentContainer from "../common/content-container" -import LimitFilter from "./filter" -import LimitTable from "./table" -import LimitModify, { ModifyInstance } from "./modify" -import LimitTest, { TestInstance } from "./test" -import limitService from "@service/limit-service" -import { useRoute, useRouter } from "vue-router" -import { t } from "@app/locale" -import { ElMessage } from "element-plus" -import { handleWindowVisibleChange } from "@util/window" - -const initialUrl = () => { - // Init with url parameter - const urlParam = useRoute().query['url'] as string - useRouter().replace({ query: {} }) - return urlParam ? decodeURIComponent(urlParam) : '' -} - -const _default = defineComponent(() => { - const url: Ref = ref(initialUrl()) - const onlyEnabled: Ref = ref(false) - const data: Ref = ref([]) - // Init and query - const queryData = async () => { - const list = await limitService.select({ filterDisabled: onlyEnabled.value, url: url.value || '' }) - data.value = list - } - queryData() - // Query data if the window become visible - handleWindowVisibleChange(queryData) - - const modify: Ref = ref() - const test: Ref = ref() - - return () => h(ContentContainer, {}, { - filter: () => h(LimitFilter, { - url: url.value, - onlyEnabled: onlyEnabled.value, - onChange(option: LimitFilterOption) { - url.value = option.url - onlyEnabled.value = option.onlyEnabled - queryData() - }, - onCreate: () => modify.value?.create?.(), - onTest: () => test.value?.show?.(), - }), - content: () => [ - h(LimitTable, { - data: data.value, - onDelayChange: (row: timer.limit.Item) => limitService.updateDelay(row), - onEnabledChange: (row: timer.limit.Item) => limitService.updateEnabled(row), - async onDelete(row: timer.limit.Item) { - await limitService.remove(row) - ElMessage.success(t(msg => msg.limit.message.deleted)) - queryData() - }, - async onModify(row: timer.limit.Item) { - modify.value?.modify?.(row) - } - }), - h(LimitModify, { - ref: modify, - onSave: queryData - }), - h(LimitTest, { - ref: test - }), - ] - }) -}) - -export default _default diff --git a/src/app/components/limit/modify/sop/period.ts b/src/app/components/limit/modify/sop/period.ts deleted file mode 100644 index b62acb87..00000000 --- a/src/app/components/limit/modify/sop/period.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright (c) 2021 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { t } from "@app/locale" -import { Check, Close, Plus } from "@element-plus/icons-vue" -import { ElButton, ElFormItem, ElTag, ElTimePicker } from "element-plus" -import { PropType, defineComponent, h, reactive, ref } from "vue" -import { checkImpact, mergePeriod, period2Str } from "@util/limit" - -const date2Idx = (date: Date): number => { - const hour = date.getHours() - const min = date.getMinutes() - return hour * 60 + min -} - -const range2Period = (range: [Date, Date]): [number, number] => { - const start = range?.[0] - const end = range?.[1] - if (start === undefined || end === undefined) { - return undefined - } - const startIdx = date2Idx(start) - const endIdx = date2Idx(end) - return [Math.min(startIdx, endIdx), Math.max(startIdx, endIdx)] -} - -const PeriodInput = defineComponent({ - emits: { - close: () => true, - save: (_p: timer.limit.Period) => true, - }, - setup(_, ctx) { - const range = ref<[Date, Date]>() - const handleSave = () => { - const val = range2Period(range.value) - val && ctx.emit("save", val) - } - return () => h('div', { class: "limit-period-input" }, [ - h(ElTimePicker, { - modelValue: range.value, - "onUpdate:modelValue": newVal => range.value = newVal, - popperClass: "limit-period-time-picker-popper", - isRange: true, - rangeSeparator: '-', - format: "HH:mm", - clearable: false, - }), - h(ElButton, { - icon: Close, - onClick: () => ctx.emit("close"), - }), - h(ElButton, { - icon: Check, - onClick: handleSave, - }), - ]) - } -}) - -const insertPeriods = (periods: timer.limit.Period[], toInsert: timer.limit.Period) => { - if (!toInsert || !periods) return - let len = periods.length - if (!len) { - periods.push(toInsert) - return - } - for (let i = 0; i < len; i++) { - const pre = periods[i] - const next = periods[i + 1] - if (checkImpact(pre, toInsert)) { - mergePeriod(pre, toInsert) - if (checkImpact(pre, next)) { - mergePeriod(pre, next) - periods.splice(i + 1, 1) - } - return - } - if (checkImpact(toInsert, next)) { - mergePeriod(next, toInsert) - return - } - } - // Append - periods.push(toInsert) - periods.sort((a, b) => a[0] - b[0]) -} - -const _default = defineComponent({ - props: { - modelValue: Array as PropType - }, - setup({ modelValue }) { - const periods = reactive(modelValue || []) - const periodEditing = ref(false) - - return () => h(ElFormItem, { - label: t(msg => msg.limit.item.period), - }, () => h('div', { class: "period-form-item-container" }, [ - ...periods?.map((p, index) => h(ElTag, { - closable: true, - size: "small", - onClose: () => periods.splice(index, 1) - }, () => period2Str(p))), - periodEditing.value - ? h(PeriodInput, { - onClose: () => periodEditing.value = false, - onSave: p => { - console.log(p) - insertPeriods(periods, p) - periodEditing.value = false - }, - }) - : h(ElButton, { - icon: Plus, - size: "small", - onClick: () => periodEditing.value = true, - }, () => t(msg => msg.button.create)), - ])) - }, -}) - - -export default _default \ No newline at end of file diff --git a/src/app/components/limit/modify/sop/step1.ts b/src/app/components/limit/modify/sop/step1.ts deleted file mode 100644 index 8c74c6c3..00000000 --- a/src/app/components/limit/modify/sop/step1.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2021 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { t } from "@app/locale" -import { Close, Right } from "@element-plus/icons-vue" -import { ElButton, ElForm, ElMessage } from "element-plus" -import { Ref, defineComponent, h, ref, watch } from "vue" -import { Protocol, parseUrl } from "./common" -import LimitUrlFormItem from "./url" -import LimitPathEdit, { PathEditInstance } from "./path-edit" - -const _default = defineComponent({ - props: { - defaultValue: { - type: String, - required: true, - }, - disabled: Boolean, - }, - emits: { - cancel: () => true, - next: (_cond: string) => true, - }, - setup({ disabled, defaultValue }, ctx) { - const { protocol, url } = parseUrl(defaultValue) - const protocalRef: Ref = ref(protocol) - const pathEditRef: Ref = ref() - const urlRef: Ref = ref(url) - - watch(() => defaultValue, () => { - const { protocol, url } = parseUrl(defaultValue) - protocalRef.value = protocol - urlRef.value = url - }) - - const handleNext = () => { - let cond = defaultValue - if (!disabled) { - const url = urlRef.value?.trim?.() - if (!url) { - return ElMessage.error(t(msg => msg.limit.message.noUrl)) - } - const protocol = protocalRef.value - cond = url ? protocol + url : '' - } - ctx.emit("next", cond) - } - - return () => { - const items = [ - h(ElForm, - { labelWidth: 180, labelPosition: "left" }, - () => h(LimitUrlFormItem, { - url: urlRef.value, - protocol: protocalRef.value, - disabled: disabled, - onUrlChange: (newUrl: string) => { - urlRef.value = newUrl - pathEditRef.value?.updateUrl?.(newUrl) - }, - onProtocolChange: (newProtocol: Protocol) => protocalRef.value = newProtocol, - }), - ), - ] - - !disabled && items.push(h(LimitPathEdit, { - ref: pathEditRef, - url: urlRef.value, - onUrlChange: (newVal: string) => urlRef.value = newVal - })) - items.push( - h('div', { class: 'sop-footer' }, [ - h(ElButton, { - type: 'info', - icon: Close, - onClick: () => ctx.emit('cancel'), - }, () => t(msg => msg.button.cancel)), - h(ElButton, { - type: 'primary', - icon: Right, - onClick: handleNext, - }, () => t(msg => msg.button.next)), - ]) - ) - return items - } - } -}) - -export default _default diff --git a/src/app/components/limit/table/column/cond.ts b/src/app/components/limit/table/column/cond.ts deleted file mode 100644 index 6a4eb5e5..00000000 --- a/src/app/components/limit/table/column/cond.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2021 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { ElTableColumn } from "element-plus" -import { defineComponent, h } from "vue" -import { t } from "@app/locale" - -const label = t(msg => msg.limit.item.condition) -const _default = defineComponent({ - name: "LimitCondColumn", - render: () => h(ElTableColumn, { - prop: 'cond', - label, - minWidth: 250, - align: 'center', - }, { - default: ({ row }: { row: timer.limit.Item }) => h('span', row.cond) - }) -}) - -export default _default \ No newline at end of file diff --git a/src/app/components/limit/table/column/delay.ts b/src/app/components/limit/table/column/delay.ts deleted file mode 100644 index a12b7bf2..00000000 --- a/src/app/components/limit/table/column/delay.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2021 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { InfoFilled } from "@element-plus/icons-vue" -import { ElIcon, ElSwitch, ElTableColumn, ElTooltip } from "element-plus" -import { defineComponent, h, toRaw } from "vue" -import { t } from "@app/locale" -import { judgeVerificationRequired, processVerification } from "./common" -import optionService from "@service/option-service" - -const label = t(msg => msg.limit.item.delayAllowed) -const tooltip = t(msg => msg.limit.item.delayAllowedInfo) - -async function handleChange(row: timer.limit.Item, newVal: boolean, callback: () => void) { - let promise: Promise = null - if (newVal && await judgeVerificationRequired(row)) { - // Open delay for limited rules, so verification is required - const option = await optionService.getAllOption() - promise = processVerification(option) - } - promise - ? promise.then(callback).catch(() => { }) - : callback() -} - -const _default = defineComponent({ - name: "LimitDelayColumn", - emits: { - rowChange: (_row: timer.limit.Rule, _val: boolean) => true, - }, - setup(_, ctx) { - return () => h(ElTableColumn, { - prop: 'delayClosed', - minWidth: 80, - align: 'center', - }, { - default: ({ row }: { row: timer.limit.Item }) => h(ElSwitch, { - modelValue: row.allowDelay, - onChange: (val: boolean) => handleChange(row, val, () => { - row.allowDelay = val - ctx.emit("rowChange", toRaw(row), val) - }) - }), - header: () => h('div', [ - label, - ' ', - h(ElTooltip, { - content: tooltip, - placement: 'top' - }, { - default: () => h(ElIcon, { size: 14, style: { paddingLeft: '4px' } }, () => h(InfoFilled)) - }) - ]) - }) - } -}) - -export default _default \ No newline at end of file diff --git a/src/app/components/limit/table/column/enabled.ts b/src/app/components/limit/table/column/enabled.ts deleted file mode 100644 index ac7939bc..00000000 --- a/src/app/components/limit/table/column/enabled.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2021 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { ElSwitch, ElTableColumn } from "element-plus" -import { defineComponent, h, toRaw } from "vue" -import { t } from "@app/locale" -import { judgeVerificationRequired, processVerification } from "./common" -import optionService from "@service/option-service" - -const label = t(msg => msg.limit.item.enabled) - -async function handleChange(row: timer.limit.Item, newVal: boolean, callback: () => void) { - let promise: Promise = null - if (!newVal && await judgeVerificationRequired(row)) { - // Disable limited rules, so verification is required - const option = await optionService.getAllOption() - promise = processVerification(option) - } - promise - ? promise.then(callback).catch(() => { }) - : callback() -} - -const _default = defineComponent({ - name: "LimitEnabledColumn", - emits: { - rowChange: (_row: timer.limit.Item, _val: boolean) => true - }, - setup(_, ctx) { - return () => h(ElTableColumn, { - prop: 'enabled', - label, - minWidth: 80, - align: 'center', - }, { - default: ({ row }: { row: timer.limit.Item }) => h(ElSwitch, { - modelValue: row.enabled, - onChange: (val: boolean) => handleChange(row, val, () => { - row.enabled = val - ctx.emit("rowChange", toRaw(row), val) - }) - }) - }) - } -}) - -export default _default \ No newline at end of file diff --git a/src/app/components/limit/table/column/period.ts b/src/app/components/limit/table/column/period.ts deleted file mode 100644 index 0cd1e4df..00000000 --- a/src/app/components/limit/table/column/period.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2023 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { ElTableColumn } from "element-plus" -import { defineComponent, h } from "vue" -import { t } from "@app/locale" -import { period2Str } from "@util/limit" - -const label = t(msg => msg.limit.item.period) - -const SPAN_STYLE: Partial = { - display: "block" -} - -const _default = defineComponent({ - setup() { - return () => h(ElTableColumn, { - label, - align: "center", - minWidth: 100, - }, { - default: ({ row }: { row: timer.limit.Item }) => { - const periods = row?.periods - if (!periods?.length) return "-" - const tags = periods.map(p => h("span", { style: SPAN_STYLE }, period2Str(p))) - return h('div', {}, tags) - } - }) - } -}) - -export default _default \ No newline at end of file diff --git a/src/app/components/limit/table/column/time.ts b/src/app/components/limit/table/column/time.ts deleted file mode 100644 index a42a076e..00000000 --- a/src/app/components/limit/table/column/time.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2021 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { ElTableColumn } from "element-plus" -import { defineComponent, h } from "vue" -import { formatPeriodCommon } from "@util/time" -import { t } from "@app/locale" - -const label = t(msg => msg.limit.item.time) - -const _default = defineComponent({ - name: "LimitTimeColumn", - render: () => h(ElTableColumn, { - prop: 'limit', - label, - minWidth: 90, - align: 'center', - }, { - default: ({ row }: { row: timer.limit.Item }) => row?.time ? h('span', formatPeriodCommon(row.time * 1000)) : '-' - }) -}) - -export default _default \ No newline at end of file diff --git a/src/app/components/limit/table/column/visit.ts b/src/app/components/limit/table/column/visit.ts deleted file mode 100644 index 3389d824..00000000 --- a/src/app/components/limit/table/column/visit.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2023 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { ElTableColumn } from "element-plus" -import { defineComponent, h } from "vue" -import { formatPeriodCommon } from "@util/time" -import { t } from "@app/locale" - -const label = t(msg => msg.limit.item.visitTime) - -const _default = defineComponent({ - render: () => h(ElTableColumn, { - label, - minWidth: 90, - align: 'center', - }, { - default: ({ row }: { row: timer.limit.Item }) => { - const visitTime = row?.visitTime - return visitTime ? h('span', formatPeriodCommon(row.visitTime * 1000)) : '-' - } - }) -}) - -export default _default \ No newline at end of file diff --git a/src/app/components/limit/table/column/waste.ts b/src/app/components/limit/table/column/waste.ts deleted file mode 100644 index 44f51542..00000000 --- a/src/app/components/limit/table/column/waste.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2021 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { ElTableColumn } from "element-plus" -import { defineComponent, h } from "vue" -import { formatPeriodCommon } from "@util/time" -import { t } from "@app/locale" - -const label = t(msg => msg.limit.item.waste) -const _default = defineComponent({ - name: "LimitWasteColumn", - render: () => h(ElTableColumn, { - prop: 'waste', - label, - minWidth: 90, - align: 'center', - }, { - default: ({ row }: { row: timer.limit.Item }) => h('span', formatPeriodCommon(row.waste)) - }) -} -) - -export default _default \ No newline at end of file diff --git a/src/app/components/limit/table/index.ts b/src/app/components/limit/table/index.ts deleted file mode 100644 index 99117009..00000000 --- a/src/app/components/limit/table/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2021 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { ElTable } from "element-plus" -import { defineComponent, h, PropType } from "vue" -import LimitCondColumn from "./column/cond" -import LimitTimeColumn from "./column/time" -import LimitWasteColumn from "./column/waste" -import LimitDelayColumn from "./column/delay" -import LimitEnabledColumn from "./column/enabled" -import LimitOperationColumn from "./column/operation" -import LimitVisitTimeColumn from "./column/visit" -import LimitPeriodColumn from "./column/period" - -const _default = defineComponent({ - props: { - data: Array as PropType - }, - emits: { - delayChange: (_row: timer.limit.Item) => true, - enabledChange: (_row: timer.limit.Item) => true, - delete: (_row: timer.limit.Item) => true, - modify: (_row: timer.limit.Item) => true, - }, - setup(props, ctx) { - return () => h(ElTable, { - border: true, - size: 'small', - style: { width: '100%' }, - highlightCurrentRow: true, - fit: true, - data: props.data - }, () => [ - h(LimitCondColumn), - h(LimitTimeColumn), - h(LimitWasteColumn), - h(LimitVisitTimeColumn), - h(LimitPeriodColumn), - h(LimitDelayColumn, { - onRowChange: (row: timer.limit.Item) => ctx.emit("delayChange", row) - }), - h(LimitEnabledColumn, { - onRowChange: (row: timer.limit.Item) => ctx.emit("enabledChange", row) - }), - h(LimitOperationColumn, { - onRowDelete: (row: timer.limit.Item) => ctx.emit("delete", row), - onRowModify: (row: timer.limit.Item) => ctx.emit("modify", row), - }) - ]) - } -}) - -export default _default - diff --git a/src/app/components/limit/test.ts b/src/app/components/limit/test.ts deleted file mode 100644 index c2e7fa72..00000000 --- a/src/app/components/limit/test.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) 2023 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { t } from "@app/locale" -import limitService from "@service/limit-service" -import { ElAlert, AlertProps, ElButton, ElDialog, ElFormItem, ElInput } from "element-plus" -import { defineComponent, Ref, ref, h, ComputedRef, computed } from "vue" - -export type TestInstance = { - show(): void -} - -async function handleTest(url: string): Promise { - const items = await limitService.select({ url, filterDisabled: true }) - return items.map(v => v.cond) -} - -function computeResultTitle(url: string, inputting: boolean, matchedCondition: string[]): string { - if (!url) { - return t(msg => msg.limit.message.inputTestUrl) - } - if (inputting) { - return t(msg => msg.limit.message.clickTestButton, { buttonText: t(msg => msg.button.test) }) - } - if (!matchedCondition?.length) { - return t(msg => msg.limit.message.noRuleMatched) - } else { - return t(msg => msg.limit.message.rulesMatched) - } -} - -function computeResultDesc(url: string, inputting: boolean, matchedCondition: string[]): string[] { - if (!url || inputting || !matchedCondition?.length) { - return [] - } - return matchedCondition -} - -type _ResultType = AlertProps['type'] - -function computeResultType(url: string, inputting: boolean, matchedCondition: string[]): _ResultType { - if (!url || inputting) { - return 'info' - } - return matchedCondition?.length ? 'success' : 'warning' -} - -const _default = defineComponent({ - setup(_props, ctx) { - const urlRef: Ref = ref() - const matchedConditionRef: Ref = ref([]) - const visible: Ref = ref(false) - const urlInputtingRef: Ref = ref(true) - const resultTitleRef: ComputedRef = computed(() => computeResultTitle(urlRef.value, urlInputtingRef.value, matchedConditionRef.value)) - const resultTypeRef: ComputedRef<_ResultType> = computed(() => computeResultType(urlRef.value, urlInputtingRef.value, matchedConditionRef.value)) - const resultDescRef: ComputedRef = computed(() => computeResultDesc(urlRef.value, urlInputtingRef.value, matchedConditionRef.value)) - - const changeInput = (newVal: string) => (urlInputtingRef.value = true) && (urlRef.value = newVal?.trim()) - const test = () => { - urlInputtingRef.value = false - handleTest(urlRef.value).then(matched => matchedConditionRef.value = matched) - } - - const instance: TestInstance = { - show() { - urlRef.value = '' - visible.value = true - urlInputtingRef.value = true - matchedConditionRef.value = [] - } - } - ctx.expose(instance) - - return () => h(ElDialog, { - title: t(msg => msg.button.test), - modelValue: visible.value, - closeOnClickModal: false, - onClose: () => visible.value = false - }, () => [ - h(ElFormItem, { - label: t(msg => msg.limit.button.test), - labelWidth: 120 - }, () => h(ElInput, { - modelValue: urlRef.value, - clearable: true, - onClear: () => changeInput(''), - onKeyup: (event: KeyboardEvent) => event.key === 'Enter' && test(), - onInput: (newVal: string) => changeInput(newVal) - }, { - append: () => h(ElButton, { - onClick: () => test() - }, () => t(msg => msg.button.test)), - })), - h(ElAlert, { - closable: false, - type: resultTypeRef.value, - title: resultTitleRef.value, - }, () => resultDescRef.value.map(desc => h('li', desc))) - ]) - } -}) - -export default _default \ No newline at end of file diff --git a/src/app/components/option/components/daily-limit/index.ts b/src/app/components/option/components/daily-limit/index.ts deleted file mode 100644 index a5448f2d..00000000 --- a/src/app/components/option/components/daily-limit/index.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) 2023 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import { t } from "@app/locale" -import { locale } from "@i18n" -import optionService from "@service/option-service" -import { defaultDailyLimit } from "@util/constant/option" -import { ElDivider, ElInput, ElOption, ElSelect } from "element-plus" -import { defineComponent, reactive, unref, UnwrapRef, h } from "vue" -import { renderOptionItem } from "../../common" -import "./daily-limit.sass" - -const ALL_LIMIT_FILTER_TYPE: timer.limit.FilterType[] = [ - 'translucent', - 'groundGlass', -] - -const ALL_LEVEL: timer.limit.RestrictionLevel[] = [ - 'nothing', - 'verification', - 'password', -] - -const ALL_DIFF: timer.limit.VerificationDifficulty[] = [ - 'easy', - 'hard', - 'disgusting', -] - -const filterSelect = (option: timer.option.DailyLimitOption) => h(ElSelect, { - modelValue: option.limitFilter, - size: 'small', - onChange: (val: timer.limit.FilterType) => { - option.limitFilter = val - optionService.setDailyLimitOption(unref(option)) - } -}, () => ALL_LIMIT_FILTER_TYPE.map(item => h(ElOption, { value: item, label: t(msg => msg.option.dailyLimit.filter[item]) }))) - -const levelSelect = (option: timer.option.DailyLimitOption) => h(ElSelect, { - modelValue: option.limitLevel, - size: 'small', - class: `option-daily-limit-level-select ${locale}`, - onChange: (val: timer.limit.RestrictionLevel) => { - option.limitLevel = val - optionService.setDailyLimitOption(unref(option)) - } -}, () => ALL_LEVEL.map(item => h(ElOption, { value: item, label: t(msg => msg.option.dailyLimit.level[item]) }))) - -const pswInput = (option: timer.option.DailyLimitOption) => h(ElInput, { - modelValue: option.limitPassword, - size: 'small', - type: 'password', - showPassword: true, - style: { width: '200px' }, - onInput: (val: string) => { - option.limitPassword = val?.trim() - optionService.setDailyLimitOption(unref(option)) - } -}) - -const veriDiffSelect = (option: timer.option.DailyLimitOption) => h(ElSelect, { - modelValue: option.limitVerifyDifficulty, - size: 'small', - onChange: (val: timer.limit.VerificationDifficulty) => { - option.limitVerifyDifficulty = val - optionService.setDailyLimitOption(unref(option)) - } -}, () => ALL_DIFF.map(item => h(ElOption, { value: item, label: t(msg => msg.option.dailyLimit.level.verificationDifficulty[item]) }))) - -function copy(target: timer.option.DailyLimitOption, source: timer.option.DailyLimitOption) { - target.limitFilter = source.limitFilter - target.limitLevel = source.limitLevel - target.limitPassword = source.limitPassword - target.limitVerifyDifficulty = source.limitVerifyDifficulty -} - -function reset(target: timer.option.DailyLimitOption) { - const defaultValue = defaultDailyLimit() - // Not to reset limitPassword - delete defaultValue.limitPassword - // Not to reset difficulty - delete defaultValue.limitVerifyDifficulty - Object.entries(defaultValue).forEach(([key, val]) => target[key] = val) -} - -const _default = defineComponent((_, ctx) => { - const option: UnwrapRef = reactive(defaultDailyLimit()) - optionService.getAllOption().then(currentVal => { - copy(option, currentVal) - }) - ctx.expose({ - reset: () => reset(option) - }) - return () => { - const nodes = [ - renderOptionItem({ - input: filterSelect(option) - }, - msg => msg.dailyLimit.filter.label, - t(msg => msg.option.dailyLimit.filter[defaultDailyLimit().limitFilter]) - ), - h(ElDivider), - renderOptionItem({ - input: levelSelect(option) - }, - msg => msg.dailyLimit.level.label, - t(msg => msg.option.dailyLimit.level[defaultDailyLimit().limitLevel]) - ), - ] - const { limitLevel } = option - limitLevel === 'password' && nodes.push( - h(ElDivider), - renderOptionItem({ - input: pswInput(option), - }, msg => msg.dailyLimit.level.passwordLabel) - ) - limitLevel === 'verification' && nodes.push( - h(ElDivider), - renderOptionItem({ - input: veriDiffSelect(option), - }, - msg => msg.dailyLimit.level.verificationLabel, - t(msg => msg.option.dailyLimit.level[defaultDailyLimit().limitVerifyDifficulty]) - ) - ) - return nodes - } -}) - -export default _default \ No newline at end of file diff --git a/src/app/router/index.ts b/src/app/router/index.ts index c22ac2da..3b6e9d66 100644 --- a/src/app/router/index.ts +++ b/src/app/router/index.ts @@ -43,7 +43,7 @@ const behaviorRoutes: RouteRecordRaw[] = [ component: () => import('../components/Habit') }, { path: LIMIT_ROUTE, - component: () => import('../components/limit') + component: () => import('../components/Limit') } ] @@ -62,7 +62,7 @@ const additionalRoutes: RouteRecordRaw[] = [ component: () => import('../components/rule-merge') }, { path: OPTION_ROUTE, - component: () => import('../components/option') + component: () => import('../components/Option') } ] diff --git a/src/app/components/limit/table/column/common.ts b/src/app/util/limit.tsx similarity index 64% rename from src/app/components/limit/table/column/common.ts rename to src/app/util/limit.tsx index 13de77b3..72999ff7 100644 --- a/src/app/components/limit/table/column/common.ts +++ b/src/app/util/limit.tsx @@ -1,12 +1,13 @@ -import { sendMsg2Runtime } from "@api/chrome/runtime" -import { t, tN } from "@app/locale" +import I18nNode from "@app/components/common/I18nNode" +import { t } from "@app/locale" import { locale } from "@i18n" import { VerificationPair } from "@service/limit-service/verification/common" import verificationProcessor from "@service/limit-service/verification/processor" -import { date2Idx, hasLimited } from "@util/limit" import { getCssVariable } from "@util/style" import { ElMessageBox, ElMessage } from "element-plus" -import { defineComponent, h, onMounted, ref, VNode } from "vue" +import { defineComponent, onMounted, ref, VNode } from "vue" +import { sendMsg2Runtime } from "@api/chrome/runtime" +import { hasLimited, dateMinute2Idx } from "@util/limit" /** * Judge wether verification is required @@ -20,8 +21,9 @@ export async function judgeVerificationRequired(item: timer.limit.Item): Promise if (hasLimited(item)) return true // Period if (periods?.length) { - const idx = date2Idx(new Date()) + const idx = dateMinute2Idx(new Date()) const hitPeriod = periods?.find(([s, e]) => s <= idx && e >= idx) + console.log(idx, periods, hitPeriod) if (hitPeriod) return true } // Visit @@ -32,17 +34,9 @@ export async function judgeVerificationRequired(item: timer.limit.Item): Promise return false } -const PROMPT_TXT_CSS: Partial = { - userSelect: 'none', -} const ANSWER_CANVAS_FONT_SIZE = 24 -const CANVAS_WRAPPER_CSS: Partial = { - fontSize: `${ANSWER_CANVAS_FONT_SIZE}px`, - textAlign: 'center', -} - const AnswerCanvas = defineComponent({ props: { text: String @@ -69,10 +63,17 @@ const AnswerCanvas = defineComponent({ ctx.fillText(text, 0, ANSWER_CANVAS_FONT_SIZE) }) - return () => h('div', { - style: CANVAS_WRAPPER_CSS, - ref: wrapper, - }, h('canvas', { ref: dom })) + return () => ( +
    + +
    + ) }) }) @@ -83,11 +84,11 @@ const AnswerCanvas = defineComponent({ export async function processVerification(option: timer.option.DailyLimitOption): Promise { const { limitLevel, limitPassword, limitVerifyDifficulty } = option let answerValue: string - let messageNodes: (VNode | string)[] + let messageNode: VNode | string | Element let incorrectMessage: string if (limitLevel === 'password' && limitPassword) { answerValue = limitPassword - messageNodes = [t(msg => msg.limit.verification.pswInputTip)] + messageNode = t(msg => msg.limit.verification.pswInputTip) incorrectMessage = t(msg => msg.limit.verification.incorrectPsw) } else if (limitLevel === 'verification') { const pair: VerificationPair = verificationProcessor.generate(limitVerifyDifficulty, locale) @@ -98,31 +99,39 @@ export async function processVerification(option: timer.option.DailyLimitOption) const promptTxt = typeof prompt === 'function' ? t(msg => prompt(msg.limit.verification), { ...promptParam, answer: answerValue }) : prompt - messageNodes = tN(msg => msg.limit.verification.inputTip, { prompt: h('b', promptTxt) }) + messageNode = ( + msg.limit.verification.inputTip} + param={{ prompt: {promptTxt} }} + /> + ) } else { const answer: VNode = limitVerifyDifficulty === 'disgusting' - ? h(AnswerCanvas, { text: answerValue }) - : h('b', answerValue) - messageNodes = tN(msg => msg.limit.verification.inputTip2, { answer }) + ? + : {answerValue} + messageNode = ( + msg.limit.verification.inputTip2} + param={{ answer }} + /> + ) } } - return messageNodes?.length && answerValue - ? new Promise(resolve => - ElMessageBox({ - boxType: 'prompt', - type: 'warning', - title: '', - message: h('div', { style: PROMPT_TXT_CSS }, messageNodes), - showInput: true, - showCancelButton: true, - showClose: true, - }).then(data => { - const { value } = data - if (value === answerValue) { - return resolve() - } - ElMessage.error(incorrectMessage) - }).catch(() => { }) - ) - : null + if (!messageNode || !answerValue) return Promise.resolve() + + return new Promise(resolve => { + ElMessageBox({ + boxType: 'prompt', + type: 'warning', + title: '', + message:
    {messageNode}
    , + showInput: true, + showCancelButton: true, + showClose: true, + }).then(data => { + const { value } = data + if (value === answerValue) return resolve() + ElMessage.error(incorrectMessage) + }).catch(() => { }) + }) } diff --git a/src/service/limit-service/index.ts b/src/service/limit-service/index.ts index 1ed6fa26..ae6de43c 100644 --- a/src/service/limit-service/index.ts +++ b/src/service/limit-service/index.ts @@ -1,6 +1,6 @@ /** * Copyright (c) 2021 Hengyang Zhang - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -17,7 +17,7 @@ const db: LimitDatabase = new LimitDatabase(storage) export type QueryParam = { filterDisabled: boolean - url: string + url?: string } async function select(cond?: QueryParam): Promise { @@ -48,7 +48,7 @@ async function noticePeriodChanged() { /** * Fired if the item is removed or disabled - * + * * @param item */ async function noticeLimitChanged() { @@ -96,8 +96,8 @@ async function getRelated(url: string): Promise { /** * Add time - * @param url url - * @param focusTime time, milliseconds + * @param url url + * @param focusTime time, milliseconds * @returns the rules is limit cause of this operation */ async function addFocusTime(url: string, focusTime: number) { diff --git a/src/util/limit.ts b/src/util/limit.ts index a4e14e33..a6a65126 100644 --- a/src/util/limit.ts +++ b/src/util/limit.ts @@ -39,6 +39,12 @@ const idx2Str = (time: number): string => { export const date2Idx = (date: Date): number => date.getHours() * 60 * 60 + date.getMinutes() * 60 + date.getSeconds() +export const dateMinute2Idx = (date: Date): number => { + const hour = date.getHours() + const min = date.getMinutes() + return hour * 60 + min +} + export const period2Str = (p: timer.limit.Period): string => { const [start, end] = p || [] return `${idx2Str(start)}-${idx2Str(end)}`