Skip to content

Commit

Permalink
feat: present meta.defaultOptions in the config inspector (#110)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <[email protected]>
  • Loading branch information
scottohara and antfu authored Dec 10, 2024
1 parent ab526d0 commit 9820d63
Show file tree
Hide file tree
Showing 12 changed files with 505 additions and 258 deletions.
9 changes: 9 additions & 0 deletions app/components/RuleItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { RuleConfigStates, RuleInfo, RuleLevel } from '~~/shared/types'
import { useClipboard } from '@vueuse/core'
import { getRuleLevel, getRuleOptions } from '~~/shared/rules'
import { vTooltip } from 'floating-vue'
import { deepCompareOptions } from '~/composables/options'
import { getRuleDefaultOptions } from '~/composables/payload'
const props = defineProps<{
rule: RuleInfo
Expand All @@ -17,6 +19,11 @@ const emit = defineEmits<{
stateClick: [RuleLevel]
}>()
function redundantOptions(options: any) {
const { hasRedundantOptions } = deepCompareOptions(options ?? [], getRuleDefaultOptions(props.rule.name))
return hasRedundantOptions
}
const { copy } = useClipboard()
function capitalize(str?: string) {
Expand All @@ -38,6 +45,7 @@ function capitalize(str?: string) {
:level="s.level"
:config-index="s.configIndex"
:has-options="!!s.options?.length"
:has-redundant-options="redundantOptions(s.options)"
/>
<template #popper="{ shown }">
<RuleStateItem v-if="shown" :state="s" />
Expand All @@ -53,6 +61,7 @@ function capitalize(str?: string) {
<RuleLevelIcon
:level="getRuleLevel(value)!"
:has-options="!!getRuleOptions(value)?.length"
:has-redundant-options="redundantOptions(getRuleOptions(value))"
/>
</div>

Expand Down
3 changes: 2 additions & 1 deletion app/components/RuleLevelIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { nth } from '~/composables/strings'
const props = defineProps<{
level: RuleLevel
hasOptions?: boolean
hasRedundantOptions?: boolean
configIndex?: number
class?: string
}>()
Expand All @@ -32,6 +33,6 @@ const icon = computed(() => ({
<template>
<div relative :class="[color, props.class]" :title="title">
<div :class="icon" />
<div v-if="hasOptions" absolute right--2px top--2px h-6px w-6px rounded-full bg-current op75 />
<div v-if="hasOptions" absolute right--2px top--2px h-6px w-6px rounded-full bg-current op75 :class="hasRedundantOptions ? 'text-blue5' : ''" />
</div>
</template>
73 changes: 58 additions & 15 deletions app/components/RuleStateItem.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<script setup lang="ts">
import type { RuleConfigState } from '~~/shared/types'
import { useRouter } from '#app/composables/router'
import { computed } from 'vue'
import { payload } from '~/composables/payload'
import { computed, reactive } from 'vue'
import { deepCompareOptions } from '~/composables/options'
import { getRuleDefaultOptions, payload } from '~/composables/payload'
import { filtersConfigs } from '~/composables/state'
import { nth, stringifyUnquoted } from '~/composables/strings'
import { nth, stringifyOptions } from '~/composables/strings'
const props = defineProps<{
state: RuleConfigState
Expand All @@ -19,6 +20,16 @@ const colors = {
const config = computed(() => payload.value.configs[props.state.configIndex])
const defaultOptions = computed(() => getRuleDefaultOptions(props.state.name))
const comparedOptions = computed(() => deepCompareOptions(props.state.options ?? [], defaultOptions.value))
const initialRuleOptionsView = computed(() => !props.state.options?.length && defaultOptions.value?.length ? 'default' : 'state')
const ruleOptions = reactive({
viewType: initialRuleOptionsView.value as 'state' | 'default',
})
const router = useRouter()
function goto() {
filtersConfigs.rule = props.state.name
Expand Down Expand Up @@ -71,20 +82,52 @@ function goto() {
</div>
</template>
</div>
<template v-if="state.options?.length">
<div flex="~ gap-2 items-center">
<div i-ph-sliders-duotone my1 flex-none op75 />
<div op50>
Rule options
<template v-if="state.options?.length || defaultOptions?.length">
<div items-center justify-between md:flex>
<div flex="~ gap-1" op50>
<button
v-if="state.options?.length"
btn-action
:class="{ 'btn-action-active': ruleOptions.viewType === 'state' }"
@click="ruleOptions.viewType = 'state'"
>
<div i-ph-sliders-duotone my1 flex-none op75 />
Rule options
</button>
<button
v-if="defaultOptions?.length"
btn-action
:class="{ 'btn-action-active': ruleOptions.viewType === 'default' }"
@click="ruleOptions.viewType = 'default'"
>
<div i-ph-faders-duotone my1 flex-none op75 />
Option defaults
</button>
</div>
</div>
<Shiki
v-for="options, idx of state.options"
:key="idx"
lang="ts"
:code="stringifyUnquoted(options)"
rounded bg-code p2 text-sm
/>
<template v-if="ruleOptions.viewType === 'state'">
<Shiki
v-for="options, idx of comparedOptions.options"
:key="idx"
lang="ts"
:code="stringifyOptions(options)"
rounded bg-code p2 text-sm
/>
</template>
<template v-if="ruleOptions.viewType === 'default'">
<Shiki
v-for="options, idx of defaultOptions"
:key="idx"
lang="ts"
:code="stringifyOptions(options)"
rounded bg-code p2 text-sm
/>
</template>
</template>
<template v-if="ruleOptions.viewType === 'state' && comparedOptions.hasRedundantOptions">
<div op50>
Options <span italic op75>italicized</span> match the default for the rule
</div>
</template>
</div>
</template>
10 changes: 10 additions & 0 deletions app/components/Shiki.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { transformerNotationMap } from '@shikijs/transformers'

// @unocss-include
export default defineComponent({
name: 'Shiki',
Expand All @@ -24,6 +26,14 @@ export default defineComponent({
node.properties.style = ''
},
},
transformerNotationMap(
{
classMap: {
muted: 'muted',
},
},
'@shikijs/transformers:notation-muted',
),
],
})
})
Expand Down
53 changes: 53 additions & 0 deletions app/composables/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Indicates if any user-supplied option values match the default value for that option
*/
let hasRedundantOptions: boolean

/**
* Wraps an option value in a 3-part array, while still preserving the original data type and value.
*
* The '--' markers provide something that a regex replace can easily later match on.
* (See transformDiff() in ./strings.ts)
*/
function redundantOption(option: any) {
hasRedundantOptions = true
return ['--', option, '--']
}

function deepCompareOption(option: any, defaultOption: any) {
if (defaultOption === void 0)
return option
if (typeof option !== typeof defaultOption)
return option

if (option === defaultOption)
return redundantOption(option)

if (typeof option === 'object' && option !== null && defaultOption !== null) {
if (Array.isArray(option) && Array.isArray(defaultOption) && option.length === defaultOption.length) {
if (option.length === 0)
return redundantOption(option)
return option.map((value: any, index: number): any[] => deepCompareOption(value, defaultOption[index]))
}
else if (!Array.isArray(option) && !Array.isArray(defaultOption)) {
const optionKeys = Object.keys(option)

return optionKeys.reduce((comparedKeys: Record<string, any>, key) => {
comparedKeys[key] = deepCompareOption(option[key], defaultOption[key])
return comparedKeys
}, {})
}
}

return option
}

export function deepCompareOptions(options: any[], defaultOptions: any[]) {
hasRedundantOptions = false
const comparedOptions = options.map((value, index) => deepCompareOption(value, index < defaultOptions.length ? defaultOptions[index] : void 0))

return {
options: comparedOptions,
hasRedundantOptions,
}
}
4 changes: 4 additions & 0 deletions app/composables/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ export function getRuleFromName(name: string): RuleInfo {
}
}

export function getRuleDefaultOptions(name: string): any[] {
return payload.value.rules[name]?.defaultOptions ?? []
}

export function getRuleStates(name: string): RuleConfigStates | undefined {
return payload.value.ruleToState.get(name)
}
Expand Down
15 changes: 15 additions & 0 deletions app/composables/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ export function nth(n: number) {
return `${n}th`
}

export function stringifyOptions(object: any) {
/**
* Replaces all occurrences of the pattern:
* `['--', value, '--']`
*
* with:
* `value, // [!code muted]
*
* Lines with the [!code muted] comment will be processed by Shiki's diff
* notation transformer and have the `.line.muted` classes applied
*/
return stringifyUnquoted(object)
.replace(/\[\s*'--',\s*(\S.+),\s*'--'\s*\],?/g, '$1, // [!code muted]')
}

export function stringifyUnquoted(obj: any) {
return JSON.stringify(obj, null, 2)
.replace(/"(\w+)":/g, '$1:')
Expand Down
5 changes: 5 additions & 0 deletions app/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ html.dark {
font-size: 15px;
}

.shiki span.line.muted {
font-style: italic;
opacity: 75%;
}

.font-mono, [font-mono=""] {
font-variant-ligatures: none;
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@iconify-json/twemoji": "catalog:",
"@iconify-json/vscode-icons": "catalog:",
"@nuxt/eslint": "catalog:",
"@shikijs/transformers": "catalog:",
"@types/connect": "catalog:",
"@types/ws": "catalog:",
"@typescript-eslint/utils": "catalog:",
Expand Down
Loading

0 comments on commit 9820d63

Please sign in to comment.