Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
salazarm committed Dec 11, 2024
1 parent 54b39a2 commit 582b015
Show file tree
Hide file tree
Showing 4 changed files with 354 additions and 219 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import {Colors, Icon, Icons} from '@dagster-io/ui-components';
import CodeMirror, {Editor, HintFunction} from 'codemirror';
import {useLayoutEffect, useMemo, useRef} from 'react';
import styled, {createGlobalStyle, css} from 'styled-components';
import {Icons} from '@dagster-io/ui-components';
import {useMemo} from 'react';
import styled from 'styled-components';

import {lintAssetSelection} from './AssetSelectionLinter';
import {assertUnreachable} from '../../app/Util';
import {AssetGraphQueryItem} from '../../asset-graph/useAssetGraphData';
import {useUpdatingRef} from '../../hooks/useUpdatingRef';
import {createSelectionHint} from '../../selection/SelectionAutoComplete';
import {
SelectionAutoCompleteInputCSS,
applyStaticSyntaxHighlighting,
} from '../../selection/SelectionAutoCompleteHighlighter';
import {SelectionAutoCompleteInput, iconStyle} from '../../selection/SelectionAutoCompleteInput';
import {placeholderTextForItems} from '../../ui/GraphQueryInput';
import {buildRepoPathForHuman} from '../../workspace/buildRepoAddress';

