async next() {
      const rawResult = await iter.next();
      if (rawResult.value) {
        rawResult.value.fileSystemItems = rawResult.value.containerItems.map(
          (val: ContainerItem): FileSystemItem => {
            return {
              ...val,
              versionId: val.version,
              properties: {
                ...val.properties,
                publicAccess: toPublicAccessType(val.properties.publicAccess)
              }
            };
          }
        );
      }
      return rawResult as any;
    }


[Symbol.asyncIterator]() {
      return this;
    }


async next(): Promise<{ done?: boolean; value: FileSystemItem }> {
      const rawResult = await iter.next();
      const result = rawResult as { done?: boolean; value: FileSystemItem };
      if (result.value) {
        result.value.properties.publicAccess = toPublicAccessType(
          rawResult.value.properties.publicAccess
        );
        result.value.versionId = rawResult.value.version;
      }
      return result;
    }


[Symbol.asyncIterator](): PagedAsyncIterableIterator<
      FileSystemItem,
      ServiceListFileSystemsSegmentResponse
    > {
      return this;
    }


byPage(
      settings: PageSettings = {}
    ): AsyncIterableIterator<ServiceListFileSystemsSegmentResponse> {
      return toFileSystemAsyncIterableIterator(iter.byPage(settings));
    }


export interface Page {
    category: string;
    type: string;
    difficulty: string;
    question: string;
    correct_answer: string;
    incorrect_answers: string[];
}


@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class ProductsRoutingModule {}


(_, j) => {
        displacements[j] = { x: 0, y: 0 };
      }


(n, j) => {
        if (!isNumber(n.x) || !isNumber(n.y)) return;
        n.x += displacements[j].x * velocityDecay;
        n.y += displacements[j].y * velocityDecay;
      }


(n) => {
      if (!isNumber(n.x) || !isNumber(n.y)) return;
      meanCenter[0] += n.x;
      meanCenter[1] += n.y;
    }


(n, j) => {
      if (!isNumber(n.x) || !isNumber(n.y)) return;
      n.x += centerOffset[0];
      n.y += centerOffset[1];
    }


(combo) => {
      const mapped = comboMap[combo.id];
      if (mapped && mapped.empty) {
        combo.x = mapped.cx || combo.x;
        combo.y = mapped.cy || combo.y;
      }
    }


(node, i) => {
      nodeMap[node.id] = node;
      nodeIdxMap[node.id] = i;
    }


(combo) => {
      oriComboMap[combo.id] = combo;
    }


() => nodeSpacing


() => 0


(d) => {
        if (d.size) {
          if (isArray(d.size)) {
            const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
            return res / 2;
          }
          return d.size / 2;
        }
        return 10;
      }


(d) => {
        return nodeSize(d);
      }


(d) => radius


() => comboSpacing


() => comboPadding


() => Math.max.apply(null, comboPadding)


(d) => {
        return linkDistance;
      }


(d) => {
        return edgeStrength;
      }


(d) => {
        return nodeStrength;
      }


(node, i) => {
      if (node.comboId) {
        const combo = comboMap[node.comboId];
        node.x = combo.cx + 100 / (i + 1);
        node.y = combo.cy + 100 / (i + 1);
      } else {
        node.x = 100 / (i + 1);
        node.y = 100 / (i + 1);
      }
    }


(ctree) => {
      const treeChildren = [];
      traverseTreeUp<ComboTree>(ctree, (treeNode) => {
        if (treeNode.itemType === 'node') return true; // skip it
        if (!oriComboMap[treeNode.id]) return true; // means it is hidden, skip it
        if (comboMap[treeNode.id] === undefined) {
          const combo = {
            name: treeNode.id,
            cx: 0,
            cy: 0,
            count: 0,
            depth: self.oriComboMap[treeNode.id].depth,
            children: [],
          };
          comboMap[treeNode.id] = combo;
        }
        const children = treeNode.children;
        if (children) {
          children.forEach((child) => {
            if (!comboMap[child.id] && !nodeMap[child.id]) return true; // means it is hidden
            treeChildren.push(child);
          });
        }
        const c = comboMap[treeNode.id];
        c.cx = 0;
        c.cy = 0;

        // In order to layout the empty combo, add a virtual node to it
        if (treeChildren.length === 0) {
          c.empty = true;
          const oriCombo = oriComboMap[treeNode.id];
          const idx = Object.keys(nodeMap).length;
          const virtualNodeId = `${treeNode.id}-visual-child-${idx}`;
          const vnode = {
            id: virtualNodeId,
            x: oriCombo.x,
            y: oriCombo.y,
            depth: c.depth + 1,
            itemType: 'node',
          };
          self.nodes.push(vnode);
          nodeMap[virtualNodeId] = vnode;
          nodeIdxMap[virtualNodeId] = idx;
          c.cx = oriCombo.x;
          c.cy = oriCombo.y;
          treeChildren.push(vnode);
        }

        treeChildren.forEach((child) => {
          c.count++;
          if (child.itemType !== 'node') {
            const childCombo = comboMap[child.id];
            if (isNumber(childCombo.cx)) c.cx += childCombo.cx;
            if (isNumber(childCombo.cy)) c.cy += childCombo.cy;
            return;
          }
          const node = nodeMap[child.id];
          // means the node is hidden, skip it
          if (!node) return;

          if (isNumber(node.x)) {
            c.cx += node.x;
          }
          if (isNumber(node.y)) {
            c.cy += node.y;
          }
        });
        c.cx /= c.count;
        c.cy /= c.count;

        c.children = treeChildren;

        return true;
      });
    }


(treeNode) => {
        if (treeNode.itemType === 'node') return true; // skip it
        if (!oriComboMap[treeNode.id]) return true; // means it is hidden, skip it
        if (comboMap[treeNode.id] === undefined) {
          const combo = {
            name: treeNode.id,
            cx: 0,
            cy: 0,
            count: 0,
            depth: self.oriComboMap[treeNode.id].depth,
            children: [],
          };
          comboMap[treeNode.id] = combo;
        }
        const children = treeNode.children;
        if (children) {
          children.forEach((child) => {
            if (!comboMap[child.id] && !nodeMap[child.id]) return true; // means it is hidden
            treeChildren.push(child);
          });
        }
        const c = comboMap[treeNode.id];
        c.cx = 0;
        c.cy = 0;

        // In order to layout the empty combo, add a virtual node to it
        if (treeChildren.length === 0) {
          c.empty = true;
          const oriCombo = oriComboMap[treeNode.id];
          const idx = Object.keys(nodeMap).length;
          const virtualNodeId = `${treeNode.id}-visual-child-${idx}`;
          const vnode = {
            id: virtualNodeId,
            x: oriCombo.x,
            y: oriCombo.y,
            depth: c.depth + 1,
            itemType: 'node',
          };
          self.nodes.push(vnode);
          nodeMap[virtualNodeId] = vnode;
          nodeIdxMap[virtualNodeId] = idx;
          c.cx = oriCombo.x;
          c.cy = oriCombo.y;
          treeChildren.push(vnode);
        }

        treeChildren.forEach((child) => {
          c.count++;
          if (child.itemType !== 'node') {
            const childCombo = comboMap[child.id];
            if (isNumber(childCombo.cx)) c.cx += childCombo.cx;
            if (isNumber(childCombo.cy)) c.cy += childCombo.cy;
            return;
          }
          const node = nodeMap[child.id];
          // means the node is hidden, skip it
          if (!node) return;

          if (isNumber(node.x)) {
            c.cx += node.x;
          }
          if (isNumber(node.y)) {
            c.cy += node.y;
          }
        });
        c.cx /= c.count;
        c.cy /= c.count;

        c.children = treeChildren;

        return true;
      }


(child) => {
            if (!comboMap[child.id] && !nodeMap[child.id]) return true; // means it is hidden
            treeChildren.push(child);
          }


(child) => {
          c.count++;
          if (child.itemType !== 'node') {
            const childCombo = comboMap[child.id];
            if (isNumber(childCombo.cx)) c.cx += childCombo.cx;
            if (isNumber(childCombo.cy)) c.cy += childCombo.cy;
            return;
          }
          const node = nodeMap[child.id];
          // means the node is hidden, skip it
          if (!node) return;

          if (isNumber(node.x)) {
            c.cx += node.x;
          }
          if (isNumber(node.y)) {
            c.cy += node.y;
          }
        }


(ctree) => {
      traverseTreeUp<ComboTree>(ctree, (treeNode) => {
        if (treeNode.itemType === 'node') return true; // skip it
        const combo = comboMap[treeNode.id];
        // means the combo is hidden, skip it
        if (!combo) return true;
        const c = comboMap[treeNode.id];

        // higher depth the combo, larger the gravity
        const gravityScale = (c.depth + 1) * 0.5;
        // apply combo center force for all the descend nodes in this combo
        // and update the center position and count for this combo
        const comboX = c.cx;
        const comboY = c.cy;
        c.cx = 0;
        c.cy = 0;
        c.children.forEach((child) => {
          if (child.itemType !== 'node') {
            const childCombo = comboMap[child.id];
            if (childCombo && isNumber(childCombo.cx)) c.cx += childCombo.cx;
            if (childCombo && isNumber(childCombo.cy)) c.cy += childCombo.cy;
            return;
          }
          const node = nodeMap[child.id];
          const vecX = node.x - comboX || 0.005;
          const vecY = node.y - comboY || 0.005;
          const l = Math.sqrt(vecX * vecX + vecY * vecY);
          const childIdx = nodeIdxMap[node.id];
          const params = ((comboGravity * alpha) / l) * gravityScale;
          displacements[childIdx].x -= vecX * params;
          displacements[childIdx].y -= vecY * params;

          if (isNumber(node.x)) c.cx += node.x;
          if (isNumber(node.y)) c.cy += node.y;
        });
        c.cx /= c.count;
        c.cy /= c.count;
        return true;
      });
    }


(treeNode) => {
        if (treeNode.itemType === 'node') return true; // skip it
        const combo = comboMap[treeNode.id];
        // means the combo is hidden, skip it
        if (!combo) return true;
        const c = comboMap[treeNode.id];

        // higher depth the combo, larger the gravity
        const gravityScale = (c.depth + 1) * 0.5;
        // apply combo center force for all the descend nodes in this combo
        // and update the center position and count for this combo
        const comboX = c.cx;
        const comboY = c.cy;
        c.cx = 0;
        c.cy = 0;
        c.children.forEach((child) => {
          if (child.itemType !== 'node') {
            const childCombo = comboMap[child.id];
            if (childCombo && isNumber(childCombo.cx)) c.cx += childCombo.cx;
            if (childCombo && isNumber(childCombo.cy)) c.cy += childCombo.cy;
            return;
          }
          const node = nodeMap[child.id];
          const vecX = node.x - comboX || 0.005;
          const vecY = node.y - comboY || 0.005;
          const l = Math.sqrt(vecX * vecX + vecY * vecY);
          const childIdx = nodeIdxMap[node.id];
          const params = ((comboGravity * alpha) / l) * gravityScale;
          displacements[childIdx].x -= vecX * params;
          displacements[childIdx].y -= vecY * params;

          if (isNumber(node.x)) c.cx += node.x;
          if (isNumber(node.y)) c.cy += node.y;
        });
        c.cx /= c.count;
        c.cy /= c.count;
        return true;
      }


(child) => {
          if (child.itemType !== 'node') {
            const childCombo = comboMap[child.id];
            if (childCombo && isNumber(childCombo.cx)) c.cx += childCombo.cx;
            if (childCombo && isNumber(childCombo.cy)) c.cy += childCombo.cy;
            return;
          }
          const node = nodeMap[child.id];
          const vecX = node.x - comboX || 0.005;
          const vecY = node.y - comboY || 0.005;
          const l = Math.sqrt(vecX * vecX + vecY * vecY);
          const childIdx = nodeIdxMap[node.id];
          const params = ((comboGravity * alpha) / l) * gravityScale;
          displacements[childIdx].x -= vecX * params;
          displacements[childIdx].y -= vecY * params;

          if (isNumber(node.x)) c.cx += node.x;
          if (isNumber(node.y)) c.cy += node.y;
        }


(v, i) => {
      nodes.forEach((u, j) => {
        if (i < j) return;
        const vx = v.x - u.x || 0.005;
        const vy = v.y - u.y || 0.005;
        let vl2 = vx * vx + vy * vy;
        const vl = Math.sqrt(vl2);
        if (vl2 < 1) vl2 = vl;
        vecMap[`${v.id}-${u.id}`] = { vx, vy, vl2, vl };
        vecMap[`${u.id}-${v.id}`] = { vx: -vx, vy: -vy, vl2, vl };
      });
    }


