1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';
|
22 |
|
23 | export const isIE = (userAgent.indexOf('Trident') >= 0);
|
24 | export const isEdge = (userAgent.indexOf('Edge/') >= 0);
|
25 | export const isEdgeOrIE = isIE || isEdge;
|
26 |
|
27 | export const isOpera = (userAgent.indexOf('Opera') >= 0);
|
28 | export const isFirefox = (userAgent.indexOf('Firefox') >= 0);
|
29 | export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0);
|
30 | export const isChrome = (userAgent.indexOf('Chrome') >= 0);
|
31 | export const isSafari = (userAgent.indexOf('Chrome') === -1) && (userAgent.indexOf('Safari') >= 0);
|
32 | export const isIPad = (userAgent.indexOf('iPad') >= 0);
|
33 |
|
34 | export const isNative = typeof (window as any).process !== 'undefined';
|
35 |
|
36 | export const isBasicWasmSupported = typeof (window as any).WebAssembly !== 'undefined';
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | export function animationFrame(n: number = 1): Promise<void> {
|
43 | return new Promise(resolve => {
|
44 | function frameFunc(): void {
|
45 | if (n <= 0) {
|
46 | resolve();
|
47 | } else {
|
48 | n--;
|
49 | requestAnimationFrame(frameFunc);
|
50 | }
|
51 | }
|
52 | frameFunc();
|
53 | });
|
54 | }
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 | export function parseCssMagnitude(value: string | null, defaultValue: number): number;
|
62 | export function parseCssMagnitude(value: string | null, defaultValue?: number): number | undefined {
|
63 | if (value) {
|
64 | let parsed: number;
|
65 | if (value.endsWith('px')) {
|
66 | parsed = parseFloat(value.substring(0, value.length - 2));
|
67 | } else {
|
68 | parsed = parseFloat(value);
|
69 | }
|
70 | if (!isNaN(parsed)) {
|
71 | return parsed;
|
72 | }
|
73 | }
|
74 | return defaultValue;
|
75 | }
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 | export function parseCssTime(time: string | null, defaultValue: number): number;
|
82 | export function parseCssTime(time: string | null, defaultValue?: number): number | undefined {
|
83 | if (time) {
|
84 | let parsed: number;
|
85 | if (time.endsWith('ms')) {
|
86 | parsed = parseFloat(time.substring(0, time.length - 2));
|
87 | } else if (time.endsWith('s')) {
|
88 | parsed = parseFloat(time.substring(0, time.length - 1)) * 1000;
|
89 | } else {
|
90 | parsed = parseFloat(time);
|
91 | }
|
92 | if (!isNaN(parsed)) {
|
93 | return parsed;
|
94 | }
|
95 | }
|
96 | return defaultValue;
|
97 | }
|
98 |
|
99 | interface ElementScroll {
|
100 | left: number
|
101 | top: number
|
102 | maxLeft: number
|
103 | maxTop: number
|
104 | }
|
105 |
|
106 | function getMonacoEditorScroll(elem: HTMLElement): ElementScroll | undefined {
|
107 | const linesContent = elem.querySelector('.lines-content') as HTMLElement;
|
108 | const viewLines = elem.querySelector('.view-lines') as HTMLElement;
|
109 |
|
110 | if (linesContent === null || viewLines === null) {
|
111 | return undefined;
|
112 | }
|
113 | const linesContentStyle = linesContent.style;
|
114 | const elemStyle = elem.style;
|
115 | const viewLinesStyle = viewLines.style;
|
116 | return {
|
117 | left: -parseCssMagnitude(linesContentStyle.left, 0),
|
118 | top: -parseCssMagnitude(linesContentStyle.top, 0),
|
119 | maxLeft: parseCssMagnitude(viewLinesStyle.width, 0) - parseCssMagnitude(elemStyle.width, 0),
|
120 | maxTop: parseCssMagnitude(viewLinesStyle.height, 0) - parseCssMagnitude(elemStyle.height, 0)
|
121 | };
|
122 | }
|
123 |
|
124 |
|
125 |
|
126 |
|
127 | export function preventNavigation(event: WheelEvent): void {
|
128 | const { currentTarget, deltaX, deltaY } = event;
|
129 | const absDeltaX = Math.abs(deltaX);
|
130 | const absDeltaY = Math.abs(deltaY);
|
131 | let elem = event.target as Element | null;
|
132 |
|
133 | while (elem && elem !== currentTarget) {
|
134 | let scroll: ElementScroll | undefined;
|
135 | if (elem.classList.contains('monaco-scrollable-element')) {
|
136 | scroll = getMonacoEditorScroll(elem as HTMLElement);
|
137 | } else {
|
138 | scroll = {
|
139 | left: elem.scrollLeft,
|
140 | top: elem.scrollTop,
|
141 | maxLeft: elem.scrollWidth - elem.clientWidth,
|
142 | maxTop: elem.scrollHeight - elem.clientHeight
|
143 | };
|
144 | }
|
145 | if (scroll) {
|
146 | const scrollH = scroll.maxLeft > 0 && (deltaX < 0 && scroll.left > 0 || deltaX > 0 && scroll.left < scroll.maxLeft);
|
147 | const scrollV = scroll.maxTop > 0 && (deltaY < 0 && scroll.top > 0 || deltaY > 0 && scroll.top < scroll.maxTop);
|
148 | if (scrollH && scrollV || scrollH && absDeltaX > absDeltaY || scrollV && absDeltaY > absDeltaX) {
|
149 |
|
150 | return;
|
151 | }
|
152 | }
|
153 | elem = elem.parentElement;
|
154 | }
|
155 |
|
156 | event.preventDefault();
|
157 | event.stopPropagation();
|
158 | }
|
159 |
|
160 | export type PartialCSSStyle = Omit<Partial<CSSStyleDeclaration>,
|
161 | 'visibility' |
|
162 | 'display' |
|
163 | 'parentRule' |
|
164 | 'getPropertyPriority' |
|
165 | 'getPropertyValue' |
|
166 | 'item' |
|
167 | 'removeProperty' |
|
168 | 'setProperty'>;
|
169 |
|
170 | export function measureTextWidth(text: string | string[], style?: PartialCSSStyle): number {
|
171 | const measureElement = getMeasurementElement(style);
|
172 | text = Array.isArray(text) ? text : [text];
|
173 | let width = 0;
|
174 | for (const item of text) {
|
175 | measureElement.textContent = item;
|
176 | width = Math.max(measureElement.getBoundingClientRect().width, width);
|
177 | }
|
178 | return width;
|
179 | }
|
180 |
|
181 | export function measureTextHeight(text: string | string[], style?: PartialCSSStyle): number {
|
182 | const measureElement = getMeasurementElement(style);
|
183 | text = Array.isArray(text) ? text : [text];
|
184 | let height = 0;
|
185 | for (const item of text) {
|
186 | measureElement.textContent = item;
|
187 | height = Math.max(measureElement.getBoundingClientRect().height, height);
|
188 | }
|
189 | return height;
|
190 | }
|
191 |
|
192 | const defaultStyle = document.createElement('div').style;
|
193 | defaultStyle.fontFamily = 'var(--theia-ui-font-family)';
|
194 | defaultStyle.fontSize = 'var(--theia-ui-font-size1)';
|
195 | defaultStyle.visibility = 'hidden';
|
196 |
|
197 | function getMeasurementElement(style?: PartialCSSStyle): HTMLElement {
|
198 | let measureElement = document.getElementById('measure');
|
199 | if (!measureElement) {
|
200 | measureElement = document.createElement('span');
|
201 | measureElement.id = 'measure';
|
202 | measureElement.style.fontFamily = defaultStyle.fontFamily;
|
203 | measureElement.style.fontSize = defaultStyle.fontSize;
|
204 | measureElement.style.visibility = defaultStyle.visibility;
|
205 | document.body.appendChild(measureElement);
|
206 | }
|
207 | const measureStyle = measureElement.style;
|
208 |
|
209 | for (let i = 0; i < measureStyle.length; i++) {
|
210 | const property = measureStyle[i];
|
211 | measureStyle.setProperty(property, defaultStyle.getPropertyValue(property));
|
212 | }
|
213 |
|
214 | if (style) {
|
215 | for (const [key, value] of Object.entries(style)) {
|
216 | measureStyle.setProperty(key, value as string);
|
217 | }
|
218 | }
|
219 | return measureElement;
|
220 | }
|