vzcode
Version:
Multiplayer code editor system
175 lines (153 loc) • 4.6 kB
text/typescript
import { Extension, RangeSet } from '@codemirror/state';
import {
Decoration,
EditorView,
ViewPlugin,
WidgetType,
} from '@codemirror/view';
import { colorPickerRegex } from './colorPicker';
const colorCircleTheme = EditorView.baseTheme({
'.color-circle-parent': {
display: 'inline-block',
cursor: 'inherit',
},
});
class ColorWidget extends WidgetType {
color: string;
constructor(color: string) {
super();
this.color = color;
}
eq(widget: ColorWidget): boolean {
return widget.color === this.color;
}
toDOM(): HTMLElement {
const parent = document.createElement('div');
const size = 20;
const innerSize = 14;
parent.setAttribute(
'style',
`width:${size}px;height:${size}px;position:relative;`,
// Position relative for the hovering tool tip to work
);
parent.className = 'color-circle-parent';
const svg = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg',
);
const colorCircle = document.createElementNS(
'http://www.w3.org/2000/svg',
'circle',
);
colorCircle.setAttributeNS(
null,
'fill',
this.color.replace(/["']/g, ''),
);
colorCircle.setAttributeNS(null, 'stroke', 'white');
colorCircle.setAttributeNS(
null,
'stroke-width',
'0.75',
);
colorCircle.setAttributeNS(
null,
'r',
'' + innerSize / 2,
);
colorCircle.setAttributeNS(null, 'cx', '' + size / 2);
colorCircle.setAttributeNS(
null,
'cy',
'' + (size / 2 - 1),
);
svg.setAttributeNS(null, 'width', '' + size);
svg.setAttributeNS(null, 'height', '' + size);
svg.appendChild(colorCircle);
parent.appendChild(svg);
// Hovering tool tip shortcut for when users hover over color circle
const hoveringTooltip = document.createElement('div');
// Set class for tooltip to use CSS styles
hoveringTooltip.className = 'tooltip_hover_circle';
hoveringTooltip.innerHTML = `<strong>Alt + Click on a hex color</strong><br />
<div>Open a color picker to modify the color</div>`;
parent.appendChild(hoveringTooltip);
// Timer to prevent constant pop-ups when scrolling through code
let hoverTimeout: number;
// Fade in and fade out effect for when user hovers over the circle
// Mouse Event to set y and x of the tool tip pop up to prevent overlap onto circle
parent.addEventListener(
'mouseenter',
(event: MouseEvent) => {
hoverTimeout = window.setTimeout(() => {
hoveringTooltip.style.top = `${event.clientY - 60}px`;
hoveringTooltip.style.left = `${event.clientX}px`;
hoveringTooltip.style.opacity = '1';
}, 700);
},
);
parent.addEventListener('mouseleave', () => {
clearTimeout(hoverTimeout);
hoveringTooltip.style.opacity = '0';
});
return parent;
}
ignoreEvent() {
return false;
}
}
export const colorsInTextPlugin: Extension = [
ViewPlugin.fromClass(
class {
decorations: any;
view: EditorView;
constructor(view: EditorView) {
this.decorations = RangeSet.of([]);
this.view = view;
}
},
{
decorations: (v) => {
const colorInfos = [];
const lines = v.view.state.doc.iter();
let line = lines.next();
// Offset is the number of characters before the hex
// so the circle can be placed properly.
let offset = 0;
while (!line.done) {
if (line.value === '\n') {
offset++;
line = lines.next();
continue;
}
const hexColorOccurences = line.value.matchAll(
colorPickerRegex,
);
let hexOccurance = hexColorOccurences.next();
while (!hexOccurance.done) {
const offsetColorInfo = hexOccurance.value;
offsetColorInfo.index += offset;
colorInfos.push(offsetColorInfo);
hexOccurance = hexColorOccurences.next();
}
offset += line.value.length;
line = lines.next();
}
return Decoration.set(
colorInfos.map((colorInfo) => {
return {
// 7 is the length of the hex color string
from: colorInfo.index + 7,
to: colorInfo.index + 7,
value: Decoration.widget({
side: -1,
widget: new ColorWidget(colorInfo[0]),
}),
};
}),
);
},
},
),
colorCircleTheme,
];