(u, j) => {
        if (i < j) return;
        const vx = v.x - u.x || 0.005;
        const vy = v.y - u.y || 0.005;
        let vl2 = vx * vx + vy * vy;
        const vl = Math.sqrt(vl2);
        if (vl2 < 1) vl2 = vl;
        vecMap[`${v.id}-${u.id}`] = { vx, vy, vl2, vl };
        vecMap[`${u.id}-${v.id}`] = { vx: -vx, vy: -vy, vl2, vl };
      }


(ctree) => {
      const treeChildren = [];
      traverseTreeUp<ComboTree>(ctree, (treeNode) => {
        if (treeNode.itemType === 'node') return true; // skip it
        const c = comboMap[treeNode.id];
        // means the combo is hidden, skip it
        if (!c) return;
        const children = treeNode.children;
        if (children) {
          children.forEach((child) => {
            // means the combo is hidden.
            if (!comboMap[child.id] && !nodeMap[child.id]) return;
            treeChildren.push(child);
          });
        }

        c.minX = Infinity;
        c.minY = Infinity;
        c.maxX = -Infinity;
        c.maxY = -Infinity;
        treeChildren.forEach((child) => {
          if (child.itemType !== 'node') return true; // skip it
          const node = nodeMap[child.id];
          if (!node) return true; // means it is hidden
          const r = nodeSize(node);
          const nodeMinX = node.x - r;
          const nodeMinY = node.y - r;
          const nodeMaxX = node.x + r;
          const nodeMaxY = node.y + r;
          if (c.minX > nodeMinX) c.minX = nodeMinX;
          if (c.minY > nodeMinY) c.minY = nodeMinY;
          if (c.maxX < nodeMaxX) c.maxX = nodeMaxX;
          if (c.maxY < nodeMaxY) c.maxY = nodeMaxY;
        });
        let minSize = self.oriComboMap[treeNode.id].size || Global.defaultCombo.size;
        if (isArray(minSize)) minSize = minSize[0];
        const maxLength = Math.max(c.maxX - c.minX, c.maxY - c.minY, minSize as number);
        c.r = maxLength / 2 + comboSpacing(c) / 2 + comboPadding(c);

        return true;
      });
    }


(treeNode) => {
        if (treeNode.itemType === 'node') return true; // skip it
        const c = comboMap[treeNode.id];
        // means the combo is hidden, skip it
        if (!c) return;
        const children = treeNode.children;
        if (children) {
          children.forEach((child) => {
            // means the combo is hidden.
            if (!comboMap[child.id] && !nodeMap[child.id]) return;
            treeChildren.push(child);
          });
        }

        c.minX = Infinity;
        c.minY = Infinity;
        c.maxX = -Infinity;
        c.maxY = -Infinity;
        treeChildren.forEach((child) => {
          if (child.itemType !== 'node') return true; // skip it
          const node = nodeMap[child.id];
          if (!node) return true; // means it is hidden
          const r = nodeSize(node);
          const nodeMinX = node.x - r;
          const nodeMinY = node.y - r;
          const nodeMaxX = node.x + r;
          const nodeMaxY = node.y + r;
          if (c.minX > nodeMinX) c.minX = nodeMinX;
          if (c.minY > nodeMinY) c.minY = nodeMinY;
          if (c.maxX < nodeMaxX) c.maxX = nodeMaxX;
          if (c.maxY < nodeMaxY) c.maxY = nodeMaxY;
        });
        let minSize = self.oriComboMap[treeNode.id].size || Global.defaultCombo.size;
        if (isArray(minSize)) minSize = minSize[0];
        const maxLength = Math.max(c.maxX - c.minX, c.maxY - c.minY, minSize as number);
        c.r = maxLength / 2 + comboSpacing(c) / 2 + comboPadding(c);

        return true;
      }


(child) => {
            // means the combo is hidden.
            if (!comboMap[child.id] && !nodeMap[child.id]) return;
            treeChildren.push(child);
          }


(child) => {
          if (child.itemType !== 'node') return true; // skip it
          const node = nodeMap[child.id];
          if (!node) return true; // means it is hidden
          const r = nodeSize(node);
          const nodeMinX = node.x - r;
          const nodeMinY = node.y - r;
          const nodeMaxX = node.x + r;
          const nodeMaxY = node.y + r;
          if (c.minX > nodeMinX) c.minX = nodeMinX;
          if (c.minY > nodeMinY) c.minY = nodeMinY;
          if (c.maxX < nodeMaxX) c.maxX = nodeMaxX;
          if (c.maxY < nodeMaxY) c.maxY = nodeMaxY;
        }


(treeNode) => {
      if (!comboMap[treeNode.id] && !nodeMap[treeNode.id] && treeNode.id !== 'comboTreeRoot')
        return; // means it is hidden
      const children = treeNode.children;
      // 同个子树下的子 combo 间两两对比
      if (children && children.length > 1) {
        children.forEach((v, i) => {
          if (v.itemType === 'node') return; // skip it
          const cv = comboMap[v.id];
          if (!cv) return; // means it is hidden, skip it
          children.forEach((u, j) => {
            if (i <= j) return;
            if (u.itemType === 'node') return; // skip it
            const cu = comboMap[u.id];
            if (!cu) return; // means it is hidden, skip it
            const vx = cv.cx - cu.cx || 0.005;
            const vy = cv.cy - cu.cy || 0.005;
            const l = vx * vx + vy * vy;
            const rv = cv.r;
            const ru = cu.r;
            const r = rv + ru;
            const ru2 = ru * ru;
            const rv2 = rv * rv;
            // overlapping
            if (l < r * r) {
              const vnodes = v.children;
              if (!vnodes || vnodes.length === 0) return; // skip it
              const unodes = u.children;
              if (!unodes || unodes.length === 0) return; // skip it
              const sqrtl = Math.sqrt(l);
              const ll = ((r - sqrtl) / sqrtl) * comboCollideStrength;
              const xl = vx * ll;
              const yl = vy * ll;
              const rratio = ru2 / (rv2 + ru2);
              const irratio = 1 - rratio;
              // 两兄弟 combo 的子节点上施加斥力
              vnodes.forEach((vn) => {
                if (vn.itemType !== 'node') return; // skip it
                if (!nodeMap[vn.id]) return; // means it is hidden, skip it
                const vindex = nodeIdxMap[vn.id];
                unodes.forEach((un) => {
                  if (un.itemType !== 'node') return;
                  if (!nodeMap[un.id]) return; // means it is hidden, skip it
                  const uindex = nodeIdxMap[un.id];
                  displacements[vindex].x += xl * rratio;
                  displacements[vindex].y += yl * rratio;
                  displacements[uindex].x -= xl * irratio;
                  displacements[uindex].y -= yl * irratio;
                });
              });
            }
          });
        });
      }
      return true;
    }


(v, i) => {
          if (v.itemType === 'node') return; // skip it
          const cv = comboMap[v.id];
          if (!cv) return; // means it is hidden, skip it
          children.forEach((u, j) => {
            if (i <= j) return;
            if (u.itemType === 'node') return; // skip it
            const cu = comboMap[u.id];
            if (!cu) return; // means it is hidden, skip it
            const vx = cv.cx - cu.cx || 0.005;
            const vy = cv.cy - cu.cy || 0.005;
            const l = vx * vx + vy * vy;
            const rv = cv.r;
            const ru = cu.r;
            const r = rv + ru;
            const ru2 = ru * ru;
            const rv2 = rv * rv;
            // overlapping
            if (l < r * r) {
              const vnodes = v.children;
              if (!vnodes || vnodes.length === 0) return; // skip it
              const unodes = u.children;
              if (!unodes || unodes.length === 0) return; // skip it
              const sqrtl = Math.sqrt(l);
              const ll = ((r - sqrtl) / sqrtl) * comboCollideStrength;
              const xl = vx * ll;
              const yl = vy * ll;
              const rratio = ru2 / (rv2 + ru2);
              const irratio = 1 - rratio;
              // 两兄弟 combo 的子节点上施加斥力
              vnodes.forEach((vn) => {
                if (vn.itemType !== 'node') return; // skip it
                if (!nodeMap[vn.id]) return; // means it is hidden, skip it
                const vindex = nodeIdxMap[vn.id];
                unodes.forEach((un) => {
                  if (un.itemType !== 'node') return;
                  if (!nodeMap[un.id]) return; // means it is hidden, skip it
                  const uindex = nodeIdxMap[un.id];
                  displacements[vindex].x += xl * rratio;
                  displacements[vindex].y += yl * rratio;
                  displacements[uindex].x -= xl * irratio;
                  displacements[uindex].y -= yl * irratio;
                });
              });
            }
          });
        }


(u, j) => {
            if (i <= j) return;
            if (u.itemType === 'node') return; // skip it
            const cu = comboMap[u.id];
            if (!cu) return; // means it is hidden, skip it
            const vx = cv.cx - cu.cx || 0.005;
            const vy = cv.cy - cu.cy || 0.005;
            const l = vx * vx + vy * vy;
            const rv = cv.r;
            const ru = cu.r;
            const r = rv + ru;
            const ru2 = ru * ru;
            const rv2 = rv * rv;
            // overlapping
            if (l < r * r) {
              const vnodes = v.children;
              if (!vnodes || vnodes.length === 0) return; // skip it
              const unodes = u.children;
              if (!unodes || unodes.length === 0) return; // skip it
              const sqrtl = Math.sqrt(l);
              const ll = ((r - sqrtl) / sqrtl) * comboCollideStrength;
              const xl = vx * ll;
              const yl = vy * ll;
              const rratio = ru2 / (rv2 + ru2);
              const irratio = 1 - rratio;
              // 两兄弟 combo 的子节点上施加斥力
              vnodes.forEach((vn) => {
                if (vn.itemType !== 'node') return; // skip it
                if (!nodeMap[vn.id]) return; // means it is hidden, skip it
                const vindex = nodeIdxMap[vn.id];
                unodes.forEach((un) => {
                  if (un.itemType !== 'node') return;
                  if (!nodeMap[un.id]) return; // means it is hidden, skip it
                  const uindex = nodeIdxMap[un.id];
                  displacements[vindex].x += xl * rratio;
                  displacements[vindex].y += yl * rratio;
                  displacements[uindex].x -= xl * irratio;
                  displacements[uindex].y -= yl * irratio;
                });
              });
            }
          }


(vn) => {
                if (vn.itemType !== 'node') return; // skip it
                if (!nodeMap[vn.id]) return; // means it is hidden, skip it
                const vindex = nodeIdxMap[vn.id];
                unodes.forEach((un) => {
                  if (un.itemType !== 'node') return;
                  if (!nodeMap[un.id]) return; // means it is hidden, skip it
                  const uindex = nodeIdxMap[un.id];
                  displacements[vindex].x += xl * rratio;
                  displacements[vindex].y += yl * rratio;
                  displacements[uindex].x -= xl * irratio;
                  displacements[uindex].y -= yl * irratio;
                });
              }


(un) => {
                  if (un.itemType !== 'node') return;
                  if (!nodeMap[un.id]) return; // means it is hidden, skip it
                  const uindex = nodeIdxMap[un.id];
                  displacements[vindex].x += xl * rratio;
                  displacements[vindex].y += yl * rratio;
                  displacements[uindex].x -= xl * irratio;
                  displacements[uindex].y -= yl * irratio;
                }


