All files / lib/utils utilities.ts

97.55% Statements 279/286
95.74% Branches 45/47
100% Functions 16/16
97.55% Lines 279/286

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 2871x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 528661x 528661x 1x 1x 1x 16577x 16577x 1x 1x 1x 43913x 43913x 1x 1x 233407x 233407x 1x 1x 3114x 3114x 1x 1x 6483x 6483x 6483x 6483x 6483x 6483x 5110x 6483x 6483x 6483x 1x 1x 1x 48913x 48913x 1x 1x 1x 48114x 48114x 48114x 540x 48114x 48114x 48114x 1x 1x 1x 76745x 76745x 76745x 48913x 16577x 76745x 76745x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 118023x 118023x 1x 1x 1x 1x 1178x 1178x 1178x 1178x 1178x 1178x 1178x 1178x 1178x 1178x 1178x 1178x 1178x 1x 1x 1x 1x 1x 1x 1x 42x 42x 1x 42x 42x 42x 42x 42x 42x 25x 25x           42x 42x 42x 42x 42x 42x 42x 42x 42x 42x 42x 42x     42x 42x 42x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 28x 42x 42x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 42x 42x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 42x 42x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 42x 1x 1x 1x 1x 1x 1x 1x 1x 19x 7x 12x 12x 19x 19x 1x 1x 1x 1x 1x 1x 1x 4854x 4854x 4854x 4854x 4854x 4854x 4854x 1x 1x 1x 1x 1x 1x 1x 214x 214x 214x 214x 198x 198x 198x 198x 214x 1x 1x 12874x 12874x 12874x 893x 893x 893x 893x 893x 12874x 12874x 12874x 12874x 12874x 12874x  
/*******************************************************************************
 * Copyright bei
 * Entwicklungs- und Pflegeverbund für das gemeinsame Fachverfahren gefa
 *
 *******************************************************************************/
import {
  FocusableElementOwner,
  FocusableRangeElementOwner,
  FormControlStatus,
  HasBadge,
  HasLabel,
  InvalidControlValue,
  Item,
  ItemGroup,
  TreeItem,
  TreeItemBasic,
  WidgetKeySet,
  WithBadge,
  WithHighlight,
  formControlStatusTypeHelper,
} from './util.types';
 
let WIDGET_COUNTER = 0;
 
export function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value != null && !Array.isArray(value);
}
 
/** check if the value is of type HasLabel */
export function isLabled(item: unknown): item is HasLabel {
  return isObject(item) && 'label' in item;
}
 
/** check if the value is of type Item */
export function isItem(item: unknown): item is Item {
  return isObject(item) && 'label' in item && 'key' in item;
}
 
export function isItemGroup<T>(item: T | ItemGroup<T>): item is ItemGroup<T> {
  return isObject(item) && 'items' in item && Array.isArray(item.items);
}
 
export function isTreeItem(item: unknown): item is TreeItem<unknown> {
  return isTreeItemBasic(item);
}
 
export function isTreeItemBasic(item: unknown): item is TreeItemBasic<unknown> {
  if (!isItem(item)) {
    return false;
  } else {
    const treeItem: TreeItemBasic<unknown> = item;
    return (
      typeof treeItem.children === 'undefined' ||
      Array.isArray(treeItem.children)
    );
  }
}
 
/** check if the value is of type HasBadge */
export function isBadge(item: unknown): item is HasBadge {
  return isObject(item) && 'variant' in item && 'status' in item;
}
 
/** check if the value is of type WithHighlight */
export function isWithHighlight(item: unknown): item is WithHighlight {
  return (
    isObject(item) &&
    isObject(item.highlight) &&
    typeof item.highlight.variant === 'string' &&
    typeof item.highlight.color === 'string'
  );
}
 
/** check if the value is of type WithBadge */
export function isWithBagde(item: unknown): item is WithBadge {
  return (
    isObject(item) &&
    'badge' in item &&
    isBadge(item.badge) &&
    isLabled(item.badge)
  );
}
 
/** check if an element is of type FocusableElementOwner */
export function isFocusableElementOwner(
  element: unknown,
): element is FocusableElementOwner {
  return (
    isObject(element) &&
    typeof (element as unknown as FocusableElementOwner).focusChild ===
      'function'
  );
}
 
/** check if an element is of type FocusableRangeElementOwner */
export function isFocusableRangeElementOwner(
  element: unknown,
): element is FocusableRangeElementOwner {
  return (
    isObject(element) &&
    typeof (element as unknown as FocusableRangeElementOwner).focusRangeSlot ===
      'function'
  );
}
 
/** method to use as trackBy in ngFor */
export function itemTrack(item: Item): string {
  return item.key + item.label;
}
 
let audioCtx: AudioContext | undefined;
 
