vzcode 0.84.0 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,509 +0,0 @@
1
- import interact, {
2
- InteractRule,
3
- } from '@replit/codemirror-interact';
4
-
5
- import {
6
- ViewPlugin,
7
- Decoration,
8
- WidgetType,
9
- } from '@codemirror/view';
10
- import { Extension, RangeSet } from '@codemirror/state';
11
- import { EditorView } from 'codemirror';
12
-
13
- // Regular expression for hex colors.
14
- const colorRegex = /#[0-9A-Fa-f]{6}/g;
15
-
16
- // Interactive code widgets.
17
- // * Number dragger
18
- // * Boolean toggler
19
- // * URL clicker
20
- // * color picker
21
- // Inspired by:
22
- // https://github.com/replit/codemirror-interact/blob/master/dev/index.ts
23
- // `onInteract` is called when the user interacts with a widget.
24
- export const widgets = ({
25
- onInteract,
26
- customInteractRules,
27
- }: {
28
- onInteract?: () => void;
29
- customInteractRules?: Array<InteractRule>;
30
- }) => {
31
- const rules: Array<InteractRule> = [
32
- // hex color picker
33
- // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
34
- // Works without quotes to support CSS.
35
- {
36
- regexp: colorRegex,
37
- cursor: 'pointer',
38
- onClick(
39
- text: string,
40
- setText: (newText: string) => void,
41
- ) {
42
- const startingColor: string = text;
43
-
44
- const sel: HTMLInputElement =
45
- document.createElement('input');
46
- sel.type = 'color';
47
- sel.value = startingColor.toLowerCase();
48
-
49
- // valueIsUpper maintains the style of the user's code. It keeps the case of a-f the same case as the original.
50
- const valueIsUpper: boolean =
51
- startingColor.toUpperCase() === startingColor;
52
-
53
- const updateHex = (e: Event) => {
54
- const el: HTMLInputElement =
55
- e.target as HTMLInputElement;
56
- if (onInteract) onInteract();
57
- if (el.value) {
58
- setText(
59
- valueIsUpper
60
- ? el.value.toUpperCase()
61
- : el.value,
62
- );
63
- }
64
- };
65
- sel.addEventListener('input', updateHex);
66
- sel.click();
67
- },
68
- },
69
-
70
- // a rule for a number dragger
71
- {
72
- // the regexp matching the value
73
- regexp: /(?<!\#)-?\b\d+\.?\d*\b/g,
74
- // set cursor to "ew-resize" on hover
75
- cursor: 'ew-resize',
76
- // change number value based on mouse X movement on drag
77
- onDrag: (text, setText, e) => {
78
- if (onInteract) onInteract();
79
- const newVal = Number(text) + e.movementX;
80
- if (isNaN(newVal)) return;
81
- setText(newVal.toString());
82
- },
83
- },
84
- // bool toggler
85
- {
86
- regexp: /true|false/g,
87
- cursor: 'pointer',
88
- onClick: (text, setText) => {
89
- if (onInteract) onInteract();
90
- switch (text) {
91
- case 'true':
92
- return setText('false');
93
- case 'false':
94
- return setText('true');
95
- }
96
- },
97
- },
98
- // vec2 slider
99
- // Inspired by: https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L61
100
- {
101
- regexp:
102
- /vec2\(-?\b\d+\.?\d*\b\s*(,\s*-?\b\d+\.?\d*\b)?\)/g,
103
- cursor: 'move',
104
-
105
- onDrag: (text, setText, e) => {
106
- const res =
107
- /vec2\((?<x>-?\b\d+\.?\d*\b)\s*(,\s*(?<y>-?\b\d+\.?\d*\b))?\)/.exec(
108
- text,
109
- );
110
- const x = Number(res?.groups?.x);
111
- let y = Number(res?.groups?.y);
112
- if (isNaN(x)) return;
113
- if (isNaN(y)) y = x;
114
- setText(
115
- `vec2(${x + e.movementX}, ${y - e.movementY})`,
116
- );
117
- },
118
- },
119
- // rgb color picker
120
- // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
121
- //TODO: create color picker for hsl colors
122
- {
123
- regexp: /rgb\(.*\)/g,
124
- cursor: 'pointer',
125
- onClick: (text, setText, e) => {
126
- const res =
127
- /rgb\((?<r>\d+)\s*,\s*(?<g>\d+)\s*,\s*(?<b>\d+)\)/.exec(
128
- text,
129
- );
130
- const r = Number(res?.groups?.r);
131
- const g = Number(res?.groups?.g);
132
- const b = Number(res?.groups?.b);
133
-
134
- //sel will open the color picker when sel.click is called.
135
- const sel = document.createElement('input');
136
- sel.type = 'color';
137
-
138
- if (!isNaN(r + g + b)) sel.value = rgb2Hex(r, g, b);
139
-
140
- const updateRGB = (e: Event) => {
141
- const el = e.target as HTMLInputElement;
142
- if (onInteract) onInteract();
143
-
144
- if (el.value) {
145
- const [r, g, b] = hex2RGB(el.value);
146
- setText(`rgb(${r}, ${g}, ${b})`);
147
- }
148
- sel.removeEventListener('change', updateRGB);
149
- };
150
-
151
- sel.addEventListener('change', updateRGB);
152
- sel.click();
153
- },
154
- },
155
- // url clicker
156
- {
157
- regexp: /https?:\/\/[^ ")]+/g,
158
- cursor: 'pointer',
159
- onClick: (text) => {
160
- window.open(text);
161
- },
162
- },
163
-
164
- //Set rotation to the angle between the x-axis and a line from the word "rotate" to the mouse pointer (while dragging).
165
- //The rotation is in range (-180,180]
166
- {
167
- regexp: /rotate\(-?\d*\.?\d*\)/g,
168
- cursor: 'move',
169
- onDragStart(text, setText, e) {
170
- rotationOrigin = { x: e.clientX, y: e.clientY };
171
- },
172
-
173
- onDrag(text, setText, e) {
174
- if (rotationOrigin == null) return;
175
- const rotationDegree = Math.round(
176
- (Math.atan2(
177
- rotationOrigin.y - e.clientY,
178
- e.clientX - rotationOrigin.x,
179
- ) *
180
- 180) /
181
- Math.PI,
182
- );
183
- //Calculate the angle between the x axis and a line from where the user first clicks to the current location of the mouse.
184
- setText(`rotate(${rotationDegree})`);
185
- const updateDragEvent = new CustomEvent(
186
- 'updateRotateDrag',
187
- { detail: rotationDegree },
188
- );
189
-
190
- document.dispatchEvent(updateDragEvent);
191
- },
192
- onDragEnd() {
193
- rotationOrigin = null;
194
- },
195
- },
196
- ];
197
- if (customInteractRules) {
198
- rules.push(...customInteractRules);
199
- }
200
- return interact({ rules });
201
- };
202
-
203
- let rotationOrigin: { x: number; y: number } = null;
204
-
205
- // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L108
206
- const hex2RGB = (hex: string): [number, number, number] => {
207
- const v = parseInt(hex.substring(1), 16);
208
- return [(v >> 16) & 255, (v >> 8) & 255, v & 255];
209
- };
210
-
211
- // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L117
212
- const rgb2Hex = (r: number, g: number, b: number): string =>
213
- '#' + r.toString(16) + g.toString(16) + b.toString(16);
214
-
215
- const colorCircleTheme = EditorView.baseTheme({
216
- '.color-circle-parent': {
217
- display: 'inline-block',
218
- cursor: 'inherit',
219
- },
220
- });
221
-
222
- export const colorsInTextPlugin: Extension = [
223
- ViewPlugin.fromClass(
224
- class {
225
- decorations: any;
226
- view: EditorView;
227
- constructor(view: EditorView) {
228
- this.decorations = RangeSet.of([]);
229
- this.view = view;
230
- }
231
- },
232
- {
233
- decorations: (v) => {
234
- const colorInfos = [];
235
-
236
- const lines = v.view.state.doc.iter();
237
- let line = lines.next();
238
-
239
- // Offset is the number of characters before the hex
240
- // so the circle can be placed properly.
241
- let offset = 0;
242
- while (!line.done) {
243
- if (line.value === '\n') {
244
- offset++;
245
- line = lines.next();
246
- continue;
247
- }
248
- const hexColorOccurences = line.value.matchAll(
249
- // /\"\#([0-9]|[A-F]|[a-f]){6}\"/g,
250
- // /["']\#([0-9]|[A-F]|[a-f]){6}["']/g,
251
- colorRegex,
252
- );
253
- let hexOccurance = hexColorOccurences.next();
254
- while (!hexOccurance.done) {
255
- const offsetColorInfo = hexOccurance.value;
256
- offsetColorInfo.index += offset;
257
- colorInfos.push(offsetColorInfo);
258
- hexOccurance = hexColorOccurences.next();
259
- }
260
- offset += line.value.length;
261
- line = lines.next();
262
- }
263
-
264
- return Decoration.set(
265
- colorInfos.map((colorInfo) => {
266
- return {
267
- // 7 is the length of the hex color string
268
- from: colorInfo.index + 7,
269
- to: colorInfo.index + 7,
270
- value: Decoration.widget({
271
- side: -1,
272
- widget: new ColorWidget(colorInfo[0]),
273
- }),
274
- };
275
- }),
276
- );
277
- },
278
- },
279
- ),
280
- colorCircleTheme,
281
- ];
282
-
283
- class ColorWidget extends WidgetType {
284
- color: string;
285
- constructor(color: string) {
286
- super();
287
- this.color = color;
288
- }
289
-
290
- eq(widget: ColorWidget): boolean {
291
- // TODO consider possibly adding a random ID to support multiple instances with the same color
292
- return widget.color === this.color;
293
- }
294
-
295
- toDOM(): HTMLElement {
296
- const parent = document.createElement('div');
297
- const size = 20;
298
- const innerSize = 14;
299
-
300
- parent.setAttribute(
301
- 'style',
302
- `width:${size}px;height:${size}px`,
303
- );
304
- parent.className = 'color-circle-parent';
305
- const svg = document.createElementNS(
306
- 'http://www.w3.org/2000/svg',
307
- 'svg',
308
- );
309
- const colorCircle = document.createElementNS(
310
- 'http://www.w3.org/2000/svg',
311
- 'circle',
312
- );
313
- colorCircle.setAttributeNS(
314
- null,
315
- 'fill',
316
- this.color.replace(/["']/g, ''),
317
- );
318
- // colorCircle.setAttributeNS(null, 'stroke', 'black');
319
- colorCircle.setAttributeNS(
320
- null,
321
- 'r',
322
- '' + innerSize / 2,
323
- );
324
- colorCircle.setAttributeNS(null, 'cx', '' + size / 2);
325
- colorCircle.setAttributeNS(
326
- null,
327
- 'cy',
328
- '' + (size / 2 - 1),
329
- );
330
-
331
- svg.setAttributeNS(null, 'width', '' + size);
332
- svg.setAttributeNS(null, 'height', '' + size);
333
-
334
- svg.appendChild(colorCircle);
335
-
336
- parent.appendChild(svg);
337
- return parent;
338
- }
339
- ignoreEvent() {
340
- return false;
341
- }
342
- }
343
-
344
- export const highlightWidgets = ViewPlugin.fromClass(
345
- class {
346
- showHighlight: boolean;
347
- view: EditorView;
348
- constructor(view: EditorView) {
349
- this.showHighlight = false;
350
- this.view = view;
351
- }
352
- },
353
- {
354
- decorations: (v) => {
355
- if (!v.showHighlight) {
356
- return Decoration.none;
357
- }
358
-
359
- const interactOpportunities = [];
360
- const lines = v.view.state.doc.iter();
361
- let line = lines.next();
362
-
363
- //Offset is the number of characters before the regex so the highlighting can be placed properly.
364
- let offset = 0;
365
- while (!line.done) {
366
- if (line.value === '\n') {
367
- offset++;
368
- line = lines.next();
369
- continue;
370
- }
371
- const interactiveOccurances = line.value.matchAll(
372
- //The below line contains all of the Regexes for all of the interactive widgets.
373
- /\"\#([0-9]|[A-F]|[a-f]){6}\"|rotate\(-?\d*\.?\d*\)|https?:\/\/[^ "]+|vec2\(-?\b\d+\.?\d*\b\s*(,\s*-?\b\d+\.?\d*\b)?\)|true|false|(?<!\#)-?\b\d+\.?\d*\b|rgb\(.*\)/dg,
374
- );
375
- let interactOccurance =
376
- interactiveOccurances.next();
377
- while (!interactOccurance.done) {
378
- const offsetInteract = interactOccurance.value;
379
- offsetInteract.indices[0][0] += offset;
380
- offsetInteract.indices[0][1] += offset;
381
- interactOpportunities.push(offsetInteract);
382
- interactOccurance = interactiveOccurances.next();
383
- }
384
- offset += line.value.length;
385
- line = lines.next();
386
- }
387
- return Decoration.set(
388
- interactOpportunities.map((opportunity) => {
389
- return {
390
- from: opportunity.indices[0][0],
391
- to: opportunity.indices[0][1],
392
- value: Decoration.mark({
393
- class: 'cm-interact ',
394
- }),
395
- };
396
- }),
397
- );
398
- },
399
- eventHandlers: {
400
- keydown(event, view) {
401
- if (event.key == 'Alt') {
402
- this.showHighlight = true;
403
- }
404
- },
405
- keyup(event, view) {
406
- if (event.key == 'Alt') {
407
- this.showHighlight = false;
408
- }
409
- },
410
- },
411
- },
412
- );
413
-
414
- export const rotationIndicator = ViewPlugin.fromClass(
415
- class {
416
- view: EditorView;
417
- textPosition?: number;
418
- rotation: number;
419
- constructor(view) {
420
- this.view = view;
421
- this.textPosition = null;
422
- this.rotation = 0;
423
-
424
- document.addEventListener(
425
- 'updateRotateDrag',
426
- (e: CustomEvent) => {
427
- this.textPosition = this.view.posAtCoords(
428
- rotationOrigin,
429
- false,
430
- );
431
-
432
- this.rotation = e.detail;
433
- },
434
- );
435
- }
436
- },
437
- {
438
- decorations: (v) => {
439
- if (rotationOrigin === null) {
440
- return Decoration.none;
441
- }
442
- return Decoration.set([
443
- {
444
- from: v.textPosition,
445
- to: v.textPosition,
446
- value: Decoration.widget({
447
- side: -1,
448
- widget: new RotationCircle(v.rotation),
449
- }),
450
- },
451
- ]);
452
- },
453
- },
454
- );
455
-
456
- class RotationCircle extends WidgetType {
457
- angle: number;
458
- constructor(angle: number) {
459
- super();
460
- this.angle = angle;
461
- }
462
-
463
- toDOM(view: EditorView): HTMLElement {
464
- const parent = document.createElement('div');
465
-
466
- parent.setAttribute('style', 'width:20px;height:20px');
467
- parent.className = 'color-circle-parent';
468
- const svg = document.createElementNS(
469
- 'http://www.w3.org/2000/svg',
470
- 'svg',
471
- );
472
- const colorCircle = document.createElementNS(
473
- 'http://www.w3.org/2000/svg',
474
- 'circle',
475
- );
476
- colorCircle.setAttributeNS(null, 'fill', '#808080');
477
- colorCircle.setAttributeNS(null, 'r', '5');
478
- colorCircle.setAttributeNS(null, 'cx', '5');
479
- colorCircle.setAttributeNS(null, 'cy', '5');
480
-
481
- const indicatorLine = document.createElementNS(
482
- 'http://www.w3.org/2000/svg',
483
- 'line',
484
- );
485
- indicatorLine.setAttributeNS(null, 'x1', '10');
486
- indicatorLine.setAttributeNS(null, 'x2', '5');
487
- indicatorLine.setAttributeNS(null, 'y1', '5');
488
- indicatorLine.setAttributeNS(null, 'y2', '5');
489
- indicatorLine.setAttributeNS(null, 'stroke', 'black');
490
- indicatorLine.setAttributeNS(
491
- null,
492
- 'transform',
493
- `rotate(${360 - this.angle},5,5)`,
494
- );
495
-
496
- svg.setAttributeNS(null, 'viewBox', '0 0 10 10');
497
- svg.setAttributeNS(null, 'width', '20');
498
- svg.setAttributeNS(null, 'height', '20');
499
-
500
- svg.appendChild(colorCircle);
501
- svg.appendChild(indicatorLine);
502
-
503
- parent.appendChild(svg);
504
- return parent;
505
- }
506
- ignoreEvent() {
507
- return false;
508
- }
509
- }