(v, i) => {
      if (!v.x || !v.y) return;

      // center gravity
      if (center) {
        const gravity = self.gravity;
        const vecX = v.x - center[0] || 0.005;
        const vecY = v.y - center[1] || 0.005;
        const l = Math.sqrt(vecX * vecX + vecY * vecY);
        displacements[i].x -= (vecX * gravity * alpha) / l;
        displacements[i].y -= (vecY * gravity * alpha) / l;
      }

      nodes.forEach((u, j) => {
        if (i === j) {
          return;
        }
        if (!u.x || !u.y) return;
        const { vl2, vl } = vecMap[`${v.id}-${u.id}`];
        if (vl > max) return;

        const { vx, vy } = vecMap[`${v.id}-${u.id}`];

        let depthDiff = Math.abs(u.depth - v.depth) + 1 || 1;
        if (u.comboId !== v.comboId) depthDiff++;
        const depthParam = depthDiff ? scale ** depthDiff : 1;

        const params = ((nodeStrength(u) * alpha) / vl2) * depthParam;
        displacements[i].x += vx * params;
        displacements[i].y += vy * params;

        // prevent node overlappings
        if (i < j && preventNodeOverlap) {
          const ri = nodeSizeFunc(v) + nodeSpacingFunc(v);
          const rj = nodeSizeFunc(u) + nodeSpacingFunc(u);
          const r = ri + rj;
          if (vl2 < r * r) {
            const ll = ((r - vl) / vl) * nodeCollideStrength;
            const rj2 = rj * rj;
            let rratio = rj2 / (ri * ri + rj2);
            const xl = vx * ll;
            const yl = vy * ll;
            displacements[i].x += xl * rratio;
            displacements[i].y += yl * rratio;
            rratio = 1 - rratio;
            displacements[j].x -= xl * rratio;
            displacements[j].y -= yl * rratio;
          }
        }
      });
    }


(u, j) => {
        if (i === j) {
          return;
        }
        if (!u.x || !u.y) return;
        const { vl2, vl } = vecMap[`${v.id}-${u.id}`];
        if (vl > max) return;

        const { vx, vy } = vecMap[`${v.id}-${u.id}`];

        let depthDiff = Math.abs(u.depth - v.depth) + 1 || 1;
        if (u.comboId !== v.comboId) depthDiff++;
        const depthParam = depthDiff ? scale ** depthDiff : 1;

        const params = ((nodeStrength(u) * alpha) / vl2) * depthParam;
        displacements[i].x += vx * params;
        displacements[i].y += vy * params;

        // prevent node overlappings
        if (i < j && preventNodeOverlap) {
          const ri = nodeSizeFunc(v) + nodeSpacingFunc(v);
          const rj = nodeSizeFunc(u) + nodeSpacingFunc(u);
          const r = ri + rj;
          if (vl2 < r * r) {
            const ll = ((r - vl) / vl) * nodeCollideStrength;
            const rj2 = rj * rj;
            let rratio = rj2 / (ri * ri + rj2);
            const xl = vx * ll;
            const yl = vy * ll;
            displacements[i].x += xl * rratio;
            displacements[i].y += yl * rratio;
            rratio = 1 - rratio;
            displacements[j].x -= xl * rratio;
            displacements[j].y -= yl * rratio;
          }
        }
      }


(e, i) => {
      if (!e.source || !e.target || e.source === e.target) return;
      const uIndex = self.nodeIdxMap[e.source];
      const vIndex = self.nodeIdxMap[e.target];
      const u = self.nodeMap[e.source];
      const v = self.nodeMap[e.target];

      let depthDiff = Math.abs(u.depth - v.depth);
      if (u.comboId === v.comboId) {
        depthDiff = depthDiff / 2;
      }
      let depthParam = depthDiff ? scale ** depthDiff : 1;
      if (u.comboId !== v.comboId && depthParam === 1) {
        depthParam = scale / 2;
      } else if (u.comboId === v.comboId) {
        depthParam = 2;
      }

      if (!isNumber(v.x) || !isNumber(u.x) || !isNumber(v.y) || !isNumber(u.y)) return;
      const { vl, vx, vy } = vecMap[`${e.target}-${e.source}`];
      const l = ((vl - linkDistance(e)) / vl) * alpha * edgeStrength(e) * depthParam;
      const vecX = vx * l;
      const vecY = vy * l;

      const b = bias[i];
      displacements[vIndex].x -= vecX * b;
      displacements[vIndex].y -= vecY * b;
      displacements[uIndex].x += vecX * (1 - b);
      displacements[uIndex].y += vecY * (1 - b);
    }


type Node = NodeConfig & {
  depth: number;
};


type Edge = EdgeConfig;


type ElementMap = {
  [key: string]: Node | ComboConfig;
};


type ComboMap = {
  [key: string]: {
    name: string | number;
    cx: number;
    cy: number;
    count: number;
    depth: number;
    children: any[];
    empty?: boolean;
  };
};


public getDefaultCfg() {
    return {
      maxIteration: 100,
      center: [0, 0],
      gravity: 10,
      speed: 1,
      comboGravity: 30,
      preventOverlap: false,
      preventComboOverlap: true,
      preventNodeOverlap: true,
      nodeSpacing: undefined,
      collideStrength: undefined,
      nodeCollideStrength: 0.5,
      comboCollideStrength: 0.5,
      comboSpacing: 20,
      comboPadding: 10,
      edgeStrength: 0.2,
      nodeStrength: 30,
      linkDistance: 10,
    };
  }


/**
   * 执行布局
   */
public execute() {
    const self = this;
    const nodes = self.nodes;
    const center = self.center;
    self.comboTree = {
      id: 'comboTreeRoot',
      depth: -1,
      children: self.comboTrees,
    };

    if (!nodes || nodes.length === 0) {
      return;
    }
    if (nodes.length === 1) {
      nodes[0].x = center[0];
      nodes[0].y = center[1];
      return;
    }

    self.initVals();

    // layout
    self.run();
    self.onLayoutEnd();
  }


public run() {
    const self = this;
    const nodes = self.nodes;
    const maxIteration = self.previousLayouted ? self.maxIteration / 5 : self.maxIteration;
    if (!self.width && typeof window !== 'undefined') {
      self.width = window.innerWidth;
    }
    if (!self.height && typeof window !== 'undefined') {
      self.height = window.innerHeight;
    }
    const center = self.center;
    const velocityDecay = self.velocityDecay;

    // init the positions to make the nodes with same combo gather around the combo
    const comboMap = self.comboMap;
    if (!self.previousLayouted) self.initPos(comboMap);

    // iterate
    for (let i = 0; i < maxIteration; i++) {
      const displacements: Point[] = [];
      nodes.forEach((_, j) => {
        displacements[j] = { x: 0, y: 0 };
      });
      self.applyCalculate(displacements);

      // gravity for combos
      self.applyComboCenterForce(displacements);

      // move
      nodes.forEach((n, j) => {
        if (!isNumber(n.x) || !isNumber(n.y)) return;
        n.x += displacements[j].x * velocityDecay;
        n.y += displacements[j].y * velocityDecay;
      });
      self.alpha += (self.alphaTarget - self.alpha) * self.alphaDecay;
      self.onTick();
    }

    // move to center
    const meanCenter = [0, 0];
    nodes.forEach((n) => {
      if (!isNumber(n.x) || !isNumber(n.y)) return;
      meanCenter[0] += n.x;
      meanCenter[1] += n.y;
    });
    meanCenter[0] /= nodes.length;
    meanCenter[1] /= nodes.length;
    const centerOffset = [center[0] - meanCenter[0], center[1] - meanCenter[1]];
    nodes.forEach((n, j) => {
      if (!isNumber(n.x) || !isNumber(n.y)) return;
      n.x += centerOffset[0];
      n.y += centerOffset[1];
    });

    // arrange the empty combo
    self.combos.forEach((combo) => {
      const mapped = comboMap[combo.id];
      if (mapped && mapped.empty) {
        combo.x = mapped.cx || combo.x;
        combo.y = mapped.cy || combo.y;
      }
    });

    self.previousLayouted = true;
  }


private initVals() {
    const self = this;
    const edges = self.edges;
    const nodes = self.nodes;
    const combos = self.combos;
    const count = {};

    const nodeMap: ElementMap = {};
    const nodeIdxMap: NodeIdxMap = {};
    nodes.forEach((node, i) => {
      nodeMap[node.id] = node;
      nodeIdxMap[node.id] = i;
    });
    self.nodeMap = nodeMap;
    self.nodeIdxMap = nodeIdxMap;

    const oriComboMap: ElementMap = {};
    combos.forEach((combo) => {
      oriComboMap[combo.id] = combo;
    });
    self.oriComboMap = oriComboMap;
    self.comboMap = self.getComboMap();

    const preventOverlap = self.preventOverlap;
    self.preventComboOverlap = self.preventComboOverlap || preventOverlap;
    self.preventNodeOverlap = self.preventNodeOverlap || preventOverlap;

    const collideStrength = self.collideStrength;
    if (collideStrength) {
      self.comboCollideStrength = collideStrength;
      self.nodeCollideStrength = collideStrength;
    }

    // get edge bias
    for (let i = 0; i < edges.length; ++i) {
      if (count[edges[i].source]) count[edges[i].source]++;
      else count[edges[i].source] = 1;
      if (count[edges[i].target]) count[edges[i].target]++;
      else count[edges[i].target] = 1;
    }
    const bias = [];
    for (let i = 0; i < edges.length; ++i) {
      bias[i] = count[edges[i].source] / (count[edges[i].source] + count[edges[i].target]);
    }
    this.bias = bias;

    const nodeSize = self.nodeSize;
    const nodeSpacing = self.nodeSpacing;
    let nodeSizeFunc: (d: any) => number;
    let nodeSpacingFunc: (d: any) => number;

    // nodeSpacing to function
    if (isNumber(nodeSpacing)) {
      nodeSpacingFunc = () => nodeSpacing;
    } else if (isFunction(nodeSpacing)) {
      nodeSpacingFunc = nodeSpacing;
    } else {
      nodeSpacingFunc = () => 0;
    }
    this.nodeSpacing = nodeSpacingFunc;

    // nodeSize to function
    if (!nodeSize) {
      nodeSizeFunc = (d) => {
        if (d.size) {
          if (isArray(d.size)) {
            const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
            return res / 2;
          }
          return d.size / 2;
        }
        return 10;
      };
    } else if (isFunction(nodeSize)) {
      nodeSizeFunc = (d) => {
        return nodeSize(d);
      };
    } else if (isArray(nodeSize)) {
      const larger = nodeSize[0] > nodeSize[1] ? nodeSize[0] : nodeSize[1];
      const radius = larger / 2;
      nodeSizeFunc = (d) => radius;
    } else {
      // number type
      const radius = nodeSize / 2;
      nodeSizeFunc = (d) => radius;
    }
    this.nodeSize = nodeSizeFunc;

    // comboSpacing to function
    const comboSpacing = self.comboSpacing;
    let comboSpacingFunc: (d: any) => number;
    if (isNumber(comboSpacing)) {
      comboSpacingFunc = () => comboSpacing;
    } else if (isFunction(comboSpacing)) {
      comboSpacingFunc = comboSpacing;
    } else {
      // null type
      comboSpacingFunc = () => 0;
    }
    this.comboSpacing = comboSpacingFunc;

    // comboPadding to function
    const comboPadding = self.comboPadding;
    let comboPaddingFunc: (d: any) => number;
    if (isNumber(comboPadding)) {
      comboPaddingFunc = () => comboPadding;
    } else if (isArray(comboPadding)) {
      comboPaddingFunc = () => Math.max.apply(null, comboPadding);
    } else if (isFunction(comboPadding)) {
      comboPaddingFunc = comboPadding;
    } else {
      // null type
      comboPaddingFunc = () => 0;
    }
    this.comboPadding = comboPaddingFunc;

    // linkDistance to function
    let linkDistance = this.linkDistance;
    let linkDistanceFunc;
    if (!linkDistance) {
      linkDistance = 10;
    }
    if (isNumber(linkDistance)) {
      linkDistanceFunc = (d) => {
        return linkDistance;
      };
    } else {
      linkDistanceFunc = linkDistance;
    }
    this.linkDistance = linkDistanceFunc;

    // linkStrength to function
    let edgeStrength = this.edgeStrength;
    let edgeStrengthFunc;
    if (!edgeStrength) {
      edgeStrength = 1;
    }
    if (isNumber(edgeStrength)) {
      edgeStrengthFunc = (d) => {
        return edgeStrength;
      };
    } else {
      edgeStrengthFunc = edgeStrength;
    }
    this.edgeStrength = edgeStrengthFunc;

    // nodeStrength to function
    let nodeStrength = this.nodeStrength;
    let nodeStrengthFunc;
    if (!nodeStrength) {
      nodeStrength = 30;
    }
    if (isNumber(nodeStrength)) {
      nodeStrengthFunc = (d) => {
        return nodeStrength;
      };
    } else {
      nodeStrengthFunc = nodeStrength;
    }
    this.nodeStrength = nodeStrengthFunc;
  }


private initPos(comboMap) {
    const self = this;
    const nodes = self.nodes;
    nodes.forEach((node, i) => {
      if (node.comboId) {
        const combo = comboMap[node.comboId];
        node.x = combo.cx + 100 / (i + 1);
        node.y = combo.cy + 100 / (i + 1);
      } else {
        node.x = 100 / (i + 1);
        node.y = 100 / (i + 1);
      }
    });
  }