/** predefined sounds to play */
export enum SoundType {
  /** sound to play on error */
  ERROR,
 
  /** sound representing a successful action */
  SUCCESS,
 
  /** sound representing an appearance of a warning  */
  WARNING,
 
  /** sound representing an appearance of a new information */
  INFO,
}
 
/**
 * play one of the predefined sounds
 *
 * @param type the predefined sound type
 */
export async function playSound(type: SoundType): Promise<void> {
  await implPlaySound(type, false);
}
 
async function implPlaySound(
  type: SoundType,
  fromResume: boolean,
): Promise<void> {
  if (audioCtx === undefined) {
    try {
      audioCtx = new AudioContext();
    } catch (e) {
      console.warn(
        'Unable to play sound because no AudioContext is available.',
      );
      return;
    }
  }
 
  /*
   * Workaround for Firefox who starts in suspended mode and
   * hence does not play the sound upon initial creation
   * Current behavior:
   *  - FF: Starts with suspended => changes state to running => needs a small delay and is then ready
   *  - Chrome: Starts with running and does not enter this code block
   *  - Safar: Starts with suspended => changes state to running => could immediate play sound (would work also without this listener!)
   */
  if (!fromResume && audioCtx.state === 'suspended') {
    await audioCtx.resume();
    await new Promise(r => setTimeout(r));
    return implPlaySound(type, true);
  }
 
  if (type === SoundType.ERROR) {
    const oscillator = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
 
    oscillator.connect(gain);
    gain.connect(audioCtx.destination);
    oscillator.type = 'sine';
    oscillator.frequency.value = 200;
    oscillator.start();
 
    const time = audioCtx.currentTime;
    gain.gain.setTargetAtTime(0, time, 0.015);
    oscillator.stop(time + 0.09);
  }
 
  if (type === SoundType.SUCCESS) {
    const oscillator = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
 
    oscillator.connect(gain);
    gain.connect(audioCtx.destination);
    oscillator.type = 'sine';
    oscillator.frequency.value = 1200;
    oscillator.start();
 
    const time = audioCtx.currentTime;
    gain.gain.setTargetAtTime(0, time, 0.08);
    oscillator.stop(time + 0.6);
  }
 
  if (type === SoundType.WARNING) {
    const oscillator = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
 
    oscillator.connect(gain);
    gain.connect(audioCtx.destination);
    oscillator.type = 'triangle';
    oscillator.frequency.value = 100;
    oscillator.start();
 
    const time = audioCtx.currentTime;
    gain.gain.value = 5;
    gain.gain.setTargetAtTime(0, time, 0.08);
    oscillator.stop(time + 0.6);
  }
 
  if (type === SoundType.INFO) {
    const oscillator = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
 
    oscillator.connect(gain);
    gain.connect(audioCtx.destination);
    oscillator.type = 'triangle';
    oscillator.frequency.value = 500;
    oscillator.start();
 
    const time = audioCtx.currentTime;
    gain.gain.setTargetAtTime(0, time, 0.08);
    oscillator.stop(time + 0.6);
  }
}
 
/**
 * function to flip the given value in the set
 *
 * @param the original set
 * @param the value to add/remove from the set
 */
export function flipSetValue<T>(set: ReadonlySet<T>, value: T): ReadonlySet<T> {
  if (set.has(value)) {
    return new Set([...set].filter(v => v !== value));
  } else {
    return new Set([...set, value]);
  }
}
 
/**
 * create a set of ids to use for widgets and parts of it
 *
 * @param the prefix to use
 */
export function createWidgetKeySet(prefix: string): WidgetKeySet {
  const baseKey = prefix + '-' + (WIDGET_COUNTER++).toString();
  const widgetKey = baseKey + '-widget';
  const labelKey = baseKey + '-label';
  const errorKey = baseKey + '-error';
 
  return { baseKey, widgetKey, labelKey, errorKey };
}
 
/**
 * Check whether a FormControlStatus represents an "ok" state, i.e. has no errors or other messages set.
 * @param status the status to check
 * @returns `true` if the status represents an OK state
 */
export function isStatusOk(status: Readonly<FormControlStatus>): boolean {
  // status is "ok" if no entry contains any messages
  for (const entry of formControlStatusTypeHelper) {
    if (status[entry]?.length) {
      return false;
    }
  }
 
  return true;
}
 
export function isInvalidControlValue(
  value: unknown,
): value is InvalidControlValue {
  if (isObject(value)) {
    const candidate: InvalidControlValue =
      value as unknown as InvalidControlValue;
    return (
      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- need to check the discriminator of the object, the casting does not guarantee typesafety
      candidate.kind === 'InvalidControlInput' &&
      typeof candidate.rawValue === 'string'
    );
  } else {
    return false;
  }
}