summaryrefslogtreecommitdiff
path: root/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts')
-rw-r--r--packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts297
1 files changed, 297 insertions, 0 deletions
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts b/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts
new file mode 100644
index 000000000000..80e075c9e070
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts
@@ -0,0 +1,297 @@
+// Copyright 2022 Google LLC
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+// http://www.apache.org/licenses/LICENSE-2.0
+
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+type IElementComment =
+ | { commentNode: undefined; textContent: undefined; hidden: undefined }
+ | { commentNode: Node; textContent: string; hidden: boolean };
+
+interface ITag {
+ attrs?: Record<string, string | number>;
+ tagName: string;
+}
+
+export interface INewTag extends ITag {
+ content?: string | number;
+ comment?: string;
+}
+
+export type IUpdateTag = Partial<Omit<INewTag, 'tagName'>>;
+
+export default class DOM {
+ static addEntry(containerElement: Element, tagOptions: INewTag) {
+ const doc = containerElement.ownerDocument;
+ const exists = this.alreadyHasEntry(containerElement, tagOptions);
+
+ if (exists) {
+ console.log('Ignored adding entry already available: ', exists.outerHTML);
+ return;
+ }
+
+ let insertPoint: Node | null = containerElement.lastElementChild; //.childNodes[containerElement.childNodes.length - 1];
+
+ if (!insertPoint) {
+ console.log('Ignored adding entry in empity parent: ', containerElement.outerHTML);
+ return;
+ }
+
+ const { attrs, comment, content, tagName } = tagOptions;
+
+ if (comment) {
+ const commentNode = doc.createComment(comment);
+ this.insertAfterIdented(commentNode, insertPoint);
+ insertPoint = commentNode;
+ }
+
+ const newEl = doc.createElement(tagName);
+ if (content) newEl.innerHTML = content.toString();
+ if (attrs)
+ Object.entries(attrs).forEach(([attr, value]) =>
+ newEl.setAttribute(attr, value.toString())
+ );
+ this.insertAfterIdented(newEl, insertPoint);
+
+ return true;
+ }
+
+ static insertBeforeIndented(newNode: Node, referenceNode: Node) {
+ const paddingNode = referenceNode.previousSibling;
+ const ownerDoc = referenceNode.ownerDocument;
+ const containerNode = referenceNode.parentNode;
+
+ if (!paddingNode || !ownerDoc || !containerNode) return;
+
+ const currentPadding = paddingNode.textContent || '';
+ const textNode = referenceNode.ownerDocument.createTextNode(currentPadding);
+
+ containerNode.insertBefore(newNode, referenceNode);
+ containerNode.insertBefore(textNode, newNode);
+ }
+
+ static insertAfterIdented(newNode: Node, referenceNode: Node) {
+ const paddingNode = referenceNode.previousSibling;
+ const ownerDoc = referenceNode.ownerDocument;
+ const containerNode = referenceNode.parentNode;
+
+ if (!paddingNode || !ownerDoc || !containerNode) return;
+
+ const currentPadding = paddingNode.textContent || '';
+ const textNode = ownerDoc.createTextNode(currentPadding);
+
+ containerNode.insertBefore(newNode, referenceNode.nextSibling);
+ containerNode.insertBefore(textNode, newNode);
+ }
+
+ static getElementComment(el: Element): IElementComment {
+ const commentNode = el.previousSibling?.previousSibling;
+
+ const out = { commentNode: undefined, textContent: undefined, hidden: undefined };
+
+ if (!commentNode) return out;
+
+ const textContent = commentNode.textContent || '';
+ const hidden = textContent.substring(textContent.length - 6) == '@hide ';
+
+ if (!(commentNode && commentNode.nodeName == '#comment')) return out;
+
+ return { commentNode, textContent, hidden: hidden };
+ }
+
+ static duplicateEntryWithChange(
+ templateElement: Element,
+ options: Omit<IUpdateTag, 'content'>
+ ) {
+ const exists = this.futureEntryAlreadyExist(templateElement, options);
+ if (exists) {
+ console.log('Ignored duplicating entry already available: ', exists.outerHTML);
+ return;
+ }
+
+ const { commentNode } = this.getElementComment(templateElement);
+ let insertPoint: Node = templateElement;
+
+ if (commentNode) {
+ const newComment = commentNode.cloneNode();
+ this.insertAfterIdented(newComment, insertPoint);
+ insertPoint = newComment;
+ }
+
+ const newEl = templateElement.cloneNode(true) as Element;
+ this.insertAfterIdented(newEl, insertPoint);
+
+ this.updateElement(newEl, options);
+ return true;
+ }
+
+ static replaceStringInAttributeValueOnQueried(
+ root: Element,
+ query: string,
+ attrArray: string[],
+ replaceMap: Map<string, string>
+ ): boolean {
+ let updated = false;
+ const queried = [...Array.from(root.querySelectorAll(query)), root];
+
+ queried.forEach((el) => {
+ attrArray.forEach((attr) => {
+ if (el.hasAttribute(attr)) {
+ const currentAttrValue = el.getAttribute(attr);
+
+ if (!currentAttrValue) return;
+
+ [...replaceMap.entries()].some(([oldStr, newStr]) => {
+ if (
+ currentAttrValue.length >= oldStr.length &&
+ currentAttrValue.indexOf(oldStr) ==
+ currentAttrValue.length - oldStr.length
+ ) {
+ el.setAttribute(attr, currentAttrValue.replace(oldStr, newStr));
+ updated = true;
+ return true;
+ }
+ return false;
+ });
+ }
+ });
+ });
+
+ return updated;
+ }
+
+ static updateElement(el: Element, updateOptions: IUpdateTag) {
+ const exists = this.futureEntryAlreadyExist(el, updateOptions);
+ if (exists) {
+ console.log('Ignored updating entry already available: ', exists.outerHTML);
+ return;
+ }
+
+ const { comment, attrs, content } = updateOptions;
+
+ if (comment) {
+ const { commentNode } = this.getElementComment(el);
+ if (commentNode) {
+ commentNode.textContent = comment;
+ }
+ }
+
+ if (attrs) {
+ for (const attr in attrs) {
+ const value = attrs[attr];
+
+ if (value != undefined) {
+ el.setAttribute(attr, `${value}`);
+ } else {
+ el.removeAttribute(attr);
+ }
+ }
+ }
+
+ if (content != undefined) {
+ el.innerHTML = `${content}`;
+ }
+
+ return true;
+ }
+
+ static elementToOptions(el: Element): ITag {
+ return {
+ attrs: this.getAllElementAttributes(el),
+ tagName: el.tagName,
+ };
+ }
+
+ static getAllElementAttributes(el: Element): Record<string, string> {
+ return el
+ .getAttributeNames()
+ .reduce(
+ (acc, attr) => ({ ...acc, [attr]: el.getAttribute(attr) || '' }),
+ {} as Record<string, string>
+ );
+ }
+
+ static futureEntryAlreadyExist(el: Element, updateOptions: IUpdateTag) {
+ const currentElOptions = this.elementToOptions(el);
+
+ if (!el.parentElement) {
+ console.log('Checked el has no parent');
+ process.exit();
+ }
+
+ return this.alreadyHasEntry(el.parentElement, {
+ ...currentElOptions,
+ ...updateOptions,
+ attrs: { ...currentElOptions.attrs, ...updateOptions.attrs },
+ });
+ }
+
+ static alreadyHasEntry(
+ containerElement: Element,
+ { attrs, tagName }: Pick<INewTag, 'attrs' | 'tagName'>
+ ) {
+ const qAttrs = attrs
+ ? Object.entries(attrs)
+ .map(([a, v]) => `[${a}="${v}"]`)
+ .join('')
+ : '';
+
+ return containerElement.querySelector(tagName + qAttrs);
+ }
+
+ static replaceContentTextOnQueried(
+ root: Element,
+ query: string,
+ replacePairs: Array<[string, string]>
+ ) {
+ let updated = false;
+ let queried = Array.from(root.querySelectorAll(query));
+
+ if (queried.length == 0) queried = [...Array.from(root.querySelectorAll(query)), root];
+
+ queried.forEach((el) => {
+ replacePairs.forEach(([oldStr, newStr]) => {
+ if (el.innerHTML == oldStr) {
+ el.innerHTML = newStr;
+ updated = true;
+ }
+ });
+ });
+
+ return updated;
+ }
+
+ static XMLDocToString(doc: XMLDocument) {
+ let str = '';
+
+ doc.childNodes.forEach((node) => {
+ switch (node.nodeType) {
+ case 8: // comment
+ str += `<!--${node.nodeValue}-->\n`;
+ break;
+
+ case 3: // text
+ str += node.textContent;
+ break;
+
+ case 1: // element
+ str += (node as Element).outerHTML;
+ break;
+
+ default:
+ console.log('Unhandled node type: ' + node.nodeType);
+ break;
+ }
+ });
+
+ return str;
+ }
+}