aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src')
-rw-r--r--subprojects/frontend/src/editor/EditorArea.tsx1
-rw-r--r--subprojects/frontend/src/editor/EditorStore.ts8
-rw-r--r--subprojects/frontend/src/editor/EditorTheme.ts36
-rw-r--r--subprojects/frontend/src/graph/DotGraphVisualizer.tsx8
-rw-r--r--subprojects/frontend/src/graph/GraphStore.ts36
-rw-r--r--subprojects/frontend/src/graph/GraphTheme.tsx31
-rw-r--r--subprojects/frontend/src/graph/dotSource.ts3
-rw-r--r--subprojects/frontend/src/graph/export/exportDiagram.tsx11
-rw-r--r--subprojects/frontend/src/graph/obfuscateColor.ts21
-rw-r--r--subprojects/frontend/src/xtext/HighlightingService.ts12
10 files changed, 154 insertions, 13 deletions
diff --git a/subprojects/frontend/src/editor/EditorArea.tsx b/subprojects/frontend/src/editor/EditorArea.tsx
index aafaad40..ae5cff34 100644
--- a/subprojects/frontend/src/editor/EditorArea.tsx
+++ b/subprojects/frontend/src/editor/EditorArea.tsx
@@ -39,6 +39,7 @@ export default observer(function EditorArea({
39 showLineNumbers={editorStore.showLineNumbers} 39 showLineNumbers={editorStore.showLineNumbers}
40 showActiveLine={!editorStore.hasSelection} 40 showActiveLine={!editorStore.hasSelection}
41 colorIdentifiers={editorStore.colorIdentifiers} 41 colorIdentifiers={editorStore.colorIdentifiers}
42 hexTypeHashes={editorStore.hexTypeHashes}
42 ref={editorParentRef} 43 ref={editorParentRef}
43 /> 44 />
44 </Box> 45 </Box>
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts
index 33bca382..f128d70d 100644
--- a/subprojects/frontend/src/editor/EditorStore.ts
+++ b/subprojects/frontend/src/editor/EditorStore.ts
@@ -111,6 +111,8 @@ export default class EditorStore {
111 111
112 unsavedChanges = false; 112 unsavedChanges = false;
113 113
114 hexTypeHashes: string[] = [];
115
114 constructor( 116 constructor(
115 initialValue: string, 117 initialValue: string,
116 pwaStore: PWAStore, 118 pwaStore: PWAStore,
@@ -275,8 +277,12 @@ export default class EditorStore {
275 this.doCommand(nextDiagnostic); 277 this.doCommand(nextDiagnostic);
276 } 278 }
277 279
278 updateSemanticHighlighting(ranges: IHighlightRange[]): void { 280 updateSemanticHighlighting(
281 ranges: IHighlightRange[],
282 hexTypeHashes: string[],
283 ): void {
279 this.dispatch(setSemanticHighlighting(ranges)); 284 this.dispatch(setSemanticHighlighting(ranges));
285 this.hexTypeHashes = hexTypeHashes;
280 } 286 }
281 287
282 updateOccurrences(write: IOccurrence[], read: IOccurrence[]): void { 288 updateOccurrences(write: IOccurrence[], read: IOccurrence[]): void {
diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts
index 4978c7f7..6deda080 100644
--- a/subprojects/frontend/src/editor/EditorTheme.ts
+++ b/subprojects/frontend/src/editor/EditorTheme.ts
@@ -14,6 +14,7 @@ import {
14 type CSSObject, 14 type CSSObject,
15 type Theme, 15 type Theme,
16} from '@mui/material/styles'; 16} from '@mui/material/styles';
17import { lch } from 'd3-color';
17import { range } from 'lodash-es'; 18import { range } from 'lodash-es';
18 19
19import svgURL from '../utils/svgURL'; 20import svgURL from '../utils/svgURL';
@@ -21,6 +22,7 @@ import svgURL from '../utils/svgURL';
21function createTypeHashStyles( 22function createTypeHashStyles(
22 theme: Theme, 23 theme: Theme,
23 colorIdentifiers: boolean, 24 colorIdentifiers: boolean,
25 hexTypeHashes: string[],
24): CSSObject { 26): CSSObject {
25 if (!colorIdentifiers) { 27 if (!colorIdentifiers) {
26 return {}; 28 return {};
@@ -34,6 +36,26 @@ function createTypeHashStyles(
34 }, 36 },
35 }; 37 };
36 }); 38 });
39 hexTypeHashes.forEach((typeHash) => {
40 let color = lch(`#${typeHash}`);
41 if (theme.palette.mode === 'dark') {
42 color = color.brighter();
43 if (color.l < 60) {
44 color.l = 60;
45 }
46 } else {
47 color = color.darker();
48 if (color.l > 60) {
49 color.l = 60;
50 }
51 }
52 result[`.tok-problem-typeHash-_${typeHash}`] = {
53 '&, .tok-typeName': {
54 color: color.formatRgb(),
55 fontWeight: theme.typography.fontWeightEditorTypeHash,
56 },
57 };
58 });
37 return result; 59 return result;
38} 60}
39 61
@@ -42,12 +64,20 @@ export default styled('div', {
42 shouldForwardProp: (propName) => 64 shouldForwardProp: (propName) =>
43 propName !== 'showLineNumbers' && 65 propName !== 'showLineNumbers' &&
44 propName !== 'showActiveLine' && 66 propName !== 'showActiveLine' &&
45 propName !== 'colorIdentifiers', 67 propName !== 'colorIdentifiers' &&
68 propName !== 'hexTypeHashes',
46})<{ 69})<{
47 showLineNumbers: boolean; 70 showLineNumbers: boolean;
48 showActiveLine: boolean; 71 showActiveLine: boolean;
49 colorIdentifiers: boolean; 72 colorIdentifiers: boolean;
50}>(({ theme, showLineNumbers, showActiveLine, colorIdentifiers }) => { 73 hexTypeHashes: string[];
74}>(({
75 theme,
76 showLineNumbers,
77 showActiveLine,
78 colorIdentifiers,
79 hexTypeHashes,
80}) => {
51 const editorFontStyle: CSSObject = { 81 const editorFontStyle: CSSObject = {
52 ...theme.typography.editor, 82 ...theme.typography.editor,
53 fontWeight: theme.typography.fontWeightEditorNormal, 83 fontWeight: theme.typography.fontWeightEditorNormal,
@@ -157,7 +187,7 @@ export default styled('div', {
157 fontStyle: 'normal', 187 fontStyle: 'normal',
158 }, 188 },
159 }, 189 },
160 ...createTypeHashStyles(theme, colorIdentifiers), 190 ...createTypeHashStyles(theme, colorIdentifiers, hexTypeHashes),
161 }; 191 };
162 192
163 const matchingStyle: CSSObject = { 193 const matchingStyle: CSSObject = {
diff --git a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx
index cc8b5116..0980ea20 100644
--- a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx
+++ b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx
@@ -154,7 +154,13 @@ function DotGraphVisualizer({
154 ], 154 ],
155 ); 155 );
156 156
157 return <GraphTheme ref={setElement} colorNodes={graph.colorNodes} />; 157 return (
158 <GraphTheme
159 ref={setElement}
160 colorNodes={graph.colorNodes}
161 hexTypeHashes={graph.hexTypeHashes}
162 />
163 );
158} 164}
159 165
160DotGraphVisualizer.defaultProps = { 166DotGraphVisualizer.defaultProps = {
diff --git a/subprojects/frontend/src/graph/GraphStore.ts b/subprojects/frontend/src/graph/GraphStore.ts
index d9282326..301b4d86 100644
--- a/subprojects/frontend/src/graph/GraphStore.ts
+++ b/subprojects/frontend/src/graph/GraphStore.ts
@@ -49,6 +49,8 @@ export function isVisibilityAllowed(
49 return true; 49 return true;
50} 50}
51 51
52const TYPE_HASH_HEX_PREFFIX = '_';
53
52export default class GraphStore { 54export default class GraphStore {
53 semantics: SemanticsSuccessResult = { 55 semantics: SemanticsSuccessResult = {
54 nodes: [], 56 nodes: [],
@@ -66,6 +68,10 @@ export default class GraphStore {
66 68
67 selectedSymbol: RelationMetadata | undefined; 69 selectedSymbol: RelationMetadata | undefined;
68 70
71 hexTypeHashes: string[] = [];
72
73 private typeHashesMap = new Map<string, number>();
74
69 constructor( 75 constructor(
70 private readonly editorStore: EditorStore, 76 private readonly editorStore: EditorStore,
71 private readonly nameOverride?: string, 77 private readonly nameOverride?: string,
@@ -188,6 +194,36 @@ export default class GraphStore {
188 this.visibility.delete(key); 194 this.visibility.delete(key);
189 }); 195 });
190 this.setSelectedSymbol(this.selectedSymbol); 196 this.setSelectedSymbol(this.selectedSymbol);
197 this.updateTypeHashes();
198 }
199
200 /**
201 * Maintains a list of past and current color codes to avoid flashing
202 * when the graph view updates.
203 *
204 * As long as the previously used colors are still in in `typeHashesMap`,
205 * the view will not flash while Graphviz is recomputing, because we'll
206 * keep emitting styles for the colors.
207 */
208 private updateTypeHashes(): void {
209 this.semantics.nodes.forEach(({ typeHash }) => {
210 if (
211 typeHash !== undefined &&
212 typeHash.startsWith(TYPE_HASH_HEX_PREFFIX)
213 ) {
214 const key = typeHash.substring(TYPE_HASH_HEX_PREFFIX.length);
215 this.typeHashesMap.set(key, 0);
216 }
217 });
218 this.hexTypeHashes = Array.from(this.typeHashesMap.keys());
219 this.hexTypeHashes.forEach((typeHash) => {
220 const age = this.typeHashesMap.get(typeHash);
221 if (age !== undefined && age < 10) {
222 this.typeHashesMap.set(typeHash, age + 1);
223 } else {
224 this.typeHashesMap.delete(typeHash);
225 }
226 });
191 } 227 }
192 228
193 get colorNodes(): boolean { 229 get colorNodes(): boolean {
diff --git a/subprojects/frontend/src/graph/GraphTheme.tsx b/subprojects/frontend/src/graph/GraphTheme.tsx
index 34954345..50a003e0 100644
--- a/subprojects/frontend/src/graph/GraphTheme.tsx
+++ b/subprojects/frontend/src/graph/GraphTheme.tsx
@@ -13,10 +13,13 @@ import {
13 type CSSObject, 13 type CSSObject,
14 type Theme, 14 type Theme,
15} from '@mui/material/styles'; 15} from '@mui/material/styles';
16import { lch } from 'd3-color';
16import { range } from 'lodash-es'; 17import { range } from 'lodash-es';
17 18
18import svgURL from '../utils/svgURL'; 19import svgURL from '../utils/svgURL';
19 20
21import obfuscateColor from './obfuscateColor';
22
20function createEdgeColor( 23function createEdgeColor(
21 suffix: string, 24 suffix: string,
22 stroke: string, 25 stroke: string,
@@ -37,16 +40,32 @@ function createEdgeColor(
37 }; 40 };
38} 41}
39 42
40function createTypeHashStyles(theme: Theme, colorNodes: boolean): CSSObject { 43function createTypeHashStyles(
44 theme: Theme,
45 colorNodes: boolean,
46 typeHashes: string[],
47): CSSObject {
41 if (!colorNodes) { 48 if (!colorNodes) {
42 return {}; 49 return {};
43 } 50 }
44 const result: CSSObject = {}; 51 const result: CSSObject = {};
45 range(theme.palette.highlight.typeHash.length).forEach((i) => { 52 range(theme.palette.highlight.typeHash.length).forEach((i) => {
46 result[`.node-typeHash-${i} .node-header`] = { 53 result[`.node-typeHash-${obfuscateColor(i.toString(10))} .node-header`] = {
47 fill: theme.palette.highlight.typeHash[i]?.box, 54 fill: theme.palette.highlight.typeHash[i]?.box,
48 }; 55 };
49 }); 56 });
57 typeHashes.forEach((typeHash) => {
58 let color = lch(`#${typeHash}`);
59 if (theme.palette.mode === 'dark') {
60 color = color.darker();
61 if (color.l > 50) {
62 color.l = 50;
63 }
64 }
65 result[`.node-typeHash-_${obfuscateColor(typeHash)} .node-header`] = {
66 fill: color.formatRgb(),
67 };
68 });
50 return result; 69 return result;
51} 70}
52 71
@@ -69,10 +88,12 @@ function iconStyle(
69export function createGraphTheme({ 88export function createGraphTheme({
70 theme, 89 theme,
71 colorNodes, 90 colorNodes,
91 hexTypeHashes,
72 noEmbedIcons, 92 noEmbedIcons,
73}: { 93}: {
74 theme: Theme; 94 theme: Theme;
75 colorNodes: boolean; 95 colorNodes: boolean;
96 hexTypeHashes: string[];
76 noEmbedIcons?: boolean; 97 noEmbedIcons?: boolean;
77}): CSSObject { 98}): CSSObject {
78 const shadowAlapha = theme.palette.mode === 'dark' ? 0.32 : 0.24; 99 const shadowAlapha = theme.palette.mode === 'dark' ? 0.32 : 0.24;
@@ -111,7 +132,7 @@ export function createGraphTheme({
111 '.node-exists-UNKNOWN .node-outline': { 132 '.node-exists-UNKNOWN .node-outline': {
112 strokeDasharray: '5 2', 133 strokeDasharray: '5 2',
113 }, 134 },
114 ...createTypeHashStyles(theme, colorNodes), 135 ...createTypeHashStyles(theme, colorNodes, hexTypeHashes),
115 '.edge': { 136 '.edge': {
116 '& text': { 137 '& text': {
117 fontFamily: theme.typography.fontFamily, 138 fontFamily: theme.typography.fontFamily,
@@ -155,7 +176,9 @@ export function createGraphTheme({
155 176
156export default styled('div', { 177export default styled('div', {
157 name: 'GraphTheme', 178 name: 'GraphTheme',
158})<{ colorNodes: boolean }>((args) => ({ 179 shouldForwardProp: (prop) =>
180 prop !== 'colorNodes' && prop !== 'hexTypeHashes',
181})<{ colorNodes: boolean; hexTypeHashes: string[] }>((args) => ({
159 '& svg': { 182 '& svg': {
160 userSelect: 'none', 183 userSelect: 'none',
161 ...createGraphTheme(args), 184 ...createGraphTheme(args),
diff --git a/subprojects/frontend/src/graph/dotSource.ts b/subprojects/frontend/src/graph/dotSource.ts
index 3ac5eb1c..bcd386cf 100644
--- a/subprojects/frontend/src/graph/dotSource.ts
+++ b/subprojects/frontend/src/graph/dotSource.ts
@@ -10,6 +10,7 @@ import type {
10} from '../xtext/xtextServiceResults'; 10} from '../xtext/xtextServiceResults';
11 11
12import type GraphStore from './GraphStore'; 12import type GraphStore from './GraphStore';
13import obfuscateColor from './obfuscateColor';
13 14
14const EDGE_WEIGHT = 1; 15const EDGE_WEIGHT = 1;
15const CONTAINMENT_WEIGHT = 5; 16const CONTAINMENT_WEIGHT = 5;
@@ -143,7 +144,7 @@ function createNodes(
143 classList.push('node-empty'); 144 classList.push('node-empty');
144 } 145 }
145 if (node.typeHash !== undefined) { 146 if (node.typeHash !== undefined) {
146 classList.push(`node-typeHash-${node.typeHash}`); 147 classList.push(`node-typeHash-${obfuscateColor(node.typeHash)}`);
147 } 148 }
148 const classes = classList.join(' '); 149 const classes = classList.join(' ');
149 const name = nodeName(graph, node); 150 const name = nodeName(graph, node);
diff --git a/subprojects/frontend/src/graph/export/exportDiagram.tsx b/subprojects/frontend/src/graph/export/exportDiagram.tsx
index 44489d28..6abbcfdf 100644
--- a/subprojects/frontend/src/graph/export/exportDiagram.tsx
+++ b/subprojects/frontend/src/graph/export/exportDiagram.tsx
@@ -147,6 +147,7 @@ function appendStyles(
147 svg: SVGSVGElement, 147 svg: SVGSVGElement,
148 theme: Theme, 148 theme: Theme,
149 colorNodes: boolean, 149 colorNodes: boolean,
150 hexTypeHashes: string[],
150 fontsCSS: string, 151 fontsCSS: string,
151): void { 152): void {
152 const cache = createCache({ 153 const cache = createCache({
@@ -159,6 +160,7 @@ function appendStyles(
159 const styles = serializeStyles([createGraphTheme], cache.registered, { 160 const styles = serializeStyles([createGraphTheme], cache.registered, {
160 theme, 161 theme,
161 colorNodes, 162 colorNodes,
163 hexTypeHashes,
162 noEmbedIcons: true, 164 noEmbedIcons: true,
163 }); 165 });
164 const rules: string[] = [fontsCSS]; 166 const rules: string[] = [fontsCSS];
@@ -336,7 +338,14 @@ export default async function exportDiagram(
336 } else if (settings.format === 'svg' && settings.embedFonts) { 338 } else if (settings.format === 'svg' && settings.embedFonts) {
337 fontsCSS = await fetchFontCSS(); 339 fontsCSS = await fetchFontCSS();
338 } 340 }
339 appendStyles(svgDocument, copyOfSVG, theme, colorNodes, fontsCSS); 341 appendStyles(
342 svgDocument,
343 copyOfSVG,
344 theme,
345 colorNodes,
346 graph.hexTypeHashes,
347 fontsCSS,
348 );
340 349
341 if (settings.format === 'pdf') { 350 if (settings.format === 'pdf') {
342 const pdf = await serializePDF(copyOfSVG, settings); 351 const pdf = await serializePDF(copyOfSVG, settings);
diff --git a/subprojects/frontend/src/graph/obfuscateColor.ts b/subprojects/frontend/src/graph/obfuscateColor.ts
new file mode 100644
index 00000000..57c15804
--- /dev/null
+++ b/subprojects/frontend/src/graph/obfuscateColor.ts
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7const regExp = /\d/g;
8const offset = 'g'.charCodeAt(0) - '0'.charCodeAt(0);
9
10/*
11 * The SVG animation framework we use garbles all numbers while interpolating,
12 * so we mask numbers in hex color codes by replacing them with letters.
13 *
14 * @param color The hex code.
15 * @return The hex code with no number characters.
16 */
17export default function obfuscateColor(color: string): string {
18 return color.replaceAll(regExp, (match) =>
19 String.fromCharCode(match.charCodeAt(0) + offset),
20 );
21}
diff --git a/subprojects/frontend/src/xtext/HighlightingService.ts b/subprojects/frontend/src/xtext/HighlightingService.ts
index 447f1401..eacee117 100644
--- a/subprojects/frontend/src/xtext/HighlightingService.ts
+++ b/subprojects/frontend/src/xtext/HighlightingService.ts
@@ -10,6 +10,8 @@ import type { IHighlightRange } from '../editor/semanticHighlighting';
10import type UpdateService from './UpdateService'; 10import type UpdateService from './UpdateService';
11import { highlightingResult } from './xtextServiceResults'; 11import { highlightingResult } from './xtextServiceResults';
12 12
13const TYPE_HASH_HEX_PREFIX = 'typeHash-_';
14
13export default class HighlightingService { 15export default class HighlightingService {
14 constructor( 16 constructor(
15 private readonly store: EditorStore, 17 private readonly store: EditorStore,
@@ -20,6 +22,7 @@ export default class HighlightingService {
20 const { regions } = highlightingResult.parse(push); 22 const { regions } = highlightingResult.parse(push);
21 const allChanges = this.updateService.computeChangesSinceLastUpdate(); 23 const allChanges = this.updateService.computeChangesSinceLastUpdate();
22 const ranges: IHighlightRange[] = []; 24 const ranges: IHighlightRange[] = [];
25 const hexTypeHashes = new Set<string>();
23 regions.forEach(({ offset, length, styleClasses }) => { 26 regions.forEach(({ offset, length, styleClasses }) => {
24 if (styleClasses.length === 0) { 27 if (styleClasses.length === 0) {
25 return; 28 return;
@@ -34,11 +37,16 @@ export default class HighlightingService {
34 to, 37 to,
35 classes: styleClasses, 38 classes: styleClasses,
36 }); 39 });
40 styleClasses.forEach((styleClass) => {
41 if (styleClass.startsWith(TYPE_HASH_HEX_PREFIX)) {
42 hexTypeHashes.add(styleClass.substring(TYPE_HASH_HEX_PREFIX.length));
43 }
44 });
37 }); 45 });
38 this.store.updateSemanticHighlighting(ranges); 46 this.store.updateSemanticHighlighting(ranges, Array.from(hexTypeHashes));
39 } 47 }
40 48
41 onDisconnect(): void { 49 onDisconnect(): void {
42 this.store.updateSemanticHighlighting([]); 50 this.store.updateSemanticHighlighting([], []);
43 } 51 }
44} 52}