private getComboMap() {
    const self = this;
    const nodeMap = self.nodeMap;
    const nodeIdxMap = self.nodeIdxMap;
    const comboTrees = self.comboTrees;
    const oriComboMap = self.oriComboMap;
    const comboMap: ComboMap = {};

    (comboTrees || []).forEach((ctree) => {
      const treeChildren = [];
      traverseTreeUp<ComboTree>(ctree, (treeNode) => {
        if (treeNode.itemType === 'node') return true; // skip it
        if (!oriComboMap[treeNode.id]) return true; // means it is hidden, skip it
        if (comboMap[treeNode.id] === undefined) {
          const combo = {
            name: treeNode.id,
            cx: 0,
            cy: 0,
            count: 0,
            depth: self.oriComboMap[treeNode.id].depth,
            children: [],
          };
          comboMap[treeNode.id] = combo;
        }
        const children = treeNode.children;
        if (children) {
          children.forEach((child) => {
            if (!comboMap[child.id] && !nodeMap[child.id]) return true; // means it is hidden
            treeChildren.push(child);
          });
        }
        const c = comboMap[treeNode.id];
        c.cx = 0;
        c.cy = 0;

        // In order to layout the empty combo, add a virtual node to it
        if (treeChildren.length === 0) {
          c.empty = true;
          const oriCombo = oriComboMap[treeNode.id];
          const idx = Object.keys(nodeMap).length;
          const virtualNodeId = `${treeNode.id}-visual-child-${idx}`;
          const vnode = {
            id: virtualNodeId,
            x: oriCombo.x,
            y: oriCombo.y,
            depth: c.depth + 1,
            itemType: 'node',
          };
          self.nodes.push(vnode);
          nodeMap[virtualNodeId] = vnode;
          nodeIdxMap[virtualNodeId] = idx;
          c.cx = oriCombo.x;
          c.cy = oriCombo.y;
          treeChildren.push(vnode);
        }

        treeChildren.forEach((child) => {
          c.count++;
          if (child.itemType !== 'node') {
            const childCombo = comboMap[child.id];
            if (isNumber(childCombo.cx)) c.cx += childCombo.cx;
            if (isNumber(childCombo.cy)) c.cy += childCombo.cy;
            return;
          }
          const node = nodeMap[child.id];
          // means the node is hidden, skip it
          if (!node) return;

          if (isNumber(node.x)) {
            c.cx += node.x;
          }
          if (isNumber(node.y)) {
            c.cy += node.y;
          }
        });
        c.cx /= c.count;
        c.cy /= c.count;

        c.children = treeChildren;

        return true;
      });
    });

    return comboMap;
  }


private applyComboCenterForce(displacements) {
    const self = this;
    const gravity = self.gravity;
    const comboGravity = self.comboGravity || gravity;
    const alpha = this.alpha;
    const comboTrees = self.comboTrees;
    const nodeIdxMap = self.nodeIdxMap;
    const nodeMap = self.nodeMap;
    const comboMap = self.comboMap;
    (comboTrees || []).forEach((ctree) => {
      traverseTreeUp<ComboTree>(ctree, (treeNode) => {
        if (treeNode.itemType === 'node') return true; // skip it
        const combo = comboMap[treeNode.id];
        // means the combo is hidden, skip it
        if (!combo) return true;
        const c = comboMap[treeNode.id];

        // higher depth the combo, larger the gravity
        const gravityScale = (c.depth + 1) * 0.5;
        // apply combo center force for all the descend nodes in this combo
        // and update the center position and count for this combo
        const comboX = c.cx;
        const comboY = c.cy;
        c.cx = 0;
        c.cy = 0;
        c.children.forEach((child) => {
          if (child.itemType !== 'node') {
            const childCombo = comboMap[child.id];
            if (childCombo && isNumber(childCombo.cx)) c.cx += childCombo.cx;
            if (childCombo && isNumber(childCombo.cy)) c.cy += childCombo.cy;
            return;
          }
          const node = nodeMap[child.id];
          const vecX = node.x - comboX || 0.005;
          const vecY = node.y - comboY || 0.005;
          const l = Math.sqrt(vecX * vecX + vecY * vecY);
          const childIdx = nodeIdxMap[node.id];
          const params = ((comboGravity * alpha) / l) * gravityScale;
          displacements[childIdx].x -= vecX * params;
          displacements[childIdx].y -= vecY * params;

          if (isNumber(node.x)) c.cx += node.x;
          if (isNumber(node.y)) c.cy += node.y;
        });
        c.cx /= c.count;
        c.cy /= c.count;
        return true;
      });
    });
  }


private applyCalculate(displacements: Point[]) {
    const self = this;
    const comboMap = self.comboMap;
    const nodes = self.nodes;
    // store the vx, vy, and distance to reduce dulplicate calculation
    const vecMap = {};
    nodes.forEach((v, i) => {
      nodes.forEach((u, j) => {
        if (i < j) return;
        const vx = v.x - u.x || 0.005;
        const vy = v.y - u.y || 0.005;
        let vl2 = vx * vx + vy * vy;
        const vl = Math.sqrt(vl2);
        if (vl2 < 1) vl2 = vl;
        vecMap[`${v.id}-${u.id}`] = { vx, vy, vl2, vl };
        vecMap[`${u.id}-${v.id}`] = { vx: -vx, vy: -vy, vl2, vl };
      });
    });
    // get the sizes of the combos
    self.updateComboSizes(comboMap);
    self.calRepulsive(displacements, vecMap);
    self.calAttractive(displacements, vecMap);

    const preventComboOverlap = self.preventComboOverlap;
    if (preventComboOverlap) self.comboNonOverlapping(displacements, comboMap);
  }


/**
   * Update the sizes of the combos according to their children
   * Used for combos nonoverlap, but not re-render the combo shapes
   */
private updateComboSizes(comboMap) {
    const self = this;
    const comboTrees = self.comboTrees;
    const nodeMap = self.nodeMap;
    const nodeSize = self.nodeSize as ((d?: unknown) => number) | undefined;
    const comboSpacing = self.comboSpacing;
    const comboPadding = self.comboPadding;
    (comboTrees || []).forEach((ctree) => {
      const treeChildren = [];
      traverseTreeUp<ComboTree>(ctree, (treeNode) => {
        if (treeNode.itemType === 'node') return true; // skip it
        const c = comboMap[treeNode.id];
        // means the combo is hidden, skip it
        if (!c) return;
        const children = treeNode.children;
        if (children) {
          children.forEach((child) => {
            // means the combo is hidden.
            if (!comboMap[child.id] && !nodeMap[child.id]) return;
            treeChildren.push(child);
          });
        }

        c.minX = Infinity;
        c.minY = Infinity;
        c.maxX = -Infinity;
        c.maxY = -Infinity;
        treeChildren.forEach((child) => {
          if (child.itemType !== 'node') return true; // skip it
          const node = nodeMap[child.id];
          if (!node) return true; // means it is hidden
          const r = nodeSize(node);
          const nodeMinX = node.x - r;
          const nodeMinY = node.y - r;
          const nodeMaxX = node.x + r;
          const nodeMaxY = node.y + r;
          if (c.minX > nodeMinX) c.minX = nodeMinX;
          if (c.minY > nodeMinY) c.minY = nodeMinY;
          if (c.maxX < nodeMaxX) c.maxX = nodeMaxX;
          if (c.maxY < nodeMaxY) c.maxY = nodeMaxY;
        });
        let minSize = self.oriComboMap[treeNode.id].size || Global.defaultCombo.size;
        if (isArray(minSize)) minSize = minSize[0];
        const maxLength = Math.max(c.maxX - c.minX, c.maxY - c.minY, minSize as number);
        c.r = maxLength / 2 + comboSpacing(c) / 2 + comboPadding(c);

        return true;
      });
    });
  }


/**
   * prevent the overlappings among combos
   */
private comboNonOverlapping(displacements, comboMap) {
    const self = this;
    const comboTree = self.comboTree;
    const comboCollideStrength = self.comboCollideStrength;
    const nodeIdxMap = self.nodeIdxMap;
    const nodeMap = self.nodeMap;

    traverseTreeUp<ComboTree>(comboTree, (treeNode) => {
      if (!comboMap[treeNode.id] && !nodeMap[treeNode.id] && treeNode.id !== 'comboTreeRoot')
        return; // means it is hidden
      const children = treeNode.children;
      // 同个子树下的子 combo 间两两对比
      if (children && children.length > 1) {
        children.forEach((v, i) => {
          if (v.itemType === 'node') return; // skip it
          const cv = comboMap[v.id];
          if (!cv) return; // means it is hidden, skip it
          children.forEach((u, j) => {
            if (i <= j) return;
            if (u.itemType === 'node') return; // skip it
            const cu = comboMap[u.id];
            if (!cu) return; // means it is hidden, skip it
            const vx = cv.cx - cu.cx || 0.005;
            const vy = cv.cy - cu.cy || 0.005;
            const l = vx * vx + vy * vy;
            const rv = cv.r;
            const ru = cu.r;
            const r = rv + ru;
            const ru2 = ru * ru;
            const rv2 = rv * rv;
            // overlapping
            if (l < r * r) {
              const vnodes = v.children;
              if (!vnodes || vnodes.length === 0) return; // skip it
              const unodes = u.children;
              if (!unodes || unodes.length === 0) return; // skip it
              const sqrtl = Math.sqrt(l);
              const ll = ((r - sqrtl) / sqrtl) * comboCollideStrength;
              const xl = vx * ll;
              const yl = vy * ll;
              const rratio = ru2 / (rv2 + ru2);
              const irratio = 1 - rratio;
              // 两兄弟 combo 的子节点上施加斥力
              vnodes.forEach((vn) => {
                if (vn.itemType !== 'node') return; // skip it
                if (!nodeMap[vn.id]) return; // means it is hidden, skip it
                const vindex = nodeIdxMap[vn.id];
                unodes.forEach((un) => {
                  if (un.itemType !== 'node') return;
                  if (!nodeMap[un.id]) return; // means it is hidden, skip it
                  const uindex = nodeIdxMap[un.id];
                  displacements[vindex].x += xl * rratio;
                  displacements[vindex].y += yl * rratio;
                  displacements[uindex].x -= xl * irratio;
                  displacements[uindex].y -= yl * irratio;
                });
              });
            }
          });
        });
      }
      return true;
    });
  }


/**
   * Calculate the repulsive force between each node pair
   * @param displacements The array stores the displacements for nodes
   * @param vecMap The map stores vector between each node pair
   */
private calRepulsive(displacements: Point[], vecMap: any) {
    const self = this;
    const nodes = self.nodes;
    const max = self.width * self.optimizeRangeFactor;
    const nodeStrength = self.nodeStrength as (d?: unknown) => number;
    const alpha = self.alpha;
    const nodeCollideStrength = self.nodeCollideStrength;
    const preventNodeOverlap = self.preventNodeOverlap;
    const nodeSizeFunc = self.nodeSize as ((d?: unknown) => number) | undefined;
    const nodeSpacingFunc = self.nodeSpacing as ((d?: unknown) => number) | undefined;
    const scale = self.depthRepulsiveForceScale;
    const center = self.center;
    nodes.forEach((v, i) => {
      if (!v.x || !v.y) return;

      // center gravity
      if (center) {
        const gravity = self.gravity;
        const vecX = v.x - center[0] || 0.005;
        const vecY = v.y - center[1] || 0.005;
        const l = Math.sqrt(vecX * vecX + vecY * vecY);
        displacements[i].x -= (vecX * gravity * alpha) / l;
        displacements[i].y -= (vecY * gravity * alpha) / l;
      }

      nodes.forEach((u, j) => {
        if (i === j) {
          return;
        }
        if (!u.x || !u.y) return;
        const { vl2, vl } = vecMap[`${v.id}-${u.id}`];
        if (vl > max) return;

        const { vx, vy } = vecMap[`${v.id}-${u.id}`];

        let depthDiff = Math.abs(u.depth - v.depth) + 1 || 1;
        if (u.comboId !== v.comboId) depthDiff++;
        const depthParam = depthDiff ? scale ** depthDiff : 1;

        const params = ((nodeStrength(u) * alpha) / vl2) * depthParam;
        displacements[i].x += vx * params;
        displacements[i].y += vy * params;

        // prevent node overlappings
        if (i < j && preventNodeOverlap) {
          const ri = nodeSizeFunc(v) + nodeSpacingFunc(v);
          const rj = nodeSizeFunc(u) + nodeSpacingFunc(u);
          const r = ri + rj;
          if (vl2 < r * r) {
            const ll = ((r - vl) / vl) * nodeCollideStrength;
            const rj2 = rj * rj;
            let rratio = rj2 / (ri * ri + rj2);
            const xl = vx * ll;
            const yl = vy * ll;
            displacements[i].x += xl * rratio;
            displacements[i].y += yl * rratio;
            rratio = 1 - rratio;
            displacements[j].x -= xl * rratio;
            displacements[j].y -= yl * rratio;
          }
        }
      });
    });
  }


