From f408fb1fdaf7b452b3cd82ab67daa9bae10f2122 Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Sun, 29 Jan 2023 11:39:46 +0800 Subject: [PATCH 1/7] Fix lint errors/warnings --- registry/lib/components/utils/import-series/logic.ts | 6 +++--- .../components/video/player/extend-speed/component.ts | 1 + src/components/i18n/dom-translator.ts | 10 ++++++++++ src/core/utils/index.ts | 1 + 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/registry/lib/components/utils/import-series/logic.ts b/registry/lib/components/utils/import-series/logic.ts index ac4786738..cf9f4cc6b 100644 --- a/registry/lib/components/utils/import-series/logic.ts +++ b/registry/lib/components/utils/import-series/logic.ts @@ -19,7 +19,7 @@ const importSeries = async (sid, uid, csrf) => { // 创建收藏夹,获取新收藏夹id let favId = 0 - while (true) { + for (;;) { const response = await fetch('https://api.bilibili.com/x/v3/fav/folder/add', { method: 'POST', credentials: 'include', @@ -43,7 +43,7 @@ const importSeries = async (sid, uid, csrf) => { for (let i = 0; i < seriesVideos.length; i++) { // 做个延迟,防止太快而遭服务器拒绝 await delay(500) - while (true) { + for (;;) { const response = await fetch('https://api.bilibili.com/x/v3/fav/resource/deal', { method: 'POST', credentials: 'include', @@ -72,7 +72,7 @@ const importSeries = async (sid, uid, csrf) => { } const importCollection = async (sid, csrf) => { - while (true) { + for (;;) { const response = await fetch('https://api.bilibili.com/x/v3/fav/season/fav', { method: 'POST', credentials: 'include', diff --git a/registry/lib/components/video/player/extend-speed/component.ts b/registry/lib/components/video/player/extend-speed/component.ts index d4bc9c82c..adf92751a 100644 --- a/registry/lib/components/video/player/extend-speed/component.ts +++ b/registry/lib/components/video/player/extend-speed/component.ts @@ -444,6 +444,7 @@ export class ExtendSpeedComponent extends EntrySpeedComponent { setTimeout(() => this.forceUpdateStyle(value)) } + // eslint-disable-next-line class-methods-use-this protected readonly filterNativeSpeed = () => ({ subscribe, next }: PublishContext) => { diff --git a/src/components/i18n/dom-translator.ts b/src/components/i18n/dom-translator.ts index 57d8c5675..9b95f4ec8 100644 --- a/src/components/i18n/dom-translator.ts +++ b/src/components/i18n/dom-translator.ts @@ -12,11 +12,15 @@ export class Translator { static map: Map static regex: [RegExp, string][] + // eslint-disable-next-line class-methods-use-this protected accepts = (node: Node) => node.nodeType === Node.ELEMENT_NODE + // eslint-disable-next-line class-methods-use-this protected getValue = (node: Node) => node.nodeValue + // eslint-disable-next-line class-methods-use-this protected setValue = (node: Node, value: string) => { node.nodeValue = value } + // eslint-disable-next-line class-methods-use-this protected getElement = (node: Node) => node as Element translate(node: Node) { let value = this.getValue(node) @@ -99,17 +103,23 @@ export class Translator { } } export class TextNodeTranslator extends Translator { + // eslint-disable-next-line class-methods-use-this accepts = (node: Node) => node.nodeType === Node.TEXT_NODE + // eslint-disable-next-line class-methods-use-this getElement = (node: Node) => node.parentElement } export class TitleTranslator extends Translator { + // eslint-disable-next-line class-methods-use-this getValue = (node: Node) => (node as Element).getAttribute('title') + // eslint-disable-next-line class-methods-use-this setValue = (node: Node, value: string) => { ;(node as Element).setAttribute('title', value) } } export class PlaceholderTranslator extends Translator { + // eslint-disable-next-line class-methods-use-this getValue = (node: Node) => (node as Element).getAttribute('placeholder') + // eslint-disable-next-line class-methods-use-this setValue = (node: Node, value: string) => { ;(node as Element).setAttribute('placeholder', value) } diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts index 270e5e04d..babc4e035 100644 --- a/src/core/utils/index.ts +++ b/src/core/utils/index.ts @@ -407,6 +407,7 @@ export class DoubleClickEvent { singleClickHandler: (e: MouseEvent) => void = none private clickedOnce = false + // eslint-disable-next-line class-methods-use-this private readonly stopPropagationHandler = (e: MouseEvent) => { e.stopImmediatePropagation() } From f2f9eb9a3e1ac8dcad09d64ae84afa3496f0a6ab Mon Sep 17 00:00:00 2001 From: JLoeve <34429322+LonelySteve@users.noreply.github.com> Date: Sat, 4 Feb 2023 22:53:11 +0800 Subject: [PATCH 2/7] feat(bisector): Add bisector --- src/components/bisector/DialogContent.vue | 56 +++++ src/components/bisector/DialogTitle.vue | 43 ++++ .../bisector/ResultToastContent.vue | 54 +++++ src/components/bisector/api.ts | 200 ++++++++++++++++++ src/components/bisector/bisect.ts | 70 ++++++ src/components/bisector/index.ts | 45 ++++ src/components/bisector/options.ts | 31 +++ src/components/built-in-components.ts | 2 + src/components/types.ts | 3 + src/components/user-component.ts | 15 ++ src/core/dialog/index.ts | 2 +- 11 files changed, 520 insertions(+), 1 deletion(-) create mode 100644 src/components/bisector/DialogContent.vue create mode 100644 src/components/bisector/DialogTitle.vue create mode 100644 src/components/bisector/ResultToastContent.vue create mode 100644 src/components/bisector/api.ts create mode 100644 src/components/bisector/bisect.ts create mode 100644 src/components/bisector/index.ts create mode 100644 src/components/bisector/options.ts diff --git a/src/components/bisector/DialogContent.vue b/src/components/bisector/DialogContent.vue new file mode 100644 index 000000000..9ef26d981 --- /dev/null +++ b/src/components/bisector/DialogContent.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/components/bisector/DialogTitle.vue b/src/components/bisector/DialogTitle.vue new file mode 100644 index 000000000..2ae4acad4 --- /dev/null +++ b/src/components/bisector/DialogTitle.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/components/bisector/ResultToastContent.vue b/src/components/bisector/ResultToastContent.vue new file mode 100644 index 000000000..09b3fc143 --- /dev/null +++ b/src/components/bisector/ResultToastContent.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/components/bisector/api.ts b/src/components/bisector/api.ts new file mode 100644 index 000000000..d7f91f322 --- /dev/null +++ b/src/components/bisector/api.ts @@ -0,0 +1,200 @@ +import { DialogInstance, showDialog } from '@/core/dialog' +import type { Settings } from '@/core/settings/types' +import { Toast } from '@/core/toast' +import { mountVueComponent } from '@/core/utils' +import { useScopedConsole } from '@/core/utils/log' +import type { RecordValue } from '../types' +import type { BisectNext } from './bisect' +import { bisect } from './bisect' +import { BisectorOptions } from './options' +import ResultToastContent from './ResultToastContent.vue' + +type UserComponent = RecordValue + +let bisectorOptions: BisectorOptions +let scopedConsole: ReturnType +let bisectorGenerator: ReturnType +let groupedComponents: Awaited> +let dialog: DialogInstance + +export const setOptions = (options: BisectorOptions) => { + if (!bisectorOptions) { + bisectorOptions = options + } else { + Object.assign(bisectorOptions, options) + } +} + +export const setConsole = (console: ReturnType) => { + scopedConsole = console +} + +const classifyComponents = async () => { + const { settings } = await import('@/core/settings') + + const { userComponents } = settings + const configurableUserComponents = lodash.pickBy( + userComponents, + v => v.metadata.configurable || true, + ) + const { targetComponents, keepDisabledComponents, keepEnabledComponents } = lodash.transform( + configurableUserComponents, + (result, v, k) => { + if (bisectorOptions.keepEnabledComponents?.includes(k)) { + result.keepEnabledComponents.push(v) + return + } + if (bisectorOptions.keepDisabledComponents?.includes(k)) { + result.keepDisabledComponents.push(v) + return + } + result.targetComponents.push(v) + }, + { + targetComponents: [] as UserComponent[], + keepDisabledComponents: [] as UserComponent[], + keepEnabledComponents: [] as UserComponent[], + }, + ) + + return { + userComponents, + configurableUserComponents, + targetComponents, + keepDisabledComponents, + keepEnabledComponents, + } +} + +const setComponentsEnabled = (components: UserComponent[], enabled: boolean) => + components.forEach(component => (component.settings.enabled = enabled)) + +const getComponentNames = (components: UserComponent[]) => + components + .map(component => `${component.metadata.displayName}(${component.metadata.name})`) + .join(', ') || '无' + +export const isRecover = () => !lodash.isEmpty(bisectorOptions.bisectInitialState) + +export const stop = async () => { + scopedConsole?.log('stop - 准备停止组件二等分') + dialog?.close() + const { configurableUserComponents } = await classifyComponents() + const unmatchedComponentNames = [] + for (const [componentName, componentSettings] of Object.entries(configurableUserComponents)) { + const originalStatus = bisectorOptions.originalComponentEnableState?.[componentName] + if (originalStatus == null) { + unmatchedComponentNames.push(componentName) + continue + } + componentSettings.settings.enabled = originalStatus + } + if (unmatchedComponentNames.length) { + scopedConsole?.warn( + `stop - 部分组件未能还原状态:${getComponentNames(unmatchedComponentNames)}`, + ) + } + scopedConsole?.log('stop - 清理状态') + bisectorGenerator = null + bisectorOptions.bisectInitialState = {} + scopedConsole?.log('stop - 重载页面') + location.reload() +} + +export const next = async (seeingBad?: boolean, autoReload?: boolean) => { + dialog?.close() + scopedConsole?.log( + `next - 当前工作状态:${ + // eslint-disable-next-line no-nested-ternary + seeingBad == null ? '未知' : seeingBad ? '异常' : '正常' + }`, + ) + const { done, value } = bisectorGenerator.next(seeingBad) as unknown as { + done: boolean + value: BisectNext | UserComponent + } + if (done) { + const elementId = `bisector-result-toast-content-${Math.floor( + Math.random() * (Number.MAX_SAFE_INTEGER + 1), + )}` + Toast.info(/* html */ `
`, '二等分结果') + setTimeout(() => { + const vm = mountVueComponent<{ userComponent: UserComponent }>( + ResultToastContent, + `#${elementId}`, + ) + vm.userComponent = value as UserComponent + vm.$on('restore', () => { + stop() + }) + }) + } else { + const { slice, low, high } = value as BisectNext + const needEnabled = slice + const needDisabled = lodash.difference(groupedComponents.targetComponents, slice) + bisectorOptions.bisectInitialState = { low, high } + scopedConsole?.log(`next - 关闭组件:${getComponentNames(needDisabled)}`) + scopedConsole?.log(`next - 开启组件:${getComponentNames(needEnabled)}`) + setComponentsEnabled(needDisabled, false) + setComponentsEnabled(needEnabled, true) + if (autoReload) { + scopedConsole?.log('next - 重载页面') + location.reload() + } + } + return { done, value } +} + +export const recover = async () => { + scopedConsole?.log('recover - 准备恢复组件二等分') + groupedComponents = await classifyComponents() + const { targetComponents, keepDisabledComponents, keepEnabledComponents } = groupedComponents + setComponentsEnabled(keepEnabledComponents, true) + setComponentsEnabled(keepDisabledComponents, false) + scopedConsole?.log(`recover - 保持关闭组件:${getComponentNames(keepDisabledComponents)}`) + scopedConsole?.log(`recover - 保持开启组件:${getComponentNames(keepEnabledComponents)}`) + scopedConsole?.log(`recover - 全部目标组件:${getComponentNames(targetComponents)}`) + bisectorGenerator = bisect(targetComponents, bisectorOptions.bisectInitialState) + const { done, value } = await next() + if (!done) { + const { rouge } = value as BisectNext + dialog = showDialog({ + title: () => import('./DialogTitle.vue'), + content: () => import('./DialogContent.vue'), + contentProps: { + rouge, + onGood: () => next(false, true), + onBad: () => next(true, true), + onAbort: () => stop(), + }, + }) + } +} + +export const start = async () => { + if (isRecover()) { + await recover() + return + } + scopedConsole?.log('start - 准备开始组件二等分') + groupedComponents = await classifyComponents() + const { + configurableUserComponents, + targetComponents, + keepDisabledComponents, + keepEnabledComponents, + } = groupedComponents + scopedConsole?.log('start - 保存组件初始启用状态') + bisectorOptions.originalComponentEnableState = lodash.mapValues( + configurableUserComponents, + v => v.settings.enabled, + ) + setComponentsEnabled(targetComponents, true) + setComponentsEnabled(keepEnabledComponents, true) + setComponentsEnabled(keepDisabledComponents, false) + scopedConsole?.log(`start - 保持关闭组件:${getComponentNames(keepDisabledComponents)}`) + scopedConsole?.log(`start - 保持开启组件:${getComponentNames(keepEnabledComponents)}`) + scopedConsole?.log(`start - 启用全部目标组件:${getComponentNames(targetComponents)}`) + bisectorGenerator = bisect(targetComponents, bisectorOptions.bisectInitialState) + await next(undefined, true) +} diff --git a/src/components/bisector/bisect.ts b/src/components/bisector/bisect.ts new file mode 100644 index 000000000..e7d8e1284 --- /dev/null +++ b/src/components/bisector/bisect.ts @@ -0,0 +1,70 @@ +/* eslint-disable no-bitwise */ + +export interface BisectNext { + low: number + high: number + mid: number + slice: O[] + rouge: number +} + +export interface InitialState { + low?: number + high?: number +} + +export function* bisectLeft(data: readonly O[], initialState?: InitialState) { + let low = initialState?.low ?? 0 + let high = initialState?.high ?? data.length + let mid = (low + high) >>> 1 + + while (true) { + const seeingBad = yield ({ + low, + high, + mid, + slice: data.slice(low, mid), + rouge: ~~Math.log2(high - low), + } as BisectNext) || false + + if (seeingBad) { + high = mid + } else { + low = mid + } + if (low + 1 < high) { + mid = (low + high) >>> 1 + } else { + return data[low] + } + } +} + +export function* bisectRight(data: readonly O[], initialState?: InitialState) { + let low = initialState?.low ?? 0 + let high = initialState?.high ?? data.length + let mid = (low + high) >>> 1 + + while (true) { + const seeingBad = yield ({ + low, + high, + mid, + slice: data.slice(mid, high), + rouge: ~~Math.log2(high - low), + } as BisectNext) || false + + if (seeingBad) { + low = mid + } else { + high = mid + } + if (low + 1 < high) { + mid = (low + high) >>> 1 + } else { + return data[low] + } + } +} + +export const bisect = bisectLeft diff --git a/src/components/bisector/index.ts b/src/components/bisector/index.ts new file mode 100644 index 000000000..cb69b857d --- /dev/null +++ b/src/components/bisector/index.ts @@ -0,0 +1,45 @@ +import { defineComponentMetadata } from '@/components/define' +import { bisectorOptionsMetadata } from './options' +import { LifeCycleEventTypes } from '@/core/life-cycle' +import { componentsTags } from '@/components/types' +import * as bisector from './api' +import { useScopedConsole } from '@/core/utils/log' +import type { LaunchBarActionProvider } from '../launch-bar/launch-bar-action' + +export const component = defineComponentMetadata({ + name: 'bisector', + displayName: '组件二等分', + tags: [componentsTags.general, componentsTags.utils], + hidden: true, + configurable: false, + entry: async ({ settings: { options } }) => { + bisector.setOptions(options) + bisector.setConsole(useScopedConsole('组件二等分')) + unsafeWindow.addEventListener(LifeCycleEventTypes.ComponentsLoaded, () => { + if (bisector.isRecover()) { + bisector.recover() + } + }) + }, + options: bisectorOptionsMetadata, + plugin: { + displayName: '组件二等分 - 功能扩展', + setup: ({ addData }) => { + addData('launchBar.actions', (providers: LaunchBarActionProvider[]) => { + providers.push({ + name: 'bisector-start', + getActions: async () => [ + { + name: '开始/继续组件二等分', + description: 'Start/Continue component bisection', + icon: 'mdi-view-split-horizontal', + action: async () => { + await bisector.start() + }, + }, + ], + }) + }) + }, + }, +}) diff --git a/src/components/bisector/options.ts b/src/components/bisector/options.ts new file mode 100644 index 000000000..d96d8342c --- /dev/null +++ b/src/components/bisector/options.ts @@ -0,0 +1,31 @@ +import { defineOptionsMetadata, OptionsOfMetadata } from '@/components/define' +import { getComponentSettings } from '@/core/settings' +import type { InitialState } from './bisect' + +export const bisectorOptionsMetadata = defineOptionsMetadata({ + // 原始的组件启用状态 + originalComponentEnableState: { + defaultValue: {} as Record, + hidden: true, + }, + // 保持禁用状态的组件内部名称数组 + keepDisabledComponents: { + defaultValue: [] as string[], + hidden: true, + }, + // 保持启用状态的组件内部名称数组 + keepEnabledComponents: { + defaultValue: [] as string[], + hidden: true, + }, + // bisect 生成器的初始状态 + bisectInitialState: { + defaultValue: {} as Partial, + hidden: true, + }, +}) + +export const getBisectorOptions = () => + getComponentSettings>('bisector').options + +export type BisectorOptions = ReturnType diff --git a/src/components/built-in-components.ts b/src/components/built-in-components.ts index e9a0b98da..1dd47ed85 100644 --- a/src/components/built-in-components.ts +++ b/src/components/built-in-components.ts @@ -4,6 +4,7 @@ import { component as LaunchBar } from './launch-bar' import { component as I18n } from './i18n' import { component as AutoUpdate } from './auto-update' import { component as NotifyNewVersion } from './notify-new-version' +import { component as Bisector } from './bisector' export const getBuiltInComponents = (): ComponentMetadata[] => [ SettingsPanel, @@ -11,6 +12,7 @@ export const getBuiltInComponents = (): ComponentMetadata[] => [ I18n, AutoUpdate, NotifyNewVersion, + Bisector, ] export const isBuiltInComponent = (name: string) => diff --git a/src/components/types.ts b/src/components/types.ts index c3b7c411a..38261f968 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -216,3 +216,6 @@ export interface ComponentMetadata /** 用户组件的非函数基本信息, 用于直接保存为 JSON */ export type UserComponentMetadata = Omit + +/** 推断 Record 的 Value 类型 */ +export type RecordValue = R extends Record ? V : never diff --git a/src/components/user-component.ts b/src/components/user-component.ts index d2c81f969..b783c708d 100644 --- a/src/components/user-component.ts +++ b/src/components/user-component.ts @@ -1,6 +1,8 @@ import { componentToSettings } from '@/core/settings' import { isBuiltInComponent } from './built-in-components' import { ComponentMetadata, componentsMap } from './component' +import * as bisector from './bisector/api' +import { BisectorOptions } from './bisector/options' /** * 安装自定义组件 @@ -130,3 +132,16 @@ export const toggleComponent = async (nameOrDisplayName: string) => { const { displayName } = userComponent.metadata return `已${enabled ? '开启' : '关闭'}组件'${displayName}', 可能需要刷新后才能生效` } + +/** + * 二等分自定义组件的开关状态 + * + * @param options 二等分选项 + * @returns + */ +export const bisectComponent = async (options?: BisectorOptions) => { + if (options) { + bisector.setOptions(options) + } + return bisector +} diff --git a/src/core/dialog/index.ts b/src/core/dialog/index.ts index b7c160a6f..8c21794a1 100644 --- a/src/core/dialog/index.ts +++ b/src/core/dialog/index.ts @@ -10,7 +10,7 @@ export interface DialogInputs { } export interface DialogInstance extends Required { open: boolean - close: Promise + close(): Promise closeListeners: (() => void)[] } export const showDialog = (inputs: DialogInputs) => { From de541ec953617f2bcb4f84a033d30a9365ebdf13 Mon Sep 17 00:00:00 2001 From: JLoeve <34429322+LonelySteve@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:58:53 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E6=9C=AA=E8=83=BD=E8=BF=98=E5=8E=9F=E7=8A=B6=E6=80=81=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E5=BC=82=E5=B8=B8=EF=BC=8C=E6=94=B9=E8=BF=9B=E4=BA=8C?= =?UTF-8?q?=E7=AD=89=E5=88=86=E7=BB=93=E6=9E=9C=E5=88=B7=E6=96=B0=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=90=8E=E7=9A=84=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/bisector/api.ts | 50 +++++++++++++------------- src/components/bisector/bisect.ts | 60 +++++++++++++++++++++---------- src/core/utils/index.ts | 8 +++++ 3 files changed, 73 insertions(+), 45 deletions(-) diff --git a/src/components/bisector/api.ts b/src/components/bisector/api.ts index d7f91f322..6e650fcf4 100644 --- a/src/components/bisector/api.ts +++ b/src/components/bisector/api.ts @@ -1,10 +1,10 @@ import { DialogInstance, showDialog } from '@/core/dialog' import type { Settings } from '@/core/settings/types' import { Toast } from '@/core/toast' -import { mountVueComponent } from '@/core/utils' +import { getRandomId, mountVueComponent, sleep } from '@/core/utils' import { useScopedConsole } from '@/core/utils/log' import type { RecordValue } from '../types' -import type { BisectNext } from './bisect' +import type { BisectNext, BisectReturn } from './bisect' import { bisect } from './bisect' import { BisectorOptions } from './options' import ResultToastContent from './ResultToastContent.vue' @@ -13,7 +13,7 @@ type UserComponent = RecordValue let bisectorOptions: BisectorOptions let scopedConsole: ReturnType -let bisectorGenerator: ReturnType +let bisectorGenerator: Generator, BisectReturn> let groupedComponents: Awaited> let dialog: DialogInstance @@ -80,23 +80,25 @@ export const stop = async () => { scopedConsole?.log('stop - 准备停止组件二等分') dialog?.close() const { configurableUserComponents } = await classifyComponents() - const unmatchedComponentNames = [] + const unmatchedComponents: UserComponent[] = [] for (const [componentName, componentSettings] of Object.entries(configurableUserComponents)) { const originalStatus = bisectorOptions.originalComponentEnableState?.[componentName] if (originalStatus == null) { - unmatchedComponentNames.push(componentName) + unmatchedComponents.push(componentSettings) continue } componentSettings.settings.enabled = originalStatus } - if (unmatchedComponentNames.length) { - scopedConsole?.warn( - `stop - 部分组件未能还原状态:${getComponentNames(unmatchedComponentNames)}`, - ) + if (unmatchedComponents.length) { + const msg = `部分组件未能还原状态:${getComponentNames(unmatchedComponents)}` + scopedConsole?.warn(`stop - ${msg}`) + Toast.error(msg, '组件二等分') + await sleep(3e3) } scopedConsole?.log('stop - 清理状态') bisectorGenerator = null bisectorOptions.bisectInitialState = {} + bisectorOptions.originalComponentEnableState = {} scopedConsole?.log('stop - 重载页面') location.reload() } @@ -109,24 +111,20 @@ export const next = async (seeingBad?: boolean, autoReload?: boolean) => { seeingBad == null ? '未知' : seeingBad ? '异常' : '正常' }`, ) - const { done, value } = bisectorGenerator.next(seeingBad) as unknown as { - done: boolean - value: BisectNext | UserComponent - } + const { done, value } = bisectorGenerator.next(seeingBad) if (done) { - const elementId = `bisector-result-toast-content-${Math.floor( - Math.random() * (Number.MAX_SAFE_INTEGER + 1), - )}` - Toast.info(/* html */ `
`, '二等分结果') - setTimeout(() => { - const vm = mountVueComponent<{ userComponent: UserComponent }>( - ResultToastContent, - `#${elementId}`, - ) - vm.userComponent = value as UserComponent - vm.$on('restore', () => { - stop() - }) + const { low, high } = value + bisectorOptions.bisectInitialState = { low, high } + const elementId = `bisector-result-toast-content-${getRandomId()}` + Toast.info(/* html */ `
`, '组件二等分结果') + await sleep() + const vm = mountVueComponent<{ userComponent: UserComponent }>( + ResultToastContent, + `#${elementId}`, + ) + vm.userComponent = value.target + vm.$on('restore', () => { + stop() }) } else { const { slice, low, high } = value as BisectNext diff --git a/src/components/bisector/bisect.ts b/src/components/bisector/bisect.ts index e7d8e1284..017bf8455 100644 --- a/src/components/bisector/bisect.ts +++ b/src/components/bisector/bisect.ts @@ -8,62 +8,84 @@ export interface BisectNext { rouge: number } +export interface BisectReturn extends BisectNext { + target: O +} + export interface InitialState { low?: number high?: number } -export function* bisectLeft(data: readonly O[], initialState?: InitialState) { +export function* bisectLeft( + data: readonly O[], + initialState?: InitialState, +): Generator, BisectReturn> { let low = initialState?.low ?? 0 let high = initialState?.high ?? data.length - let mid = (low + high) >>> 1 + let mid: number - while (true) { - const seeingBad = yield ({ + while (low + 1 < high) { + mid = (low + high) >>> 1 + + const seeingBad = yield { low, high, mid, slice: data.slice(low, mid), rouge: ~~Math.log2(high - low), - } as BisectNext) || false + } if (seeingBad) { high = mid } else { low = mid } - if (low + 1 < high) { - mid = (low + high) >>> 1 - } else { - return data[low] - } + } + + return { + low, + high, + mid, + slice: data.slice(low, mid), + rouge: ~~Math.log2(high - low), + target: data[low], } } -export function* bisectRight(data: readonly O[], initialState?: InitialState) { +export function* bisectRight( + data: readonly O[], + initialState?: InitialState, +): Generator, BisectReturn> { let low = initialState?.low ?? 0 let high = initialState?.high ?? data.length let mid = (low + high) >>> 1 - while (true) { - const seeingBad = yield ({ + while (low + 1 < high) { + mid = (low + high) >>> 1 + + const seeingBad = yield { low, high, mid, slice: data.slice(mid, high), rouge: ~~Math.log2(high - low), - } as BisectNext) || false + } if (seeingBad) { low = mid } else { high = mid } - if (low + 1 < high) { - mid = (low + high) >>> 1 - } else { - return data[low] - } + } + + return { + low, + high, + mid, + slice: data.slice(low, mid), + rouge: ~~Math.log2(high - low), + target: data[low], } } diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts index 270e5e04d..797f88631 100644 --- a/src/core/utils/index.ts +++ b/src/core/utils/index.ts @@ -634,3 +634,11 @@ export const getRandomId = (length = 8) => { .join('') .substring(0, length) } + +/** + * 异步延时指定毫秒数 + * + * @param ms 传递给 setTimeout 的第二个参数,表示延时时间 + * @returns + */ +export const sleep = (ms?: number) => new Promise(resolve => setTimeout(resolve, ms)) From e7288c8e1f6a89a2a58bb0dcca3c5b3a7896ec4f Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Mon, 20 Feb 2023 02:59:04 +0800 Subject: [PATCH 4/7] Change load-feature-code.ts --- src/core/external-input/load-feature-code.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/core/external-input/load-feature-code.ts b/src/core/external-input/load-feature-code.ts index 7e926330a..95ef0d805 100644 --- a/src/core/external-input/load-feature-code.ts +++ b/src/core/external-input/load-feature-code.ts @@ -1,5 +1,8 @@ export class LoadFeatureCodeError extends Error {} +/** + * feature 代码运行沙箱 + */ interface CodeSandbox { /** * 在沙箱中执行代码 @@ -15,11 +18,11 @@ interface CodeSandbox { } /** - * 获取代码运行沙箱 + * 创建 feature 代码的运行沙箱 * - * @returns 一个函数:接受代码,返回。 + * @returns 一个 `CodeSandbox`。 */ -const getSandbox = lodash.once((): CodeSandbox => { +const createCodeSandbox = (): CodeSandbox => { // 需要被注入到 `sandbox` 中的键值对 const injection = new Map([ // 加固,防止逃逸 @@ -55,8 +58,9 @@ const getSandbox = lodash.once((): CodeSandbox => { return [exported, returned] }, } -}) +} +let staticCodeSandbox: CodeSandbox | undefined /** * 执行 feature (component, plugin, style) 的代码,并尝试获取其导出元数据 * @@ -69,14 +73,15 @@ const getSandbox = lodash.once((): CodeSandbox => { * * 代码默认以非严格模式执行,启用需自行添加 `use strict`。(从本项目中打包的 feature 自带严格模式) * - * 全局对象为脚本管理器提供的 `window`,支持访问 `unsafeWindow`。 + * 代码执行时的全局对象为脚本管理器提供的 `window`。代码中支持访问 `unsafeWindow`。 * * @param code - 被执行的代码 - * @returns 导出的元数据(不检测正确性) + * @returns 导出的元数据(不检测是否为正确的 feature) * @throws {@link LoadFeatureCodeError} * 代码包含语法错误或代码执行时产生了异常 */ export const loadFeatureCode = (code: string): unknown => { - const [exported, returned] = getSandbox().run(code) + staticCodeSandbox || (staticCodeSandbox = createCodeSandbox()) + const [exported, returned] = staticCodeSandbox.run(code) return exported || returned } From 3fd8f6ddb0eca6400d92f12413a162f96a4fc2c0 Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Mon, 20 Feb 2023 04:05:27 +0800 Subject: [PATCH 5/7] Change interface to type alias --- src/core/external-input/load-feature-code.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/external-input/load-feature-code.ts b/src/core/external-input/load-feature-code.ts index 95ef0d805..0b283d780 100644 --- a/src/core/external-input/load-feature-code.ts +++ b/src/core/external-input/load-feature-code.ts @@ -3,7 +3,7 @@ export class LoadFeatureCodeError extends Error {} /** * feature 代码运行沙箱 */ -interface CodeSandbox { +type CodeSandbox = { /** * 在沙箱中执行代码 * From 6ad8b3e27bff49fcda3d87bb7245633f3e28d975 Mon Sep 17 00:00:00 2001 From: JLoeve <34429322+LonelySteve@users.noreply.github.com> Date: Mon, 20 Feb 2023 22:40:53 +0800 Subject: [PATCH 6/7] fix: Improve bisector --- src/components/bisector/DialogContent.vue | 2 +- src/components/bisector/api.ts | 6 +++--- src/components/bisector/index.ts | 2 +- src/core/utils/index.ts | 8 -------- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/components/bisector/DialogContent.vue b/src/components/bisector/DialogContent.vue index 9ef26d981..544247986 100644 --- a/src/components/bisector/DialogContent.vue +++ b/src/components/bisector/DialogContent.vue @@ -40,7 +40,7 @@ export default Vue.extend({