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 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | /*******************************************************************************
* 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 && 'color' 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'
);
}
/**@deprecated in favour of isWithBadge*/
export function isWithBagde(item: unknown): item is WithBadge {
return isWithBadge(item);
}
/** check if the value is of type WithBadge */
export function isWithBadge(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,
}
export const SoundContextUnknown: unique symbol = Symbol('SoundMetaUnknown');
export type SoundContextUnknownType = typeof SoundContextUnknown;
/**
* Context informations
* - `COPY_TO_CLIP_BOARD`: the sound is played in the context of copying text to the clipboard clipboard
* - `FIELD`: the sound is played in the context of a field
* - `VALIDATION_ERROR`: the sound is played in the context of a validation error
* - `ACTION`: the sound is played in the context of executing an action
* - `FORM`: the sound is played in context of a form
* - `STATE_SUCCESS`: the sound is played in the context of a success (eg a form save successully, ...)
* - `ALERT`: the sound is played in context of an alert
* - `MAXLENGTH_EXCEEDED`: the sounds is played because the max-length succeeded
* - `ERROR`: the sound is played in an error context (eg validation error, error-alert, ...)
* - `INFO`: the sound is played in an info context (eg alert with info is shown, ...)
* - `QUESTION`: the sound is played in a question context (eg alert alert with a question is shown, ...)
*/
export type SounContextData =
| 'COPY_TO_CLIP_BOARD'
| 'FIELD'
| 'VALIDATION_ERROR'
| 'ACTION'
| 'FORM'
| 'STATE_SUCCESS'
| 'ALERT'
| 'MAXLENGTH_EXCEEDED'
| 'SUCCESS'
| 'ERROR'
| 'WARNING'
| 'INFO'
| 'QUESTION'
| 'NOTIFICATION'
// eslint-disable-next-line sonarjs/no-useless-intersection
| (string & {});
let STRATEGY:
| ((
type: SoundType,
context: readonly SounContextData[] | SoundContextUnknownType,
) => boolean)
| undefined;
/**
* Install strategy to decide the sound should played. **If a strategy is already registered this is a noop**
*
* @param strategy the strategy to decide if a sound should be played
* @returns block to unregister the strategy
*/
export function installSoundPlaybackStrategy(
strategy: (
type: SoundType,
context: readonly SounContextData[] | SoundContextUnknownType,
) => boolean,
): () => void {
if (STRATEGY !== undefined) {
console.error(
'Strategy already registered, please unregister the current strategy before installing a new one',
);
return () => {
// Noop
};
}
STRATEGY = strategy;
return () => {
STRATEGY = undefined;
};
}
/**
* play one of the predefined sounds
*
* @param type the predefined sound type
* @param metaData optional metaData with information about the origin, reason, ...
*/
export async function playSound(
type: SoundType,
context:
| readonly SounContextData[]
| SoundContextUnknownType = SoundContextUnknown,
): Promise<void> {
if (STRATEGY === undefined || STRATEGY(type, context)) {
await implPlaySound(type, false);
return Promise.resolve();
} else {
return Promise.resolve();
}
}
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.',
e,
);
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;
}
}
/**
* Check that the element is showing.
*
* The condition that an element is shown are:
* - its display value is not 'none'
* - its visibility value is not 'hidden'
* - unless the display value is 'contents' that its width and height are greater than 0 and `checkVisibility()` returns true
*
* @param el element to check
* @returns `true` if the element itself and all its ancestors are shown according to the rules above
*/
export function isElementShowing(el: Element) {
const style = getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden') {
return false;
}
if (style.display === 'contents') {
if (el.parentElement) {
if (el.parentElement.nodeName === 'BODY') {
return true;
}
return isElementShowing(el.parentElement);
}
return true;
}
if (
!el.checkVisibility({
contentVisibilityAuto: true,
opacityProperty: true,
visibilityProperty: true,
})
) {
return false;
}
const bounds = el.getBoundingClientRect();
if (bounds.width > 0 && bounds.height > 0) {
if (el.parentElement) {
if (el.parentElement.nodeName === 'BODY') {
return true;
}
return isElementShowing(el.parentElement);
}
return true;
}
return false;
}
|