/**
   * Calculate the attractive force between the node pair with edge
   * @param displacements The array stores the displacements for nodes
   * @param vecMap The map stores vector between each node pair
   */
private calAttractive(displacements: Point[], vecMap: any) {
    const self = this;
    const edges = self.edges;
    const linkDistance = self.linkDistance as (d?: unknown) => number;
    const alpha = self.alpha;
    const edgeStrength = self.edgeStrength as (d?: unknown) => number;
    const bias = self.bias;
    const scale = self.depthAttractiveForceScale;
    edges.forEach((e, i) => {
      if (!e.source || !e.target || e.source === e.target) return;
      const uIndex = self.nodeIdxMap[e.source];
      const vIndex = self.nodeIdxMap[e.target];
      const u = self.nodeMap[e.source];
      const v = self.nodeMap[e.target];

      let depthDiff = Math.abs(u.depth - v.depth);
      if (u.comboId === v.comboId) {
        depthDiff = depthDiff / 2;
      }
      let depthParam = depthDiff ? scale ** depthDiff : 1;
      if (u.comboId !== v.comboId && depthParam === 1) {
        depthParam = scale / 2;
      } else if (u.comboId === v.comboId) {
        depthParam = 2;
      }

      if (!isNumber(v.x) || !isNumber(u.x) || !isNumber(v.y) || !isNumber(u.y)) return;
      const { vl, vx, vy } = vecMap[`${e.target}-${e.source}`];
      const l = ((vl - linkDistance(e)) / vl) * alpha * edgeStrength(e) * depthParam;
      const vecX = vx * l;
      const vecY = vy * l;

      const b = bias[i];
      displacements[vIndex].x -= vecX * b;
      displacements[vIndex].y -= vecY * b;
      displacements[uIndex].x += vecX * (1 - b);
      displacements[uIndex].y += vecY * (1 - b);
    });
  }


@NgModule({
  imports: [
    BrowserModule,
    HttpModule,
    RouterModule.forRoot(appRoutes),
    FormsModule,
    MaterialModule,
    BrowserAnimationsModule
  ],
  declarations: [
    AppComponent, DialogContentComponent,
    WelcomeComponent,
    MaterialDemoComponent,
    LoginComponent,
    DomainListComponent,
    CourseListComponent,
    CourseSearchComponent
    //,MenuComponent
  ],
  providers: [
    AuthService,
    RequestService,
    DataEmitterService,
    AuthRouteActivator,
    Location, {provide: LocationStrategy, useClass: PathLocationStrategy}
  ],
  entryComponents: [ DialogContentComponent ],
  bootstrap: [ AppComponent ],
})
export class AppModule { }


export function drawUnclusteredLayer(
  sourceName: string,
  map: maplibreMap,
  { showMarkerPopup = false, ...options }: UnclusteredOptions
): { unclusteredLayerId: string } {
  const unclusteredLayerId = `${sourceName}-layer-unclustered-point`;

  const popupRender = options.popupRender ? options.popupRender : getPopupRenderFunction(unclusteredLayerId, options);

  addUnclusteredMarkerImages(map, options);

  const defaultUnclusteredPoint: SymbolLayer = {
    id: unclusteredLayerId,
    type: "symbol",
    source: sourceName,
    filter: ["!", ["has", "point_count"]],
    layout: {
      "icon-image": "inactive-marker",
    },
  };
  map.addLayer({ ...defaultUnclusteredPoint });

  /*
   * Add css to header to hide default popup tip
   */
  if (showMarkerPopup) {
    const element = document.getElementById(HIDE_TIP);
    if (!element) {
      const style = document.createElement("style");
      style.setAttribute("id", HIDE_TIP);
      document.head.append(style);
      style.textContent = ".mapboxgl-popup-tip { display: none; }";
    }
  }

  /*
   * Set active state on markers when clicked
   */
  map.on("click", unclusteredLayerId, function (e) {
    if (typeof options.onClick === "function") options.onClick(e);

    map.setLayoutProperty(unclusteredLayerId, "icon-image", [
      "match",
      ["id"],
      e.features[0].id, // check if the clicked id matches
      "active-marker", //image when id is the clicked feature id
      "inactive-marker", // default
    ]);

    // If popup option is set show a popup on click
    if (showMarkerPopup) {
      const selectedFeature = e.features[0];
      const coordinates = (selectedFeature.geometry as Point).coordinates;

      if (isCoordinates(coordinates)) {
        new Popup()
          .setLngLat(coordinates as Coordinates)
          .setHTML(popupRender(selectedFeature))
          .setOffset(15)
          .addTo(map);
      }
    }
  });

  return { unclusteredLayerId };
}


/*
 * Adds marker images to the maplibre canvas to be used for rendering unclustered points
 */
function addUnclusteredMarkerImages(
  map: maplibreMap,
  {
    selectedColor = ACTIVE_MARKER_COLOR,
    selectedBorderColor = COLOR_WHITE,
    selectedBorderWidth = 4,
    defaultBorderColor = COLOR_WHITE,
    defaultBorderWidth = 4,
    defaultColor: fillColor = MARKER_COLOR,
  }: UnclusteredOptions
) {
  const inactiveMarker = createMarker({
    fillColor: fillColor,
    strokeColor: defaultBorderColor,
    lineWidth: defaultBorderWidth,
  });
  const activeMarker = createMarker({
    fillColor: selectedColor,
    strokeColor: selectedBorderColor,
    lineWidth: selectedBorderWidth,
  });

  map.addImage("inactive-marker", inactiveMarker, { pixelRatio: 2 });
  map.addImage("active-marker", activeMarker, { pixelRatio: 2 });
}


@Module({
  providers: [MessageService],
  exports: [MessageService],
})
export class MessageModule {}


async function assertItemCount(page: Page, count: number, total?: number) {
  await expect(page.locator('.todo-count > strong')).toContainText(String(count));
  await expect(page.locator('.todo-list>li')).toHaveCount(total == undefined ? count : total);
}


async function addTodoItem(page: Page, text: string) {
  await page.fill('input.new-todo', text);
  await page.press('input.new-todo', 'Enter');
  await page.waitForTimeout(50);
}


() => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/todo/');
    page.on('pageerror', (err) => expect(err).toEqual(undefined));
  });

  test('todo title', async ({ page }) => {
    const title = page.locator('title');
    await expect(title).toHaveText('Qwik Demo: Todo');
  });

  test('should start with 3 items', async ({ page }) => {
    await expect(page.locator('.todo-count > strong')).toContainText('3');
  });

  test('should add new item', async ({ page }) => {
    await addTodoItem(page, 'New Item');
    await assertItemCount(page, 4);
    await expect(page.locator('.todo-list>li:last-child label')).toContainText('New Item');
  });

  test('should remove item', async ({ page }) => {
    await assertItemCount(page, 3);
    await page.locator('.todo-list>li:last-child').hover();
    await page.locator('.todo-list>li:last-child button').click();
    await assertItemCount(page, 2);
  });

  test('should complete an item', async ({ page }) => {
    await assertItemCount(page, 3);
    await page.locator('.todo-list>li:last-child input').click();
    await assertItemCount(page, 2, 3);
  });

  test('should edit an item', async ({ page }) => {
    await page.locator('.todo-list>li:first-child label').dblclick();
    await page.locator('.todo-list>li:first-child input.edit').fill('');
    await page.locator('.todo-list>li:first-child input.edit').press('X');
    await page.locator('.todo-list>li:first-child input.edit').press('Enter');
    await page.waitForTimeout(100);
    await expect(page.locator('.todo-list>li:first-child')).toContainText('X');
  });

  test('should blur input.edit element', async ({ page }) => {
    await page.locator('.todo-list>li:first-child label').dblclick();
    await page.locator('.todo-list>li:first-child input.edit').dispatchEvent('blur');
  });

  test('should clear completed', async ({ page }) => {
    await assertItemCount(page, 3);
    await page.locator('.todo-list>li:first-child input[type=checkbox]').click();
    await page.locator('button.clear-completed').click();
    await assertItemCount(page, 2);
  });

  test('should remove first item and update last', async ({ page }) => {
    await assertItemCount(page, 3);
    await page.locator('.todo-list>li:first-child').hover();
    await page.locator('.todo-list>li:first-child button').click();
    await assertItemCount(page, 2);
    await page.waitForTimeout(100);

    await page.locator('.todo-list>li:last-child input').click();
    await assertItemCount(page, 1, 2);
  });

  // Flaky on E2E Tests (ubuntu-latest, chromium)
  // test('should add item, remove item, set filter.', async ({ page }) => {
  //   await addTodoItem(page, 'New Item');
  //   await assertItemCount(page, 4);
  //   await page.locator('.todo-list>li:nth-child(2)').hover();
  //   await page.locator('.todo-list>li:nth-child(2) button').click();
  //   await assertItemCount(page, 3);
  //   await page.locator('.todo-list>li:last-child').hover();
  //   await page.locator('.todo-list>li:last-child input').click();
  //   await assertItemCount(page, 2, 3);
  //   await page.locator('footer li:first-child').click();
  //   await page.locator('.clear-completed').click();
  //   await assertItemCount(page, 2);
  // });
}


async ({ page }) => {
    await page.goto('/todo/');
    page.on('pageerror', (err) => expect(err).toEqual(undefined));
  }


(err) => expect(err).toEqual(undefined)


async ({ page }) => {
    const title = page.locator('title');
    await expect(title).toHaveText('Qwik Demo: Todo');
  }


async ({ page }) => {
    await expect(page.locator('.todo-count > strong')).toContainText('3');
  }


async ({ page }) => {
    await addTodoItem(page, 'New Item');
    await assertItemCount(page, 4);
    await expect(page.locator('.todo-list>li:last-child label')).toContainText('New Item');
  }


async ({ page }) => {
    await assertItemCount(page, 3);
    await page.locator('.todo-list>li:last-child').hover();
    await page.locator('.todo-list>li:last-child button').click();
    await assertItemCount(page, 2);
  }


async ({ page }) => {
    await assertItemCount(page, 3);
    await page.locator('.todo-list>li:last-child input').click();
    await assertItemCount(page, 2, 3);
  }


async ({ page }) => {
    await page.locator('.todo-list>li:first-child label').dblclick();
    await page.locator('.todo-list>li:first-child input.edit').fill('');
    await page.locator('.todo-list>li:first-child input.edit').press('X');
    await page.locator('.todo-list>li:first-child input.edit').press('Enter');
    await page.waitForTimeout(100);
    await expect(page.locator('.todo-list>li:first-child')).toContainText('X');
  }


async ({ page }) => {
    await page.locator('.todo-list>li:first-child label').dblclick();
    await page.locator('.todo-list>li:first-child input.edit').dispatchEvent('blur');
  }


async ({ page }) => {
    await assertItemCount(page, 3);
    await page.locator('.todo-list>li:first-child input[type=checkbox]').click();
    await page.locator('button.clear-completed').click();
    await assertItemCount(page, 2);
  }


async ({ page }) => {
    await assertItemCount(page, 3);
    await page.locator('.todo-list>li:first-child').hover();
    await page.locator('.todo-list>li:first-child button').click();
    await assertItemCount(page, 2);
    await page.waitForTimeout(100);

    await page.locator('.todo-list>li:last-child input').click();
    await assertItemCount(page, 1, 2);
  }


(to, from, next) => {
  end = Performance.startExecute(`${from.path} => ${to.path} 路由耗时`) /// 路由性能监控
  next()
  setTimeout(() => {
    end()
  }, 0)
}


() => {
    end()
  }


function createGroupIterator(group: SettingsTreeGroupElement): Iterable<ITreeElement<SettingsTreeGroupChild>> {
	return Iterable.map(group.children, g => {
		return {
			element: g,
			children: g instanceof SettingsTreeGroupElement ?
				createGroupIterator(g) :
				undefined
		};
	});
}


