xref: /aosp_15_r20/external/perfetto/ui/src/base/utils.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2023 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker//
3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker//
7*6dbdd20aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker//
9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Worker// Return true if value is not nullish - i.e. not null or undefined
16*6dbdd20aSAndroid Build Coastguard Worker// Allows doing the following
17*6dbdd20aSAndroid Build Coastguard Worker//   exists(val) && m('div', val)
18*6dbdd20aSAndroid Build Coastguard Worker// Even if val is a non-nullish falsey value like 0 or ''
19*6dbdd20aSAndroid Build Coastguard Workerexport function exists<T>(value: T): value is NonNullable<T> {
20*6dbdd20aSAndroid Build Coastguard Worker  return value !== undefined && value !== null;
21*6dbdd20aSAndroid Build Coastguard Worker}
22*6dbdd20aSAndroid Build Coastguard Worker
23*6dbdd20aSAndroid Build Coastguard Worker// Type util to make sure that exactly one of the passed keys is defined.
24*6dbdd20aSAndroid Build Coastguard Worker// Example usage:
25*6dbdd20aSAndroid Build Coastguard Worker// type FooOrBar = ExactlyOne<{foo: number; bar: number}>;
26*6dbdd20aSAndroid Build Coastguard Worker// const x : FooOrBar = {foo: 42};      // OK
27*6dbdd20aSAndroid Build Coastguard Worker// const x : FooOrBar = {bar: 42};      // OK
28*6dbdd20aSAndroid Build Coastguard Worker// const x : FooOrBar = {};             // Compiler error
29*6dbdd20aSAndroid Build Coastguard Worker// const x : FooOrBar = {foo:1, bar:2}; // Compiler error
30*6dbdd20aSAndroid Build Coastguard Workerexport type ExactlyOne<T, K extends keyof T = keyof T> = K extends keyof T
31*6dbdd20aSAndroid Build Coastguard Worker  ? {[P in K]: T[P]} & {[P in Exclude<keyof T, K>]?: undefined}
32*6dbdd20aSAndroid Build Coastguard Worker  : never;
33*6dbdd20aSAndroid Build Coastguard Worker
34*6dbdd20aSAndroid Build Coastguard Worker// Escape characters that are not allowed inside a css selector
35*6dbdd20aSAndroid Build Coastguard Workerexport function escapeCSSSelector(selector: string): string {
36*6dbdd20aSAndroid Build Coastguard Worker  return selector.replace(/([!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g, '\\$1');
37*6dbdd20aSAndroid Build Coastguard Worker}
38*6dbdd20aSAndroid Build Coastguard Worker
39*6dbdd20aSAndroid Build Coastguard Worker// Make field K required in T
40*6dbdd20aSAndroid Build Coastguard Workerexport type RequiredField<T, K extends keyof T> = Omit<T, K> &
41*6dbdd20aSAndroid Build Coastguard Worker  Required<Pick<T, K>>;
42*6dbdd20aSAndroid Build Coastguard Worker
43*6dbdd20aSAndroid Build Coastguard Worker// The lowest common denoninator between Map<> and WeakMap<>.
44*6dbdd20aSAndroid Build Coastguard Worker// This is just to avoid duplication of the getOrCreate below.
45*6dbdd20aSAndroid Build Coastguard Workerinterface MapLike<K, V> {
46*6dbdd20aSAndroid Build Coastguard Worker  get(key: K): V | undefined;
47*6dbdd20aSAndroid Build Coastguard Worker  set(key: K, value: V): this;
48*6dbdd20aSAndroid Build Coastguard Worker}
49*6dbdd20aSAndroid Build Coastguard Worker
50*6dbdd20aSAndroid Build Coastguard Workerexport function getOrCreate<K, V>(
51*6dbdd20aSAndroid Build Coastguard Worker  map: MapLike<K, V>,
52*6dbdd20aSAndroid Build Coastguard Worker  key: K,
53*6dbdd20aSAndroid Build Coastguard Worker  factory: () => V,
54*6dbdd20aSAndroid Build Coastguard Worker): V {
55*6dbdd20aSAndroid Build Coastguard Worker  let value = map.get(key);
56*6dbdd20aSAndroid Build Coastguard Worker  if (value !== undefined) return value;
57*6dbdd20aSAndroid Build Coastguard Worker  value = factory();
58*6dbdd20aSAndroid Build Coastguard Worker  map.set(key, value);
59*6dbdd20aSAndroid Build Coastguard Worker  return value;
60*6dbdd20aSAndroid Build Coastguard Worker}
61*6dbdd20aSAndroid Build Coastguard Worker
62*6dbdd20aSAndroid Build Coastguard Worker// Allows to take an existing class instance (`target`) and override some of its
63*6dbdd20aSAndroid Build Coastguard Worker// methods via `overrides`. We use this for cases where we want to expose a
64*6dbdd20aSAndroid Build Coastguard Worker// "manager" (e.g. TrackManager, SidebarManager) to the plugins, but we want to
65*6dbdd20aSAndroid Build Coastguard Worker// override few of its methods (e.g. to inject the pluginId in the args).
66*6dbdd20aSAndroid Build Coastguard Workerexport function createProxy<T extends object>(
67*6dbdd20aSAndroid Build Coastguard Worker  target: T,
68*6dbdd20aSAndroid Build Coastguard Worker  overrides: Partial<T>,
69*6dbdd20aSAndroid Build Coastguard Worker): T {
70*6dbdd20aSAndroid Build Coastguard Worker  return new Proxy(target, {
71*6dbdd20aSAndroid Build Coastguard Worker    get: (target: T, prop: string | symbol, receiver) => {
72*6dbdd20aSAndroid Build Coastguard Worker      // If the property is overriden, use that; otherwise, use target
73*6dbdd20aSAndroid Build Coastguard Worker      const overrideValue = (overrides as {[key: symbol | string]: {}})[prop];
74*6dbdd20aSAndroid Build Coastguard Worker      if (overrideValue !== undefined) {
75*6dbdd20aSAndroid Build Coastguard Worker        return typeof overrideValue === 'function'
76*6dbdd20aSAndroid Build Coastguard Worker          ? overrideValue.bind(overrides)
77*6dbdd20aSAndroid Build Coastguard Worker          : overrideValue;
78*6dbdd20aSAndroid Build Coastguard Worker      }
79*6dbdd20aSAndroid Build Coastguard Worker      const baseValue = Reflect.get(target, prop, receiver);
80*6dbdd20aSAndroid Build Coastguard Worker      return typeof baseValue === 'function'
81*6dbdd20aSAndroid Build Coastguard Worker        ? baseValue.bind(target)
82*6dbdd20aSAndroid Build Coastguard Worker        : baseValue;
83*6dbdd20aSAndroid Build Coastguard Worker    },
84*6dbdd20aSAndroid Build Coastguard Worker  }) as T;
85*6dbdd20aSAndroid Build Coastguard Worker}
86