Skip to content

Commit

Permalink
Small boolean widget tweaks (#8994)
Browse files Browse the repository at this point in the history
Fixes #8962
  • Loading branch information
farmaazon committed Feb 12, 2024
1 parent e1943bd commit a0029f2
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 38 deletions.
35 changes: 25 additions & 10 deletions app/gui2/src/components/GraphEditor/widgets/WidgetCheckbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import CheckboxWidget from '@/components/widgets/CheckboxWidget.vue'
import { Score, WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry'
import { useGraphStore } from '@/stores/graph'
import { requiredImportsByFQN } from '@/stores/graph/imports'
import { SuggestionDb, useSuggestionDbStore } from '@/stores/suggestionDatabase'
import { assert } from '@/util/assert'
import { Ast } from '@/util/ast'
import type { TokenId } from '@/util/ast/abstract.ts'
Expand All @@ -11,31 +13,42 @@ import { computed } from 'vue'
const props = defineProps(widgetProps(widgetDefinition))
const graph = useGraphStore()
const suggestionDb = useSuggestionDbStore()
const trueImport = computed(() =>
requiredImportsByFQN(
suggestionDb.entries,
'Standard.Base.Data.Boolean.Boolean.True' as QualifiedName,
true,
),
)
const falseImport = computed(() =>
requiredImportsByFQN(
suggestionDb.entries,
'Standard.Base.Data.Boolean.Boolean.False' as QualifiedName,
true,
),
)
const value = computed({
get() {
return WidgetInput.valueRepr(props.input)?.endsWith('True') ?? false
},
set(value) {
const edit = graph.startEdit()
const theImport = value ? trueImport.value : falseImport.value
if (props.input.value instanceof Ast.Ast) {
setBoolNode(
const { requiresImport } = setBoolNode(
edit.getVersion(props.input.value),
value ? ('True' as Identifier) : ('False' as Identifier),
)
if (requiresImport) graph.addMissingImports(edit, theImport)
props.onUpdate({ edit })
} else {
graph.addMissingImports(edit, [
{
kind: 'Unqualified',
from: 'Standard.Base.Data.Boolean' as QualifiedName,
import: 'Boolean' as Identifier,
},
])
graph.addMissingImports(edit, theImport)
props.onUpdate({
edit,
portUpdate: {
value: value ? 'Boolean.True' : 'Boolean.False',
value: value ? 'True' : 'False',
origin: asNot<TokenId>(props.input.portId),
},
})
Expand All @@ -54,12 +67,14 @@ function isBoolNode(ast: Ast.Ast) {
: undefined
return candidate && ['True', 'False'].includes(candidate.code())
}
function setBoolNode(ast: Ast.Mutable, value: Identifier) {
function setBoolNode(ast: Ast.Mutable, value: Identifier): { requiresImport: boolean } {
if (ast instanceof Ast.MutablePropertyAccess) {
ast.setRhs(value)
return { requiresImport: false }
} else {
assert(ast instanceof Ast.MutableIdent)
ast.setToken(value)
return { requiresImport: true }
}
}
Expand Down
21 changes: 19 additions & 2 deletions app/gui2/src/components/GraphEditor/widgets/WidgetSelection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ import {
import { useGraphStore } from '@/stores/graph'
import { requiredImports, type RequiredImport } from '@/stores/graph/imports.ts'
import { useSuggestionDbStore } from '@/stores/suggestionDatabase'
import { type SuggestionEntry } from '@/stores/suggestionDatabase/entry.ts'
import {
type SuggestionEntry,
type SuggestionEntryArgument,
} from '@/stores/suggestionDatabase/entry.ts'
import { Ast } from '@/util/ast'
import type { TokenId } from '@/util/ast/abstract.ts'
import { ArgumentInfoKey } from '@/util/callTree'
import { arrayEquals } from '@/util/data/array'
import { asNot } from '@/util/data/types.ts'
import {
qnLastSegment,
Expand Down Expand Up @@ -143,11 +147,24 @@ watch(selectedIndex, (_index) => {
</script>

<script lang="ts">
function hasBooleanTagValues(parameter: SuggestionEntryArgument): boolean {
if (parameter.tagValues == null) return false
return arrayEquals(Array.from(parameter.tagValues).sort(), [
'Standard.Base.Data.Boolean.Boolean.False',
'Standard.Base.Data.Boolean.Boolean.True',
])
}
export const widgetDefinition = defineWidget(WidgetInput.isAstOrPlaceholder, {
priority: 50,
score: (props) => {
if (props.input.dynamicConfig?.kind === 'Single_Choice') return Score.Perfect
if (props.input[ArgumentInfoKey]?.info?.tagValues != null) return Score.Perfect
// Boolean arguments also have tag values, but the checkbox widget should handle them.
if (
props.input[ArgumentInfoKey]?.info?.tagValues != null &&
!hasBooleanTagValues(props.input[ArgumentInfoKey].info)
)
return Score.Perfect
return Score.Mismatch
},
})
Expand Down
58 changes: 36 additions & 22 deletions app/gui2/src/stores/graph/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,37 +221,41 @@ function requiredImportToAst(value: RequiredImport, module?: MutableModule) {
}

/** A list of required imports for specific suggestion entry */
export function requiredImports(db: SuggestionDb, entry: SuggestionEntry): RequiredImport[] {
export function requiredImports(
db: SuggestionDb,
entry: SuggestionEntry,
directConImport: boolean = false,
): RequiredImport[] {
const unqualifiedImport = (from: QualifiedName): UnqualifiedImport[] => [
{
kind: 'Unqualified',
from,
import: entry.name,
},
]
switch (entry.kind) {
case SuggestionKind.Module:
return entry.reexportedIn
? [
{
kind: 'Unqualified',
from: entry.reexportedIn,
import: entry.name,
},
]
? unqualifiedImport(entry.reexportedIn)
: [
{
kind: 'Qualified',
module: entry.definedIn,
},
]
case SuggestionKind.Type: {
const from = entry.reexportedIn ? entry.reexportedIn : entry.definedIn
return [
{
kind: 'Unqualified',
from,
import: entry.name,
},
]
}
case SuggestionKind.Constructor: {
const selfType = selfTypeEntry(db, entry)
return selfType ? requiredImports(db, selfType) : []
}
case SuggestionKind.Type:
return unqualifiedImport(entry.reexportedIn ? entry.reexportedIn : entry.definedIn)
case SuggestionKind.Constructor:
if (directConImport) {
return entry.reexportedIn
? unqualifiedImport(entry.reexportedIn)
: entry.memberOf
? unqualifiedImport(entry.memberOf)
: []
} else {
const selfType = selfTypeEntry(db, entry)
return selfType ? requiredImports(db, selfType) : []
}
case SuggestionKind.Method: {
const isStatic = entry.selfType == null
const selfType = selfTypeEntry(db, entry)
Expand All @@ -272,6 +276,16 @@ export function requiredImports(db: SuggestionDb, entry: SuggestionEntry): Requi
}
}

export function requiredImportsByFQN(
db: SuggestionDb,
fqn: QualifiedName,
directConImport: boolean = false,
) {
const entry = db.getEntryByQualifiedName(fqn)
if (!entry) return []
return requiredImports(db, entry, directConImport)
}

function selfTypeEntry(db: SuggestionDb, entry: SuggestionEntry): SuggestionEntry | undefined {
if (entry.memberOf) {
return db.getEntryByQualifiedName(entry.memberOf)
Expand Down
26 changes: 26 additions & 0 deletions app/gui2/src/util/equals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export function defaultEquality(a: unknown, b: unknown): boolean {
return a === b
}

/**
* Element-wise equality check of arrays.
*
* @param a left array
* @param b right array
* @param eq equality function for elements. When not specified, `===` operator is used.
* @returns true if arrays are equal.
*/
export function arrayEquals<T>(
a: Array<T>,
b: Array<T>,
eq: (a: T, b: T) => boolean = defaultEquality,
) {
if (a.length !== b.length) return false
for (let i = 0; i < a.length; ++i) {
const aVal = a[i]
const bVal = b[i]
if ((aVal == undefined) != (bVal == undefined)) return false
if (aVal != undefined && bVal != undefined && !eq(aVal, bVal)) return false
}
return true
}
5 changes: 1 addition & 4 deletions app/gui2/src/util/reactivity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @file Functions for manipulating Vue reactive objects. */

import { defaultEquality } from '@/util/equals'
import { nop } from 'lib0/function'
import {
callWithErrorHandling,
Expand Down Expand Up @@ -99,10 +100,6 @@ export class LazySyncEffectSet {
}
}

function defaultEquality(a: unknown, b: unknown): boolean {
return a === b
}

/**
* Create a ref that is updated whenever the given function's return value changes. Similar to
* `computed`, but differs in significant ways:
Expand Down

0 comments on commit a0029f2

Please sign in to comment.