g => {
		return {
			element: g,
			children: g instanceof SettingsTreeGroupElement ?
				createGroupIterator(g) :
				undefined
		};
	}


e => {
			if (e.source !== ConfigurationTarget.DEFAULT) {
				this.onConfigUpdate(e.affectedKeys);
			}
		}


() => timeout(0)


() => {
				// Don't block setInput on render (which can trigger an async search)
				this.render(token).then(() => {
					options = options || SettingsEditorOptions.create({});

					if (!this.viewState.settingsTarget) {
						if (!options.target) {
							options.target = ConfigurationTarget.USER_LOCAL;
						}
					}

					this._setOptions(options);

					this._register(input.onDispose(() => {
						this.searchWidget.setValue('');
					}));

					// Init TOC selection
					this.updateTreeScrollSync();
				});
			}


() => {
					options = options || SettingsEditorOptions.create({});

					if (!this.viewState.settingsTarget) {
						if (!options.target) {
							options.target = ConfigurationTarget.USER_LOCAL;
						}
					}

					this._setOptions(options);

					this._register(input.onDispose(() => {
						this.searchWidget.setValue('');
					}));

					// Init TOC selection
					this.updateTreeScrollSync();
				}


() => {
						this.searchWidget.setValue('');
					}


() => {
				this.searchWidget.onHide();
			}


suggestion => {
			query = query.replace(suggestion, '');
		}


() => { this.clearSearchResults(); return Promise.resolve(null); }


(query: string) => {
				return SettingsEditor2.SUGGESTIONS.filter(tag => query.indexOf(tag) === -1).map(tag => tag.endsWith(':') ? tag : tag + ' ');
			}


tag => query.indexOf(tag) === -1


tag => tag.endsWith(':') ? tag : tag + ' '


() => {
			this._currentFocusContext = SettingsFocusContext.Search;
		}


colors => {
			const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
			const border = colors.contrastBorder ? colors.contrastBorder.toString() : '';
			const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : '';

			this.countElement.style.backgroundColor = background;
			this.countElement.style.color = foreground;

			this.countElement.style.borderWidth = border ? '1px' : '';
			this.countElement.style.borderStyle = border ? 'solid' : '';
			this.countElement.style.borderColor = border;
		}


() => {
			const searchVal = this.searchWidget.getValue();
			clearInputAction.enabled = !!searchVal;
			this.onSearchInputChanged();
		}


target => this.onDidSettingsTargetChange(target)


e => {
			const event = new StandardKeyboardEvent(e);
			if (event.keyCode === KeyCode.DownArrow) {
				this.focusSettings();
			}
		}


lastSyncedLabel => {
				this.lastSyncedLabel = lastSyncedLabel;
				this.updateInputAriaLabel();
			}


(_action) => { return undefined; }


() => this.settingsTree.setFocus([elements[0]])


() => {
				this.searchWidget.setValue('');
				this.onDidClickSetting(evt, true);
			}


(e: MouseEvent) => {
			DOM.EventHelper.stop(e, false);
			this.clearSearchFilters();
		}


colors => {
			this.noResultsMessage.style.color = colors.editorForeground ? colors.editorForeground.toString() : '';
		}


(e: StandardKeyboardEvent) => {
			if (
				e.keyCode === KeyCode.KEY_A &&
				(platform.isMacintosh ? e.metaKey : e.ctrlKey) &&
				e.target.tagName !== 'TEXTAREA' &&
				e.target.tagName !== 'INPUT'
			) {
				// Avoid browser ctrl+a
				e.browserEvent.stopPropagation();
				e.browserEvent.preventDefault();
			}
		}


() => {
			this._currentFocusContext = SettingsFocusContext.TableOfContents;
		}


e => {
			const element: SettingsTreeGroupElement | null = e.elements[0];
			if (this.tocFocusedElement === element) {
				return;
			}

			this.tocFocusedElement = element;
			this.tocTree.setSelection(element ? [element] : []);
			if (this.searchResultModel) {
				if (this.viewState.filterToCategory !== element) {
					this.viewState.filterToCategory = withNullAsUndefined(element);
					this.renderTree();
					this.settingsTree.scrollTop = 0;
				}
			} else if (element && (!e.browserEvent || !(<IFocusEventFromScroll>e.browserEvent).fromScroll)) {
				this.settingsTree.reveal(element, 0);
				this.settingsTree.setFocus([element]);
			}
		}


() => {
			this.tocRowFocused.set(true);
		}


() => {
			this.tocRowFocused.set(false);
		}


e => this.onDidChangeSetting(e.key, e.value, e.type)


settingKey => {
			this.openSettingsFile({ editSetting: settingKey });
		}


settingName => this.onDidClickSetting(settingName)


element => {
			this.settingsTree.setFocus([element]);
			this._currentFocusContext = SettingsFocusContext.SettingControl;
			this.settingRowFocused.set(false);
		}


(element: ISettingOverrideClickEvent) => {
			if (element.scope.toLowerCase() === 'workspace') {
				this.settingsTargetsWidget.updateTarget(ConfigurationTarget.WORKSPACE);
			} else if (element.scope.toLowerCase() === 'user') {
				this.settingsTargetsWidget.updateTarget(ConfigurationTarget.USER_LOCAL);
			} else if (element.scope.toLowerCase() === 'remote') {
				this.settingsTargetsWidget.updateTarget(ConfigurationTarget.USER_REMOTE);
			}

			this.searchWidget.setValue(element.targetKey);
		}


() => {
			if (this.settingsTree.scrollTop === this.settingsTreeScrollTop) {
				return;
			}

			this.settingsTreeScrollTop = this.settingsTree.scrollTop;

			// setTimeout because calling setChildren on the settingsTree can trigger onDidScroll, so it fires when
			// setChildren has called on the settings tree but not the toc tree yet, so their rendered elements are out of sync
			setTimeout(() => {
				this.updateTreeScrollSync();
			}, 0);
		}


() => {
				this.updateTreeScrollSync();
			}


() => {
			if (document.activeElement?.classList.contains('monaco-list')) {
				this._currentFocusContext = SettingsFocusContext.SettingTree;
				this.settingRowFocused.set(true);
			}
		}


() => {
			this.settingRowFocused.set(false);
		}


e => {
			const element = e.elements[0];
			if (this.treeFocusedElement === element) {
				return;
			}

			if (this.treeFocusedElement) {
				this.treeFocusedElement.tabbable = false;
			}

			this.treeFocusedElement = element;

			if (this.treeFocusedElement) {
				this.treeFocusedElement.tabbable = true;
			}

			this.settingsTree.setSelection(element ? [element] : []);
		}


() => this.updateChangedSetting(key, value)


e => this.tocTree.expand(<SettingsTreeGroupElement>e)


() => {
				this.renderTree(key, isManualReset);
				const reportModifiedProps = {
					key,
					query: this.searchWidget.getValue(),
					searchResults: this.searchResultModel && this.searchResultModel.getUniqueResults(),
					rawResults: this.searchResultModel && this.searchResultModel.getRawResults(),
					showConfiguredOnly: !!this.viewState.tagFilters && this.viewState.tagFilters.has(MODIFIED_SETTING_TAG),
					isReset: typeof value === 'undefined',
					settingsTarget: this.settingsTargetsWidget.settingsTarget as SettingsTarget
				};

				return this.reportModifiedSetting(reportModifiedProps);
			}


m => m.setting.key === props.key


(model: IEditorModel | null) => {
					if (token.isCancellationRequested || !(model instanceof Settings2EditorModel)) {
						return undefined;
					}

					this._register(model.onDidChangeGroups(() => {
						this.updatedConfigSchemaDelayer.trigger(() => {
							this.onConfigUpdate(undefined, undefined, true);
						});
					}));
					this.defaultSettingsEditorModel = model;
					return this.onConfigUpdate(undefined, true);
				}


() => {
						this.updatedConfigSchemaDelayer.trigger(() => {
							this.onConfigUpdate(undefined, undefined, true);
						});
					}


() => {
							this.onConfigUpdate(undefined, undefined, true);
						}


r => r.dispose()


() => {
			scheduledRefreshTracker.dispose();
			this.scheduledRefreshes.delete(key);
			this.onConfigUpdate([key]);
		}


g => g.extensionInfo ? 'extension' : 'core'


s => {
				settingKeyList.push(s.key);
			}


key => this.searchResultModel!.updateElementsByName(key)


key => this.settingsTreeModel.updateElementsByName(key)


key => this.renderTree(key)


() => this.reportFilteringUsed(query, this.searchResultModel!.getUniqueResults())


tag => this.viewState.tagFilters!.add(tag)


extensionId => this.viewState.extensionFilters!.add(extensionId)


feature => this.viewState.featureFilters!.add(feature)


() => {
			if (searchInProgress && !searchInProgress.token.isCancellationRequested) {
				return this.localFilterPreferences(query).then(result => {
					if (result && !result.exactMatch) {
						this.remoteSearchThrottle.trigger(() => {
							return searchInProgress && !searchInProgress.token.isCancellationRequested ?
								this.remoteSearchPreferences(query, this.searchInProgress!.token) :
								Promise.resolve();
						});
					}
				});
			} else {
				return Promise.resolve();
			}
		}


result => {
					if (result && !result.exactMatch) {
						this.remoteSearchThrottle.trigger(() => {
							return searchInProgress && !searchInProgress.token.isCancellationRequested ?
								this.remoteSearchPreferences(query, this.searchInProgress!.token) :
								Promise.resolve();
						});
					}
				}


() => {
							return searchInProgress && !searchInProgress.token.isCancellationRequested ?
								this.remoteSearchPreferences(query, this.searchInProgress!.token) :
								Promise.resolve();
						}


result => {
			if (token && token.isCancellationRequested) {
				// Handle cancellation like this because cancellation is lost inside the search provider due to async/await
				return null;
			}

			if (!this.searchResultModel) {
				this.searchResultModel = this.instantiationService.createInstance(SearchResultModel, this.viewState);
				this.searchResultModel.setResult(type, result);
				this.tocTreeModel.currentSearchModel = this.searchResultModel;
				this.onSearchModeToggled();
			} else {
				this.searchResultModel.setResult(type, result);
				this.tocTreeModel.update();
			}

			if (type === SearchResultIdx.Local) {
				this.tocTree.setFocus([]);
				this.viewState.filterToCategory = undefined;
				this.tocTree.expandAll();
			}

			this.refreshTOCTree();
			this.renderTree(undefined, true);
			return result;
		}


err => {
				if (isPromiseCanceledError(err)) {
					return Promise.reject(err);
				} else {
					/* __GDPR__
						"settingsEditor.searchError" : {
							"message": { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
						}
					*/
					const message = getErrorMessage(err).trim();
					if (message && message !== 'Error') {
						// "Error" = any generic network error
						this.telemetryService.publicLogError('settingsEditor.searchError', { message });
						this.logService.info('Setting search error: ' + message);
					}
					return null;
				}
			}


async () => {
			await this.commandService.executeCommand('workbench.userDataSync.actions.turnOn');
		}


() => {
			this.updateLastSyncedTime();
		}


() => this.updateLastSyncedTime()


() => {
			this.update();
		}


class SyncControls extends Disposable {
	private readonly lastSyncedLabel!: HTMLElement;
	private readonly turnOnSyncButton!: Button;

	private readonly _onDidChangeLastSyncedLabel = this._register(new Emitter<string>());
	public readonly onDidChangeLastSyncedLabel = this._onDidChangeLastSyncedLabel.event;

	constructor(
		container: HTMLElement,
		@ICommandService private readonly commandService: ICommandService,
		@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
		@IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService,
		@IThemeService themeService: IThemeService,
	) {
		super();

		const headerRightControlsContainer = DOM.append(container, $('.settings-right-controls'));
		const turnOnSyncButtonContainer = DOM.append(headerRightControlsContainer, $('.turn-on-sync'));
		this.turnOnSyncButton = this._register(new Button(turnOnSyncButtonContainer, { title: true }));
		this._register(attachButtonStyler(this.turnOnSyncButton, themeService));
		this.lastSyncedLabel = DOM.append(headerRightControlsContainer, $('.last-synced-label'));
		DOM.hide(this.lastSyncedLabel);

		this.turnOnSyncButton.enabled = true;
		this.turnOnSyncButton.label = localize('turnOnSyncButton', "Turn on Settings Sync");
		DOM.hide(this.turnOnSyncButton.element);

		this._register(this.turnOnSyncButton.onDidClick(async () => {
			await this.commandService.executeCommand('workbench.userDataSync.actions.turnOn');
		}));

		this.updateLastSyncedTime();
		this._register(this.userDataSyncService.onDidChangeLastSyncTime(() => {
			this.updateLastSyncedTime();
		}));

		const updateLastSyncedTimer = this._register(new IntervalTimer());
		updateLastSyncedTimer.cancelAndSet(() => this.updateLastSyncedTime(), 60 * 1000);

		this.update();
		this._register(this.userDataSyncService.onDidChangeStatus(() => {
			this.update();
		}));

		this._register(this.userDataAutoSyncEnablementService.onDidChangeEnablement(() => {
			this.update();
		}));
	}

