Skip to content

Commit d0c849c

Browse files
committed
fix a performance bug on concept shelf
1 parent 075d2b3 commit d0c849c

16 files changed

+155
-397
lines changed

src/components/ComponentType.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ export interface FieldItem {
2020
type: Type;
2121
source: FieldSource;
2222
domain: any[];
23+
tableRef: string; // which table it belongs to, it matters when it's an original field or a derived field
24+
2325
transform?: ConceptTransformation;
24-
tableRef?: string; // which table it comes from, it matters when it's an original field
25-
temporary?: true;
26+
temporary?: true; // the field is temporary, and it will be deleted unless it's saved
2627
levels?: {values: any[], reason: string}; // the order in which values in this field would be sorted
2728
semanticType?: string; // the semantic type of the object, inferred by the model
2829
}
@@ -69,13 +70,15 @@ export interface DictTable {
6970
// source specifies how the deriviation is done from the source tables, they may be the same, but not necessarily
7071
// in fact, right now dict tables are all triggered from charts
7172
trigger: Trigger,
72-
}
73+
};
74+
anchored: boolean; // whether this table is anchored as a persistent table used to derive other tables
7375
}
7476

7577
export function createDictTable(
7678
id: string, rows: any[],
7779
derive: {code: string, codeExpl: string, source: string[], dialog: any[],
78-
trigger: Trigger} | undefined = undefined) : DictTable {
80+
trigger: Trigger} | undefined = undefined,
81+
anchored: boolean = false) : DictTable {
7982

8083
let names = Object.keys(rows[0])
8184

@@ -85,6 +88,7 @@ export function createDictTable(
8588
rows,
8689
types: names.map(name => inferTypeFromValueArray(rows.map(r => r[name]))),
8790
derive,
91+
anchored
8892
}
8993
}
9094

src/data/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const createTableFromText = (title: string, text: string): DictTable | un
5555
return createTableFromFromObjectArray(title, values);
5656
};
5757

58-
export const createTableFromFromObjectArray = (title: string, values: any[], derive?: any): DictTable => {
58+
export const createTableFromFromObjectArray = (title: string, values: any[], derive?: any, anchored?: boolean): DictTable => {
5959
const len = values.length;
6060
let names: string[] = [];
6161
let cleanNames: string[] = [];
@@ -98,7 +98,8 @@ export const createTableFromFromObjectArray = (title: string, values: any[], der
9898
names: columnTable.names(),
9999
types: columnTable.names().map(name => (columnTable.column(name) as Column).type),
100100
rows: columnTable.objects(),
101-
derive: derive
101+
derive: derive,
102+
anchored: false
102103
}
103104
};
104105

src/scss/App.scss

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,15 @@ h2.view-title {
9191

9292
.Resizer.disabled:hover {
9393
border-color: transparent;
94-
}
94+
}
95+
96+
.GroupHeader {
97+
position: sticky;
98+
padding: 4px 8px;
99+
color: rgba(0, 0, 0, 0.6);
100+
font-size: 12px;
101+
}
102+
103+
.GroupItems {
104+
padding: 0;
105+
}

src/views/ConceptCard.tsx

Lines changed: 12 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -63,28 +63,16 @@ export interface ConceptCardProps {
6363
field: FieldItem,
6464
}
6565

66-
export const GroupHeader = styled('div')(({ theme }) => ({
67-
position: 'sticky',
68-
marginTop: '-8px',
69-
padding: '4px 4px',
70-
color: "rgba(0, 0, 0, 0.6)",
71-
fontSize: "12px",
72-
}));
73-
74-
export const GroupItems = styled('ul')({
75-
padding: 0,
76-
});
77-
7866
const checkConceptIsEmpty = (field: FieldItem) => {
7967
return field.name == "" &&
8068
((field.source == "derived" && !field.transform?.description && (field.transform as ConceptTransformation).code == "")
8169
|| (field.source == "custom"))
8270
}
8371

84-
export const genFreshDerivedConcept = (parentIDs: string[]) => {
72+
export const genFreshDerivedConcept = (parentIDs: string[], tableRef: string) => {
8573
return {
8674
id: `concept-${Date.now()}`, name: "", type: "string" as Type,
87-
source: "derived", domain:[],
75+
source: "derived", domain:[], tableRef: tableRef,
8876
transform: { parentIDs: parentIDs, code: "", description: ""}
8977
} as FieldItem
9078
}
@@ -130,17 +118,16 @@ export const ConceptCard: FC<ConceptCardProps> = function ConceptCard({ field })
130118
let border = "hidden";
131119

132120
const cursorStyle = isDragging ? "grabbing" : "grab";
133-
let editOption = field.source === "original" ? undefined : (
121+
let editOption = field.source == "derived" && (
134122
<Tooltip key="edit-icon-button" title="edit">
135123
<IconButton size="small" key="edit-icon-button"
136124
color="primary" aria-label="Edit" component="span"
137125
onClick={() => { setEditMode(!editMode) }}>
138126
<EditIcon fontSize="inherit" />
139127
</IconButton>
140-
</Tooltip>
141-
);
128+
</Tooltip>);
142129

143-
let deriveOption = (
130+
let deriveOption = (field.source == "derived" || field.source == "original") && (
144131
<Tooltip key="derive-icon-button" title="derive new concept">
145132
<IconButton size="small"
146133
key="derive-icon-button"
@@ -149,15 +136,14 @@ export const ConceptCard: FC<ConceptCardProps> = function ConceptCard({ field })
149136
&& f.transform?.parentIDs.includes(field.id)).length > 0) {
150137
return
151138
}
152-
handleUpdateConcept(genFreshDerivedConcept([field.id]));
139+
handleUpdateConcept(genFreshDerivedConcept([field.id], field.tableRef));
153140
}} >
154141
<ForkRightIcon fontSize="inherit" sx={{ transform: "rotate(90deg)" }} />
155142
</IconButton>
156143
</Tooltip>
157144
);
158145