Expand All @@ -32,215 +26,81 @@ interface AssetSelectionInputProps {
const FUNCTIONS = ['sinks', 'roots'];

export const AssetSelectionInput = ({value, onChange, assets}: AssetSelectionInputProps) => {
const editorRef = useRef<HTMLDivElement>(null);
const cmInstance = useRef<CodeMirror.Editor | null>(null);

const currentValueRef = useRef(value);

const hintRef = useUpdatingRef(
useMemo(() => {
const assetNamesSet: Set<string> = new Set();
const tagNamesSet: Set<string> = new Set();
const ownersSet: Set<string> = new Set();
const groupsSet: Set<string> = new Set();
const kindsSet: Set<string> = new Set();
const codeLocationSet: Set<string> = new Set();

assets.forEach((asset) => {
assetNamesSet.add(asset.name);
asset.node.tags.forEach((tag) => {
if (tag.key && tag.value) {
tagNamesSet.add(`${tag.key}=${tag.value}`);
} else {
tagNamesSet.add(tag.key);
}
});
asset.node.owners.forEach((owner) => {
switch (owner.__typename) {
case 'TeamAssetOwner':
ownersSet.add(owner.team);
break;
case 'UserAssetOwner':
ownersSet.add(owner.email);
break;
default:
assertUnreachable(owner);
}
});
if (asset.node.groupName) {
groupsSet.add(asset.node.groupName);
}
asset.node.kinds.forEach((kind) => {
kindsSet.add(kind);
});
const location = buildRepoPathForHuman(
asset.node.repository.name,
asset.node.repository.location.name,
);
codeLocationSet.add(location);
});

const assetNames = Array.from(assetNamesSet);
const tagNames = Array.from(tagNamesSet);
const owners = Array.from(ownersSet);
const groups = Array.from(groupsSet);
const kinds = Array.from(kindsSet);
const codeLocations = Array.from(codeLocationSet);

return createSelectionHint(
'key',
{
key: assetNames,
tag: tagNames,
owner: owners,
group: groups,
kind: kinds,
code_location: codeLocations,
},
FUNCTIONS,
);
}, [assets]),
);

useLayoutEffect(() => {
if (editorRef.current && !cmInstance.current) {
cmInstance.current = CodeMirror(editorRef.current, {
value,
mode: 'assetSelection',
lineNumbers: false,
lineWrapping: false,
scrollbarStyle: 'native',
autoCloseBrackets: true,
lint: {
getAnnotations: lintAssetSelection,
async: false,
},
placeholder: placeholderTextForItems('Type an asset subset…', assets),
extraKeys: {
'Ctrl-Space': 'autocomplete',
Tab: (cm: Editor) => {
cm.replaceSelection(' ', 'end');
},
},
});

cmInstance.current.setSize('100%', 20);

// Enforce single line by preventing newlines
cmInstance.current.on('beforeChange', (_instance: Editor, change) => {
if (change.text.some((line) => line.includes('\n'))) {
change.cancel();
const attributesMap = useMemo(() => {
const assetNamesSet: Set<string> = new Set();
const tagNamesSet: Set<string> = new Set();
const ownersSet: Set<string> = new Set();
const groupsSet: Set<string> = new Set();
const kindsSet: Set<string> = new Set();
const codeLocationSet: Set<string> = new Set();

assets.forEach((asset) => {
assetNamesSet.add(asset.name);
asset.node.tags.forEach((tag) => {
if (tag.key && tag.value) {
tagNamesSet.add(`${tag.key}=${tag.value}`);
} else {
tagNamesSet.add(tag.key);
}
});

cmInstance.current.on('change', (instance: Editor, change) => {
const newValue = instance.getValue().replace(/\s+/g, ' ');
currentValueRef.current = newValue;
onChange(newValue);

if (change.origin === 'complete' && change.text[0]?.endsWith('()')) {
// Set cursor inside the right parenthesis
const cursor = instance.getCursor();
instance.setCursor({...cursor, ch: cursor.ch - 1});
asset.node.owners.forEach((owner) => {
switch (owner.__typename) {
case 'TeamAssetOwner':
ownersSet.add(owner.team);
break;
case 'UserAssetOwner':
ownersSet.add(owner.email);
break;
default:
assertUnreachable(owner);
}
});

cmInstance.current.on('inputRead', (instance: Editor) => {
showHint(instance, hintRef.current);
});

cmInstance.current.on('cursorActivity', (instance: Editor) => {
applyStaticSyntaxHighlighting(instance);
showHint(instance, hintRef.current);
});

requestAnimationFrame(() => {
if (!cmInstance.current) {
return;
}

applyStaticSyntaxHighlighting(cmInstance.current);
if (asset.node.groupName) {
groupsSet.add(asset.node.groupName);
}
asset.node.kinds.forEach((kind) => {
kindsSet.add(kind);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// Update CodeMirror when value prop changes
useLayoutEffect(() => {
const noNewLineValue = value.replace('\n', ' ');
if (cmInstance.current && cmInstance.current.getValue() !== noNewLineValue) {
const instance = cmInstance.current;
const cursor = instance.getCursor();
instance.setValue(noNewLineValue);
instance.setCursor(cursor);
showHint(instance, hintRef.current);
}
}, [hintRef, value]);
const location = buildRepoPathForHuman(
asset.node.repository.name,
asset.node.repository.location.name,
);
codeLocationSet.add(location);
});

const assetNames = Array.from(assetNamesSet);
const tagNames = Array.from(tagNamesSet);
const owners = Array.from(ownersSet);
const groups = Array.from(groupsSet);
const kinds = Array.from(kindsSet);
const codeLocations = Array.from(codeLocationSet);

return {
key: assetNames,
tag: tagNames,
owner: owners,
group: groups,
kind: kinds,
code_location: codeLocations,
};
}, [assets]);
return (
<>
<GlobalHintStyles />
<InputDiv
style={{
display: 'grid',
gridTemplateColumns: 'auto minmax(0, 1fr) auto',
alignItems: 'center',
}}
>
<Icon name="op_selector" />
<div ref={editorRef} />
<Icon name="info" />
</InputDiv>
</>
<WrapperDiv>
<SelectionAutoCompleteInput
nameBase="key"
attributesMap={attributesMap}
placeholder={placeholderTextForItems('Type an asset subset…', assets)}
functions={FUNCTIONS}
linter={lintAssetSelection}
value={value}
onChange={onChange}
/>
</WrapperDiv>
);
};
const iconStyle = (img: string) => css`
&:before {
content: ' ';
width: 14px;
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
mask-image: url(${img});
background: ${Colors.accentPrimary()};
display: inline-block;
}
`;

const InputDiv = styled.div`
${SelectionAutoCompleteInputCSS}

const WrapperDiv = styled.div`
.attribute-owner {
${iconStyle(Icons.owner.src)}
}
`;

const GlobalHintStyles = createGlobalStyle`
.CodeMirror-hints {
background: ${Colors.popoverBackground()};
border: none;
border-radius: 4px;
padding: 8px 4px;
.CodeMirror-hint {
border-radius: 4px;
font-size: 14px;
padding: 6px 8px 6px 12px;
color: ${Colors.textDefault()};
&.CodeMirror-hint-active {
background-color: ${Colors.backgroundBlue()};
color: ${Colors.textDefault()};
}
}
}
`;

function showHint(instance: Editor, hint: HintFunction) {
requestAnimationFrame(() => {
instance.showHint({
hint,
completeSingle: false,
moveOnOverlap: true,
updateOnCursorActivity: true,
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type TextCallback = (value: string) => string;
const DEFAULT_TEXT_CALLBACK = (value: string) => value;

// set to true for useful debug output.
const DEBUG = true;
const DEBUG = false;

export class SelectionAutoCompleteVisitor
extends AbstractParseTreeVisitor<void>
Expand Down Expand Up @@ -507,11 +507,15 @@ export class SelectionAutoCompleteVisitor
}
}

export function createSelectionHint<T extends Record<string, string[]>, N extends keyof T>(
_nameBase: N,
attributesMap: T,
functions: string[],
): CodeMirror.HintFunction {
export function createSelectionHint<T extends Record<string, string[]>, N extends keyof T>({
nameBase: _nameBase,
attributesMap,
functions,
}: {
nameBase: N;
attributesMap: T;
functions: string[];
}): CodeMirror.HintFunction {
const nameBase = _nameBase as string;

return function (cm: CodeMirror.Editor, _options: CodeMirror.ShowHintOptions): any {
Expand Down
Loading

0 comments on commit 582b015

Please sign in to comment.