	private updateLastSyncedTime(): void {
		const last = this.userDataSyncService.lastSyncTime;
		let label: string;
		if (typeof last === 'number') {
			const d = fromNow(last, true);
			label = localize('lastSyncedLabel', "Last synced: {0}", d);
		} else {
			label = '';
		}

		this.lastSyncedLabel.textContent = label;
		this._onDidChangeLastSyncedLabel.fire(label);
	}

	private update(): void {
		if (this.userDataSyncService.status === SyncStatus.Uninitialized) {
			return;
		}

		if (this.userDataAutoSyncEnablementService.isEnabled() || this.userDataSyncService.status !== SyncStatus.Idle) {
			DOM.show(this.lastSyncedLabel);
			DOM.hide(this.turnOnSyncButton.element);
		} else {
			DOM.hide(this.lastSyncedLabel);
			DOM.show(this.turnOnSyncButton.element);
		}
	}
}


interface IFocusEventFromScroll extends KeyboardEvent {
	fromScroll: true;
}


interface ISettingsEditor2State {
	searchQuery: string;
	target: SettingsTarget;
}


export const enum SettingsFocusContext {
	Search,
	TableOfContents,
	SettingTree,
	SettingControl
}


private static shouldSettingUpdateFast(type: SettingValueType | SettingValueType[]): boolean {
		if (isArray(type)) {
			// nullable integer/number or complex
			return false;
		}
		return type === SettingValueType.Enum ||
			type === SettingValueType.ArrayOfString ||
			type === SettingValueType.Complex ||
			type === SettingValueType.Boolean ||
			type === SettingValueType.Exclude;
	}


createEditor(parent: HTMLElement): void {
		parent.setAttribute('tabindex', '-1');
		this.rootElement = DOM.append(parent, $('.settings-editor', { tabindex: '-1' }));

		this.createHeader(this.rootElement);
		this.createBody(this.rootElement);
		this.addCtrlAInterceptor(this.rootElement);
		this.updateStyles();
	}


setInput(input: SettingsEditor2Input, options: SettingsEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
		this.inSettingsEditorContextKey.set(true);
		return super.setInput(input, options, context, token)
			.then(() => timeout(0)) // Force setInput to be async
			.then(() => {
				// Don't block setInput on render (which can trigger an async search)
				this.render(token).then(() => {
					options = options || SettingsEditorOptions.create({});

					if (!this.viewState.settingsTarget) {
						if (!options.target) {
							options.target = ConfigurationTarget.USER_LOCAL;
						}
					}

					this._setOptions(options);

					this._register(input.onDispose(() => {
						this.searchWidget.setValue('');
					}));

					// Init TOC selection
					this.updateTreeScrollSync();
				});
			});
	}


private restoreCachedState(): ISettingsEditor2State | null {
		const cachedState = this.group && this.input && this.editorMemento.loadEditorState(this.group, this.input);
		if (cachedState && typeof cachedState.target === 'object') {
			cachedState.target = URI.revive(cachedState.target);
		}

		if (cachedState) {
			const settingsTarget = cachedState.target;
			this.settingsTargetsWidget.settingsTarget = settingsTarget;
			this.viewState.settingsTarget = settingsTarget;
			this.searchWidget.setValue(cachedState.searchQuery);
		}

		if (this.input) {
			this.editorMemento.clearEditorState(this.input, this.group);
		}

		return withUndefinedAsNull(cachedState);
	}


setOptions(options: SettingsEditorOptions | undefined): void {
		super.setOptions(options);

		if (options) {
			this._setOptions(options);
		}
	}


private _setOptions(options: SettingsEditorOptions): void {
		if (options.query) {
			this.searchWidget.setValue(options.query);
		}

		const target: SettingsTarget = options.folderUri || <SettingsTarget>options.target;
		if (target) {
			this.settingsTargetsWidget.settingsTarget = target;
			this.viewState.settingsTarget = target;
		}
	}


clearInput(): void {
		this.inSettingsEditorContextKey.set(false);
		super.clearInput();
	}


layout(dimension: DOM.Dimension): void {
		this.dimension = dimension;

		if (!this.isVisible()) {
			return;
		}

		this.layoutTrees(dimension);

		const innerWidth = Math.min(1000, dimension.width) - 24 * 2; // 24px padding on left and right;
		// minus padding inside inputbox, countElement width, controls width, extra padding before countElement
		const monacoWidth = innerWidth - 10 - this.countElement.clientWidth - this.controlsElement.clientWidth - 12;
		this.searchWidget.layout(new DOM.Dimension(monacoWidth, 20));

		this.rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600);
		this.rootElement.classList.toggle('narrow-width', dimension.width < 600);
	}


focus(): void {
		if (this._currentFocusContext === SettingsFocusContext.Search) {
			this.focusSearch();
		} else if (this._currentFocusContext === SettingsFocusContext.SettingControl) {
			const element = this.focusedSettingDOMElement;
			if (element) {
				const control = element.querySelector(AbstractSettingRenderer.CONTROL_SELECTOR);
				if (control) {
					(<HTMLElement>control).focus();
					return;
				}
			}
		} else if (this._currentFocusContext === SettingsFocusContext.SettingTree) {
			this.settingsTree.domFocus();
		} else if (this._currentFocusContext === SettingsFocusContext.TableOfContents) {
			this.tocTree.domFocus();
		}
	}


protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
		super.setEditorVisible(visible, group);

		if (!visible) {
			// Wait for editor to be removed from DOM #106303
			setTimeout(() => {
				this.searchWidget.onHide();
			}, 0);
		}
	}


focusSettings(focusSettingInput = false): void {
		const focused = this.settingsTree.getFocus();
		if (!focused.length) {
			this.settingsTree.focusFirst();
		}

		this.settingsTree.domFocus();

		if (focusSettingInput) {
			const controlInFocusedRow = this.settingsTree.getHTMLElement().querySelector(`.focused ${AbstractSettingRenderer.CONTROL_SELECTOR}`);
			if (controlInFocusedRow) {
				(<HTMLElement>controlInFocusedRow).focus();
			}
		}
	}


focusTOC(): void {
		this.tocTree.domFocus();
	}


showContextMenu(): void {
		const focused = this.settingsTree.getFocus()[0];
		const rowElement = this.focusedSettingDOMElement;
		if (rowElement && focused instanceof SettingsTreeSettingElement) {
			this.settingRenderers.showContextMenu(focused, rowElement);
		}
	}


focusSearch(filter?: string, selectAll = true): void {
		if (filter && this.searchWidget) {
			this.searchWidget.setValue(filter);
		}

		this.searchWidget.focus(selectAll);
	}


clearSearchResults(): void {
		this.searchWidget.setValue('');
		this.focusSearch();
	}


clearSearchFilters(): void {
		let query = this.searchWidget.getValue();

		SettingsEditor2.SUGGESTIONS.forEach(suggestion => {
			query = query.replace(suggestion, '');
		});

		this.searchWidget.setValue(query.trim());
	}


private updateInputAriaLabel() {
		let label = searchBoxLabel;
		if (this.searchResultLabel) {
			label += `. ${this.searchResultLabel}`;
		}

		if (this.lastSyncedLabel) {
			label += `. ${this.lastSyncedLabel}`;
		}

		this.searchWidget.updateAriaLabel(label);
	}


