Hide placeholders (#9246)

* New menuVisible logic

* Hide placeholders unless sole selected node, required argument, or requested by dynamic config
This commit is contained in:
Kaz Wesley 2024-03-05 09:16:38 -05:00 committed by GitHub
parent f02213ae2b
commit b0842feea2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 68 additions and 27 deletions

View File

@ -60,6 +60,10 @@ test('Selection widgets in Data.read node', async ({ page }) => {
// Check initially visible arguments
const node = locate.graphNodeByBinding(page, 'data')
const argumentNames = node.locator('.WidgetArgumentName')
await expect(argumentNames).toHaveCount(1)
// Check arguments after selecting node
await node.click()
await expect(argumentNames).toHaveCount(3)
// Set value on `on_problems` (static drop-down)
@ -129,6 +133,10 @@ test('Managing aggregates in `aggregate` node', async ({ page }) => {
// Check initially visible arguments
const node = locate.graphNodeByBinding(page, 'aggregated')
const argumentNames = node.locator('.WidgetArgumentName')
await expect(argumentNames).toHaveCount(1)
// Check arguments after selecting node
await node.click()
await expect(argumentNames).toHaveCount(3)
// Add first aggregate

View File

@ -30,6 +30,7 @@ export const graphSelection: GraphSelection = {
hoveredNode: undefined,
hoveredPort: undefined,
isSelected: () => false,
isChanging: false,
mouseHandler: () => false,
selectAll: () => {},
selected: new Set(),

View File

@ -88,14 +88,6 @@ const contentNode = ref<HTMLElement>()
const nodeSize = useResizeObserver(rootNode)
const baseNodeSize = computed(() => new Vec2(contentNode.value?.scrollWidth ?? 0, nodeSize.value.y))
/// Menu can be full, partial or off
enum MenuState {
Full,
Partial,
Off,
}
const menuVisible = ref(MenuState.Off)
const error = computed(() => {
const externalId = graph.db.idToExternal(nodeId.value)
if (!externalId) return
@ -122,12 +114,19 @@ const warning = computed(() => {
})
const isSelected = computed(() => nodeSelection?.isSelected(nodeId.value) ?? false)
const isOnlyOneSelected = computed(() => isSelected.value && nodeSelection?.selected.size === 1)
watch(isSelected, (selected) => {
if (!selected) {
menuVisible.value = MenuState.Off
}
const isOnlyOneSelected = computed(
() => isSelected.value && nodeSelection?.selected.size === 1 && !nodeSelection.isChanging,
)
const menuVisible = isOnlyOneSelected
const menuFull = ref(false)
watch(menuVisible, (visible) => {
if (!visible) menuFull.value = false
})
function openFullMenu() {
menuFull.value = true
nodeSelection?.setSelection(new Set([nodeId.value]))
}
const isDocsVisible = ref(false)
const visualizationWidth = computed(() => props.node.vis?.width ?? null)
@ -181,7 +180,6 @@ const dragPointer = usePointer((pos, event, type) => {
) {
nodeSelection?.handleSelectionOf(event, new Set([nodeId.value]))
handleNodeClick(event)
menuVisible.value = MenuState.Partial
}
startEvent = null
startEpochMs.value = 0
@ -363,13 +361,6 @@ function portGroupStyle(port: PortData) {
}
}
function openFullMenu() {
if (!nodeSelection?.isSelected(nodeId.value)) {
nodeSelection?.setSelection(new Set([nodeId.value]))
}
menuVisible.value = MenuState.Full
}
const editingComment = ref(false)
const documentation = computed<string | undefined>({
@ -419,12 +410,12 @@ const documentation = computed<string | undefined>({
{{ node.pattern?.code() ?? '' }}
</div>
<CircularMenu
v-if="menuVisible === MenuState.Full || menuVisible === MenuState.Partial"
v-if="menuVisible"
v-model:isOutputContextOverridden="isOutputContextOverridden"
v-model:isDocsVisible="isDocsVisible"
:isOutputContextEnabledGlobally="projectStore.isOutputContextEnabled"
:isVisualizationVisible="isVisualizationVisible"
:isFullMenuVisible="menuVisible === MenuState.Full"
:isFullMenuVisible="menuVisible && menuFull"
@update:isVisualizationVisible="emit('update:visualizationVisible', $event)"
@startEditing="startEditingNode"
@startEditingComment="editingComment = true"
@ -436,7 +427,7 @@ const documentation = computed<string | undefined>({
:nodeSize="baseNodeSize"
:scale="navigator?.scale ?? 1"
:nodePosition="props.node.position"
:isCircularMenuVisible="menuVisible === MenuState.Full || menuVisible === MenuState.Partial"
:isCircularMenuVisible="menuVisible"
:currentType="node.vis?.identifier"
:isFullscreen="isVisualizationFullscreen"
:dataSource="{ type: 'node', nodeId: externalId }"
@ -474,6 +465,7 @@ const documentation = computed<string | undefined>({
:icon="icon"
:connectedSelfArgumentId="connectedSelfArgumentId"
:potentialSelfArgumentId="potentialSelfArgumentId"
:extended="isOnlyOneSelected"
@openFullMenu="openFullMenu"
/>
</div>
@ -484,7 +476,7 @@ const documentation = computed<string | undefined>({
<GraphNodeError
v-if="warning && (nodeHovered || isSelected)"
class="afterNode warning"
:class="{ messageWithMenu: menuVisible !== MenuState.Off }"
:class="{ messageWithMenu: menuVisible }"
:message="warning"
icon="warning"
type="warning"

View File

@ -15,6 +15,7 @@ const props = defineProps<{
icon: Icon
connectedSelfArgumentId: Ast.AstId | undefined
potentialSelfArgumentId: Ast.AstId | undefined
extended: boolean
}>()
const emit = defineEmits<{
openFullMenu: []
@ -69,6 +70,7 @@ provideWidgetTree(
toRef(props, 'icon'),
toRef(props, 'connectedSelfArgumentId'),
toRef(props, 'potentialSelfArgumentId'),
toRef(props, 'extended'),
layoutTransitions.active,
() => {
emit('openFullMenu')

View File

@ -1,11 +1,13 @@
<script setup lang="ts">
import NodeWidget from '@/components/GraphEditor/NodeWidget.vue'
import { WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry'
import { injectWidgetTree } from '@/providers/widgetTree'
import { Ast } from '@/util/ast'
import { ArgumentApplication, ArgumentApplicationKey } from '@/util/callTree'
import { computed } from 'vue'
const props = defineProps(widgetProps(widgetDefinition))
const tree = injectWidgetTree()
const application = computed(() => props.input[ArgumentApplicationKey])
const targetMaybePort = computed(() => {
const target = application.value.target
@ -45,7 +47,13 @@ export const widgetDefinition = defineWidget(ArgumentApplicationKey, {
<div v-if="application.infixOperator" class="infixOp" :style="operatorStyle">
<NodeWidget :input="WidgetInput.FromAst(application.infixOperator)" />
</div>
<NodeWidget :input="application.argument.toWidgetInput()" nest />
<div
v-if="tree.extended || !application.argument.hideByDefault"
class="argument"
:class="{ animateWhenShown: application.argument.hideByDefault }"
>
<NodeWidget :input="application.argument.toWidgetInput()" nest />
</div>
</span>
</template>
@ -75,4 +83,22 @@ export const widgetDefinition = defineWidget(ArgumentApplicationKey, {
white-space: pre;
}
}
.argument {
display: flex;
flex-direction: row;
place-items: center;
}
.animateWhenShown {
animation: show 4800ms 100ms cubic-bezier(0.38, 0.97, 0.56, 0.76) forwards;
max-width: 0;
overflow-x: clip;
}
@keyframes show {
100% {
max-width: 2000px;
}
}
</style>

View File

@ -148,6 +148,7 @@ export function useSelection<T>(
},
deselectAll: () => selected.clear(),
isSelected: (element: T) => selected.has(element),
isChanging: computed(() => anchor.value != null),
setSelection,
handleSelectionOf,
hoveredNode,

View File

@ -14,6 +14,7 @@ const { provideFn, injectFn } = createContextStore(
icon: Ref<Icon>,
connectedSelfArgumentId: Ref<Ast.AstId | undefined>,
potentialSelfArgumentId: Ref<Ast.AstId | undefined>,
extended: Ref<boolean>,
hasActiveAnimations: Ref<boolean>,
emitOpenFullMenu: () => void,
) => {
@ -25,6 +26,7 @@ const { provideFn, injectFn } = createContextStore(
icon,
connectedSelfArgumentId,
potentialSelfArgumentId,
extended,
nodeSpanStart,
hasActiveAnimations,
emitOpenFullMenu,

View File

@ -2,6 +2,7 @@ import type { PortId } from '@/providers/portInfo'
import { WidgetInput } from '@/providers/widgetRegistry'
import type { WidgetConfiguration } from '@/providers/widgetRegistry/configuration'
import * as widgetCfg from '@/providers/widgetRegistry/configuration'
import { DisplayMode } from '@/providers/widgetRegistry/configuration'
import type { SuggestionEntry, SuggestionEntryArgument } from '@/stores/suggestionDatabase/entry'
import { Ast } from '@/util/ast'
import { findLastIndex, tryGetIndex } from '@/util/data/array'
@ -23,7 +24,7 @@ export class ArgumentPlaceholder {
public argInfo: SuggestionEntryArgument,
public kind: ApplicationKind,
public insertAsNamed: boolean,
public dynamicConfig?: WidgetConfiguration | undefined,
public dynamicConfig?: (WidgetConfiguration & { display?: DisplayMode }) | undefined,
) {}
static WithRetrievedConfig(
@ -52,6 +53,10 @@ export class ArgumentPlaceholder {
get portId(): PortId {
return `${this.callId}[${this.index}]` as PortId
}
get hideByDefault(): boolean {
return this.argInfo.hasDefault && this.dynamicConfig?.display !== DisplayMode.Always
}
}
export class ArgumentAst {
@ -88,6 +93,10 @@ export class ArgumentAst {
get portId(): PortId {
return this.ast.id
}
get hideByDefault(): boolean {
return false
}
}
type InterpretedCall = InterpretedInfix | InterpretedPrefix