159-
let deleteOption = field.source == "original" ? "" :
160-
<IconButton size="small"
146+
let deleteOption = !(field.source == "original") && <IconButton size="small"
161147
key="delete-icon-button"
162148
color="primary" aria-label="Delete" component="span"
163149
disabled={conceptShelfItems.filter(f => f.source == "derived" && f.transform?.parentIDs.includes(field.id)).length > 0}
@@ -169,16 +155,13 @@ export const ConceptCard: FC<ConceptCardProps> = function ConceptCard({ field })
169155
deleteOption,
170156
deriveOption,
171157
editOption,
172-
//deleteOption
173158
]
174159

175-
const editModeCard = (
160+
const editModeCard = field.source == "derived" && (
176161
<CardContent className="draggable-card-body-edit-mode">
177-
{field.source == "derived" ? <DerivedConceptForm concept={field} handleUpdateConcept={handleUpdateConcept}
178-
handleDeleteConcept={handleDeleteConcept}
179-
turnOffEditMode={() => { setEditMode(false); }} /> : field.source == "custom" ? <CustomConceptForm concept={field} handleUpdateConcept={handleUpdateConcept}
162+
<DerivedConceptForm concept={field} handleUpdateConcept={handleUpdateConcept}
180163
handleDeleteConcept={handleDeleteConcept}
181-
turnOffEditMode={() => { setEditMode(false); }} /> : ""}
164+
turnOffEditMode={() => { setEditMode(false); }} />
182165
</CardContent>
183166
);
184167

@@ -335,128 +318,6 @@ export const CodeEditor: FC<{ code: string; handleSaveCode: (code: string) => vo
335318
</Box>
336319
}
337320

338-
export const CustomConceptForm: FC<ConceptFormProps> = function CustomConceptForm({ concept, handleUpdateConcept, handleDeleteConcept, turnOffEditMode }) {
339-
340-
const conceptShelfItems = useSelector((state: DataFormulatorState) => state.conceptShelfItems);
341-
342-
const [name, setName] = useState(concept.name);
343-
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { setName(event.target.value); };
344-
345-
const [dtype, setDtype] = useState(concept.name == "" ? "auto" : concept.type as string);
346-
const handleDtypeChange = (event: SelectChangeEvent) => { setDtype(event.target.value); };
347-
348-
// if these two fields are changed from other places, update their values
349-
useEffect(() => { setDtype(concept.type) }, [concept.type]);
350-
351-
let typeList = TypeList
352-
let nameField = (
353-
<TextField key="name-field" id="name" label="concept name" value={name} sx={{ minWidth: 120, maxWidth: 160, flex: 1 }}
354-
FormHelperTextProps={{
355-
style: { fontSize: 8, marginTop: 0, marginLeft: "auto" }
356-
}}
357-
multiline
358-
helperText={conceptShelfItems.some(f => f.name == name && f.id != concept.id) ? "this name already exists" : ""}
359-
size="small" onChange={handleNameChange} required error={name == "" || conceptShelfItems.some(f => f.name == name && f.id != concept.id)}
360-
/>)
361-
362-
let typeField = (
363-
<FormControl key="type-select" sx={{ width: 100, marginLeft: "4px" }} size="small">
364-
<InputLabel id="dtype-select-label">data type</InputLabel>
365-
<Select
366-
labelId="dtype-select-label"
367-
id="dtype-select"
368-
value={dtype}
369-
label="data type"
370-
onChange={handleDtypeChange}>
371-
{typeList.map((t, i) => (
372-
<MenuItem value={t} key={`${concept.id}-${i}`}>
373-
<Typography component="span" sx={{ fontSize: "inherit", marginLeft: "0px" }}>{t}</Typography>
374-
</MenuItem>
375-
))}
376-
</Select>
377-
</FormControl>
378-
)
379-
380-
let cardTopComponents = undefined;
381-
382-
let childrenConceptIDs = [concept.id];
383-
while (true) {
384-
let newChildrens = conceptShelfItems.filter(f => f.source == "derived"
385-
&& !childrenConceptIDs.includes(f.id)
386-
&& f.transform?.parentIDs.some(pid => childrenConceptIDs.includes(pid)))
387-
.map(f => f.id);
388-
if (newChildrens.length == 0) {
389-
break
390-
}
391-
childrenConceptIDs = [...childrenConceptIDs, ...newChildrens];
392-
}
393-
394-
cardTopComponents = [
395-
nameField,
396-
typeField,
397-
]
398-
399-
const checkCustomConceptDiff = () => {
400-
let nameTypeNeq = (concept.name != name || concept.type != dtype);
401-
return (nameTypeNeq );
402-
}
403-
404-
let saveDisabledMsg = [];
405-
if (name == "" || conceptShelfItems.some(f => f.name == name && f.id != concept.id)) {
406-
saveDisabledMsg.push("concept name is empty")
407-
}
408-
409-
return (
410-
<Box sx={{ display: "flex", flexDirection: "column" }} >
411-
<Box component="form" className="concept-form"
412-
sx={{ display: "flex", flexWrap: "wrap", '& > :not(style)': { margin: "4px", /*width: '25ch'*/ }, }}
413-
noValidate
414-
autoComplete="off">
415-
<Box sx={{ overflowX: "clip", display: "flex", flexDirection: "row", justifyContent: "flex-start", alignItems: "baseline" }}>
416-
{cardTopComponents}
417-
</Box>
418-
<ButtonGroup size="small" sx={{ "& button": { textTransform: "none", padding: "2px 4px", marginLeft: "4px" }, flexGrow: 1, justifyContent: "right" }}>
419-
<IconButton size="small"
420-
color="primary" aria-label="Delete" component="span"
421-
disabled={conceptShelfItems.filter(f => f.source == "derived" && f.transform?.parentIDs.includes(concept.id)).length > 0}
422-
onClick={() => { handleDeleteConcept(concept.id); }}>
423-
<Tooltip title="delete">
424-
<DeleteIcon fontSize="inherit" />
425-
</Tooltip>
426-
</IconButton>
427-
<Button size="small" variant="outlined" onClick={() => {
428-
setName(concept.name);
429-
setDtype(concept.type);
430-
431-
if (checkConceptIsEmpty(concept)) {
432-
handleDeleteConcept(concept.id);
433-
}
434-
if (turnOffEditMode) {
435-
turnOffEditMode();
436-
}
437-
}}>
438-
Cancel
439-
</Button>
440-
<Button size="small" variant={checkCustomConceptDiff() ? "contained" : "outlined"} disabled={saveDisabledMsg.length > 0 || checkCustomConceptDiff() == false} onClick={() => {
441-
442-
let tmpConcept = duplicateField(concept);
443-
tmpConcept.name = name;
444-
tmpConcept.type = dtype as Type;
445-
446-
if (turnOffEditMode) {
447-
turnOffEditMode();
448-
}
449-
handleUpdateConcept(tmpConcept);
450-
451-
//setName(""); setDtype("string" as Type); setExamples([]);
452-
}}>
453-
Save
454-
</Button>
455-
</ButtonGroup>
456-
</Box>
457-
</Box>
458-
);
459-
}
460321

461322
export const DerivedConceptForm: FC<ConceptFormProps> = function DerivedConceptForm({ concept, handleUpdateConcept, handleDeleteConcept, turnOffEditMode }) {
462323

@@ -497,9 +358,6 @@ export const DerivedConceptForm: FC<ConceptFormProps> = function DerivedConceptF
497358

498359
let dispatch = useDispatch();
499360

500-
const [collapseCode, setCollapseCode] = useState<boolean>(true);
501-
const [collapseVisInspector, setCollapseVisInspector] = useState<boolean>(true);
502-
503361
let [dialogOpen, setDialogOpen] = useState<boolean>(false);
504362
let [codeCandidates, setCodeCandidates] = useState<string[]>([]);
505363

@@ -514,11 +372,8 @@ export const DerivedConceptForm: FC<ConceptFormProps> = function DerivedConceptF
514372
}
515373
}, [transformCode]);
516374

517-
// time stamps used to track functions from server
518-
const [requestTimeStamp, setRequestTimeStamp] = useState<number>(0);
519375
const [codeGenInProgress, setCodeGenInProgress] = useState<boolean>(false);
520376

521-
let typeList = TypeList
522377
let nameField = (
523378
<TextField key="name-field" id="name" fullWidth label="concept name" value={name} sx={{ minWidth: 120, flex: 1, paddingBottom: 1 }}
524379
FormHelperTextProps={{
@@ -605,9 +460,9 @@ export const DerivedConceptForm: FC<ConceptFormProps> = function DerivedConceptF
605460

606461
viewExamples = (<Box key="viewexample--box" width="100%" sx={{ position: "relative", }}>
607462
<InputLabel shrink>illustration of the generated function</InputLabel>
608-
<GroupItems sx={{ padding: "0px 0px 6px 0px", margin: 0 }}>
463+
<Box className="GroupItems" sx={{ padding: "0px 0px 6px 0px", margin: 0 }}>
609464
{simpleTableView(transformResult, colNames, conceptShelfItems, 5)}
610-
</GroupItems>
465+
</Box>
611466
</Box>)
612467
}
613468

0 commit comments

Comments
 (0)