« get me outta code hell

client-util.js « js « static « src - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/static/js/client-util.js
blob: 5a35bcf2f2286ee0ff8755df4eac4459413c8742 (plain)
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
/* eslint-env browser */

export function rebase(href, rebaseKey = 'rebaseLocalized') {
  let result = document.documentElement.dataset[rebaseKey] || './';

  if (!result.endsWith('/')) {
    result += '/';
  }

  if (href.startsWith('/')) {
    href = href.slice(1);
  }

  result += href;

  return result;
}

export function cssProp(el, ...args) {
  if (typeof args[0] === 'string' && args.length === 1) {
    return getComputedStyle(el).getPropertyValue(args[0]).trim();
  }

  if (typeof args[0] === 'string' && args.length === 2) {
    if (args[1] === null) {
      el.style.removeProperty(args[0]);
    } else {
      el.style.setProperty(args[0], args[1]);
    }
    return;
  }

  if (typeof args[0] === 'object') {
    for (const [property, value] of Object.entries(args[0])) {
      cssProp(el, property, value);
    }
  }
}

export function templateContent(el) {
  if (el === null) {
    return null;
  }

  if (el?.nodeName !== 'TEMPLATE') {
    throw new Error(`Expected a <template> element`);
  }

  return el.content.cloneNode(true);
}

// Curry-style, so multiple points can more conveniently be tested at once.
export function pointIsOverAnyOf(elements) {
  return (clientX, clientY) => {
    const element = document.elementFromPoint(clientX, clientY);
    return elements.some(el => el.contains(element));
  };
}

export function getVisuallyContainingElement(child) {
  let parent = child.parentElement;

  while (parent) {
    if (
      cssProp(parent, 'overflow') === 'hidden' ||
      cssProp(parent, 'contain') === 'paint'
    ) {
      return parent;
    }

    parent = parent.parentElement;
  }

  return null;
}

// TODO: These should pro8a8ly access some shared urlSpec path. We'd need to
// separ8te the tooling around that into common-shared code too.

/*
const getLinkHref = (type, directory) => rebase(`${type}/${directory}`);
*/

export const openAlbum = d => rebase(`album/${d}`);
export const openArtTag = d => rebase(`tag/${d}`);
export const openArtist = d => rebase(`artist/${d}`);
export const openFlash = d => rebase(`flash/${d}`);
export const openGroup = d => rebase(`group/${d}`);
export const openTrack = d => rebase(`track/${d}`);

// TODO: This should also use urlSpec.

/*
export function fetchData(type, directory) {
  return fetch(rebase(`${type}/${directory}/data.json`, 'rebaseData')).then(
    (res) => res.json()
  );
}
*/

// TODO: This should probably be imported from another file.
export function dispatchInternalEvent(event, eventName, ...args) {
  const info = event[Symbol.for('hsmusic.clientInfo')];

  if (!info) {
    throw new Error(`Expected event to be stored on clientInfo`);
  }

  const infoName = info.id;

  const {[eventName]: listeners} = event;

  if (!listeners) {
    throw new Error(`Event name "${eventName}" isn't stored on ${infoName}.event`);
  }

  let results = [];
  for (const listener of listeners) {
    try {
      results.push(listener(...args));
    } catch (error) {
      console.error(`Uncaught error in listener for ${infoName}.${eventName}`);
      console.error(error);
      results.push(undefined);
    }
  }

  return results;
}

const languageCode = document.documentElement.getAttribute('lang');

export function formatDate(inputDate) {
  const date = new Date(inputDate);
  return date.toLocaleDateString(languageCode);
}