private createHeader(parent: HTMLElement): void {
		this.headerContainer = DOM.append(parent, $('.settings-header'));

		const searchContainer = DOM.append(this.headerContainer, $('.search-container'));

		const clearInputAction = new Action(SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Settings Search Input"), 'codicon-clear-all', false, () => { this.clearSearchResults(); return Promise.resolve(null); });

		this.searchWidget = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${SettingsEditor2.ID}.searchbox`, searchContainer, {
			triggerCharacters: ['@'],
			provideResults: (query: string) => {
				return SettingsEditor2.SUGGESTIONS.filter(tag => query.indexOf(tag) === -1).map(tag => tag.endsWith(':') ? tag : tag + ' ');
			}
		}, searchBoxLabel, 'settingseditor:searchinput' + SettingsEditor2.NUM_INSTANCES++, {
			placeholderText: searchBoxLabel,
			focusContextKey: this.searchFocusContextKey,
			// TODO: Aria-live
		}));
		this._register(this.searchWidget.onFocus(() => {
			this._currentFocusContext = SettingsFocusContext.Search;
		}));

		this._register(attachSuggestEnabledInputBoxStyler(this.searchWidget, this.themeService, {
			inputBorder: settingsTextInputBorder
		}));

		this.countElement = DOM.append(searchContainer, DOM.$('.settings-count-widget.monaco-count-badge.long'));
		this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => {
			const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
			const border = colors.contrastBorder ? colors.contrastBorder.toString() : '';
			const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : '';

			this.countElement.style.backgroundColor = background;
			this.countElement.style.color = foreground;

			this.countElement.style.borderWidth = border ? '1px' : '';
			this.countElement.style.borderStyle = border ? 'solid' : '';
			this.countElement.style.borderColor = border;
		}));

		this._register(this.searchWidget.onInputDidChange(() => {
			const searchVal = this.searchWidget.getValue();
			clearInputAction.enabled = !!searchVal;
			this.onSearchInputChanged();
		}));

		const headerControlsContainer = DOM.append(this.headerContainer, $('.settings-header-controls'));
		const targetWidgetContainer = DOM.append(headerControlsContainer, $('.settings-target-container'));
		this.settingsTargetsWidget = this._register(this.instantiationService.createInstance(SettingsTargetsWidget, targetWidgetContainer, { enableRemoteSettings: true }));
		this.settingsTargetsWidget.settingsTarget = ConfigurationTarget.USER_LOCAL;
		this.settingsTargetsWidget.onDidTargetChange(target => this.onDidSettingsTargetChange(target));
		this._register(DOM.addDisposableListener(targetWidgetContainer, DOM.EventType.KEY_DOWN, e => {
			const event = new StandardKeyboardEvent(e);
			if (event.keyCode === KeyCode.DownArrow) {
				this.focusSettings();
			}
		}));

		if (this.userDataSyncWorkbenchService.enabled && this.userDataAutoSyncEnablementService.canToggleEnablement()) {
			const syncControls = this._register(this.instantiationService.createInstance(SyncControls, headerControlsContainer));
			this._register(syncControls.onDidChangeLastSyncedLabel(lastSyncedLabel => {
				this.lastSyncedLabel = lastSyncedLabel;
				this.updateInputAriaLabel();
			}));
		}

		this.controlsElement = DOM.append(searchContainer, DOM.$('.settings-clear-widget'));

		const actionBar = this._register(new ActionBar(this.controlsElement, {
			animated: false,
			actionViewItemProvider: (_action) => { return undefined; }
		}));

		actionBar.push([clearInputAction], { label: false, icon: true });
	}


private onDidSettingsTargetChange(target: SettingsTarget): void {
		this.viewState.settingsTarget = target;

		// TODO Instead of rebuilding the whole model, refresh and uncache the inspected setting value
		this.onConfigUpdate(undefined, true);
	}


private onDidClickSetting(evt: ISettingLinkClickEvent, recursed?: boolean): void {
		const elements = this.currentSettingsModel.getElementsByName(evt.targetKey);
		if (elements && elements[0]) {
			let sourceTop = 0.5;
			try {
				const _sourceTop = this.settingsTree.getRelativeTop(evt.source);
				if (_sourceTop !== null) {
					sourceTop = _sourceTop;
				}
			} catch {
				// e.g. clicked a searched element, now the search has been cleared
			}

			this.settingsTree.reveal(elements[0], sourceTop);

			// We need to shift focus from the setting that contains the link to the setting that's
			//  linked. Clicking on the link sets focus on the setting that contains the link,
			//  which is why we need the setTimeout
			setTimeout(() => this.settingsTree.setFocus([elements[0]]), 50);

			const domElements = this.settingRenderers.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), evt.targetKey);
			if (domElements && domElements[0]) {
				const control = domElements[0].querySelector(AbstractSettingRenderer.CONTROL_SELECTOR);
				if (control) {
					(<HTMLElement>control).focus();
				}
			}
		} else if (!recursed) {
			const p = this.triggerSearch('');
			p.then(() => {
				this.searchWidget.setValue('');
				this.onDidClickSetting(evt, true);
			});
		}
	}


switchToSettingsFile(): Promise<IEditorPane | undefined> {
		const query = parseQuery(this.searchWidget.getValue()).query;
		return this.openSettingsFile({ query });
	}


private async openSettingsFile(options?: ISettingsEditorOptions): Promise<IEditorPane | undefined> {
		const currentSettingsTarget = this.settingsTargetsWidget.settingsTarget;

		if (currentSettingsTarget === ConfigurationTarget.USER_LOCAL) {
			return this.preferencesService.openGlobalSettings(true, options);
		} else if (currentSettingsTarget === ConfigurationTarget.USER_REMOTE) {
			return this.preferencesService.openRemoteSettings();
		} else if (currentSettingsTarget === ConfigurationTarget.WORKSPACE) {
			return this.preferencesService.openWorkspaceSettings(true, options);
		} else if (URI.isUri(currentSettingsTarget)) {
			return this.preferencesService.openFolderSettings(currentSettingsTarget, true, options);
		}

		return undefined;
	}


private createBody(parent: HTMLElement): void {
		const bodyContainer = DOM.append(parent, $('.settings-body'));

		this.noResultsMessage = DOM.append(bodyContainer, $('.no-results-message'));

		this.noResultsMessage.innerText = localize('noResults', "No Settings Found");

		this.clearFilterLinkContainer = $('span.clear-search-filters');

		this.clearFilterLinkContainer.textContent = ' - ';
		const clearFilterLink = DOM.append(this.clearFilterLinkContainer, $('a.pointer.prominent', { tabindex: 0 }, localize('clearSearchFilters', 'Clear Filters')));
		this._register(DOM.addDisposableListener(clearFilterLink, DOM.EventType.CLICK, (e: MouseEvent) => {
			DOM.EventHelper.stop(e, false);
			this.clearSearchFilters();
		}));

		DOM.append(this.noResultsMessage, this.clearFilterLinkContainer);

		this._register(attachStylerCallback(this.themeService, { editorForeground }, colors => {
			this.noResultsMessage.style.color = colors.editorForeground ? colors.editorForeground.toString() : '';
		}));

		this.createTOC(bodyContainer);
		this.createSettingsTree(bodyContainer);
	}


private addCtrlAInterceptor(container: HTMLElement): void {
		this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => {
			if (
				e.keyCode === KeyCode.KEY_A &&
				(platform.isMacintosh ? e.metaKey : e.ctrlKey) &&
				e.target.tagName !== 'TEXTAREA' &&
				e.target.tagName !== 'INPUT'
			) {
				// Avoid browser ctrl+a
				e.browserEvent.stopPropagation();
				e.browserEvent.preventDefault();
			}
		}));
	}


private createTOC(parent: HTMLElement): void {
		this.tocTreeModel = this.instantiationService.createInstance(TOCTreeModel, this.viewState);
		this.tocTreeContainer = DOM.append(parent, $('.settings-toc-container'));

		this.tocTree = this._register(this.instantiationService.createInstance(TOCTree,
			DOM.append(this.tocTreeContainer, $('.settings-toc-wrapper', {
				'role': 'navigation',
				'aria-label': localize('settings', "Settings"),
			})),
			this.viewState));

		this._register(this.tocTree.onDidFocus(() => {
			this._currentFocusContext = SettingsFocusContext.TableOfContents;
		}));

		this._register(this.tocTree.onDidChangeFocus(e => {
			const element: SettingsTreeGroupElement | null = e.elements[0];
			if (this.tocFocusedElement === element) {
				return;
			}

			this.tocFocusedElement = element;
			this.tocTree.setSelection(element ? [element] : []);
			if (this.searchResultModel) {
				if (this.viewState.filterToCategory !== element) {
					this.viewState.filterToCategory = withNullAsUndefined(element);
					this.renderTree();
					this.settingsTree.scrollTop = 0;
				}
			} else if (element && (!e.browserEvent || !(<IFocusEventFromScroll>e.browserEvent).fromScroll)) {
				this.settingsTree.reveal(element, 0);
				this.settingsTree.setFocus([element]);
			}
		}));

		this._register(this.tocTree.onDidFocus(() => {
			this.tocRowFocused.set(true);
		}));

		this._register(this.tocTree.onDidBlur(() => {
			this.tocRowFocused.set(false);
		}));
	}


private createSettingsTree(parent: HTMLElement): void {
		this.settingsTreeContainer = DOM.append(parent, $('.settings-tree-container'));

		this.settingRenderers = this.instantiationService.createInstance(SettingTreeRenderers);
		this._register(this.settingRenderers.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value, e.type)));
		this._register(this.settingRenderers.onDidOpenSettings(settingKey => {
			this.openSettingsFile({ editSetting: settingKey });
		}));
		this._register(this.settingRenderers.onDidClickSettingLink(settingName => this.onDidClickSetting(settingName)));
		this._register(this.settingRenderers.onDidFocusSetting(element => {
			this.settingsTree.setFocus([element]);
			this._currentFocusContext = SettingsFocusContext.SettingControl;
			this.settingRowFocused.set(false);
		}));
		this._register(this.settingRenderers.onDidClickOverrideElement((element: ISettingOverrideClickEvent) => {
			if (element.scope.toLowerCase() === 'workspace') {
				this.settingsTargetsWidget.updateTarget(ConfigurationTarget.WORKSPACE);
			} else if (element.scope.toLowerCase() === 'user') {
				this.settingsTargetsWidget.updateTarget(ConfigurationTarget.USER_LOCAL);
			} else if (element.scope.toLowerCase() === 'remote') {
				this.settingsTargetsWidget.updateTarget(ConfigurationTarget.USER_REMOTE);
			}

			this.searchWidget.setValue(element.targetKey);
		}));

		this.settingsTree = this._register(this.instantiationService.createInstance(SettingsTree,
			this.settingsTreeContainer,
			this.viewState,
			this.settingRenderers.allRenderers));

		this._register(this.settingsTree.onDidScroll(() => {
			if (this.settingsTree.scrollTop === this.settingsTreeScrollTop) {
				return;
			}

			this.settingsTreeScrollTop = this.settingsTree.scrollTop;

			// setTimeout because calling setChildren on the settingsTree can trigger onDidScroll, so it fires when
			// setChildren has called on the settings tree but not the toc tree yet, so their rendered elements are out of sync
			setTimeout(() => {
				this.updateTreeScrollSync();
			}, 0);
		}));

		this._register(this.settingsTree.onDidFocus(() => {
			if (document.activeElement?.classList.contains('monaco-list')) {
				this._currentFocusContext = SettingsFocusContext.SettingTree;
				this.settingRowFocused.set(true);
			}
		}));

		this._register(this.settingsTree.onDidBlur(() => {
			this.settingRowFocused.set(false);
		}));

		// There is no different select state in the settings tree
		this._register(this.settingsTree.onDidChangeFocus(e => {
			const element = e.elements[0];
			if (this.treeFocusedElement === element) {
				return;
			}

			if (this.treeFocusedElement) {
				this.treeFocusedElement.tabbable = false;
			}

			this.treeFocusedElement = element;

			if (this.treeFocusedElement) {
				this.treeFocusedElement.tabbable = true;
			}

			this.settingsTree.setSelection(element ? [element] : []);
		}));
	}


private notifyNoSaveNeeded() {
		if (!this.storageService.getBoolean(SETTINGS_AUTOSAVE_NOTIFIED_KEY, StorageScope.GLOBAL, false)) {
			this.storageService.store(SETTINGS_AUTOSAVE_NOTIFIED_KEY, true, StorageScope.GLOBAL, StorageTarget.USER);
			this.notificationService.info(localize('settingsNoSaveNeeded', "Your changes are automatically saved as you edit."));
		}
	}


private onDidChangeSetting(key: string, value: any, type: SettingValueType | SettingValueType[]): void {
		this.notifyNoSaveNeeded();

		if (this.pendingSettingUpdate && this.pendingSettingUpdate.key !== key) {
			this.updateChangedSetting(key, value);
		}

		this.pendingSettingUpdate = { key, value };
		if (SettingsEditor2.shouldSettingUpdateFast(type)) {
			this.settingFastUpdateDelayer.trigger(() => this.updateChangedSetting(key, value));
		} else {
			this.settingSlowUpdateDelayer.trigger(() => this.updateChangedSetting(key, value));
		}
	}


private updateTreeScrollSync(): void {
		this.settingRenderers.cancelSuggesters();
		if (this.searchResultModel) {
			return;
		}

		if (!this.tocTreeModel) {
			return;
		}

		const elementToSync = this.settingsTree.firstVisibleElement;
		const element = elementToSync instanceof SettingsTreeSettingElement ? elementToSync.parent :
			elementToSync instanceof SettingsTreeGroupElement ? elementToSync :
				null;

		// It's possible for this to be called when the TOC and settings tree are out of sync - e.g. when the settings tree has deferred a refresh because
		// it is focused. So, bail if element doesn't exist in the TOC.
		let nodeExists = true;
		try { this.tocTree.getNode(element); } catch (e) { nodeExists = false; }
		if (!nodeExists) {
			return;
		}

		if (element && this.tocTree.getSelection()[0] !== element) {
			const ancestors = this.getAncestors(element);
			ancestors.forEach(e => this.tocTree.expand(<SettingsTreeGroupElement>e));

			this.tocTree.reveal(element);
			const elementTop = this.tocTree.getRelativeTop(element);
			if (typeof elementTop !== 'number') {
				return;
			}

			this.tocTree.collapseAll();

			ancestors.forEach(e => this.tocTree.expand(<SettingsTreeGroupElement>e));
			if (elementTop < 0 || elementTop > 1) {
				this.tocTree.reveal(element);
			} else {
				this.tocTree.reveal(element, elementTop);
			}

			this.tocTree.expand(element);

			this.tocTree.setSelection([element]);

			const fakeKeyboardEvent = new KeyboardEvent('keydown');
			(<IFocusEventFromScroll>fakeKeyboardEvent).fromScroll = true;
			this.tocTree.setFocus([element], fakeKeyboardEvent);
		}
	}


private getAncestors(element: SettingsTreeElement): SettingsTreeElement[] {
		const ancestors: any[] = [];

		while (element.parent) {
			if (element.parent.id !== 'root') {
				ancestors.push(element.parent);
			}

			element = element.parent;
		}

		return ancestors.reverse();
	}


private updateChangedSetting(key: string, value: any): Promise<void> {
		// ConfigurationService displays the error if this fails.
		// Force a render afterwards because onDidConfigurationUpdate doesn't fire if the update doesn't result in an effective setting value change
		const settingsTarget = this.settingsTargetsWidget.settingsTarget;
		const resource = URI.isUri(settingsTarget) ? settingsTarget : undefined;
		const configurationTarget = <ConfigurationTarget>(resource ? ConfigurationTarget.WORKSPACE_FOLDER : settingsTarget);
		const overrides: IConfigurationOverrides = { resource };

		const isManualReset = value === undefined;

		// If the user is changing the value back to the default, do a 'reset' instead
		const inspected = this.configurationService.inspect(key, overrides);
		if (inspected.defaultValue === value) {
			value = undefined;
		}

		return this.configurationService.updateValue(key, value, overrides, configurationTarget)
			.then(() => {
				this.renderTree(key, isManualReset);
				const reportModifiedProps = {
					key,
					query: this.searchWidget.getValue(),
					searchResults: this.searchResultModel && this.searchResultModel.getUniqueResults(),
					rawResults: this.searchResultModel && this.searchResultModel.getRawResults(),
					showConfiguredOnly: !!this.viewState.tagFilters && this.viewState.tagFilters.has(MODIFIED_SETTING_TAG),
					isReset: typeof value === 'undefined',
					settingsTarget: this.settingsTargetsWidget.settingsTarget as SettingsTarget
				};

				return this.reportModifiedSetting(reportModifiedProps);
			});
	}