iobroker.kanban 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +224 -0
  3. package/admin/i18n/de.json +100 -0
  4. package/admin/i18n/en.json +100 -0
  5. package/admin/i18n/es.json +100 -0
  6. package/admin/i18n/fr.json +100 -0
  7. package/admin/i18n/it.json +100 -0
  8. package/admin/i18n/nl.json +100 -0
  9. package/admin/i18n/pl.json +100 -0
  10. package/admin/i18n/pt.json +100 -0
  11. package/admin/i18n/ru.json +100 -0
  12. package/admin/i18n/uk.json +100 -0
  13. package/admin/i18n/zh-cn.json +100 -0
  14. package/admin/jsonConfig.json +668 -0
  15. package/admin/kanban.png +0 -0
  16. package/io-package.json +299 -0
  17. package/lib/cron.js +203 -0
  18. package/lib/dueat.js +77 -0
  19. package/lib/events.js +24 -0
  20. package/lib/holidays.js +167 -0
  21. package/lib/i18n-server.js +301 -0
  22. package/lib/notify.js +627 -0
  23. package/lib/scheduler.js +198 -0
  24. package/lib/server.js +799 -0
  25. package/lib/store.js +1500 -0
  26. package/main.js +382 -0
  27. package/package.json +58 -0
  28. package/www/css/app.css +1022 -0
  29. package/www/i18n/de.json +238 -0
  30. package/www/i18n/en.json +238 -0
  31. package/www/i18n/es.json +238 -0
  32. package/www/i18n/fr.json +238 -0
  33. package/www/i18n/it.json +238 -0
  34. package/www/i18n/nl.json +238 -0
  35. package/www/i18n/pl.json +238 -0
  36. package/www/i18n/pt.json +238 -0
  37. package/www/i18n/ru.json +238 -0
  38. package/www/i18n/uk.json +238 -0
  39. package/www/i18n/zh-cn.json +238 -0
  40. package/www/index.html +171 -0
  41. package/www/js/api.js +98 -0
  42. package/www/js/app.js +411 -0
  43. package/www/js/board.js +983 -0
  44. package/www/js/colorpicker.js +165 -0
  45. package/www/js/dialogs.js +1419 -0
  46. package/www/js/i18n.js +43 -0
  47. package/www/vendor/marked.min.js +69 -0
  48. package/www/vendor/purify.min.js +3 -0
  49. package/www/vendor/sortable.min.js +2 -0
@@ -0,0 +1,165 @@
1
+ // Eigener eingebetteter Colorpicker – überall identisch, voller Farbraum,
2
+ // touch-tauglich (der native <input type=color> zeigt auf Mobilgeräten oft
3
+ // nur eine kleine Palette). SV-Feld + Hue-Slider + Hex-Eingabe + Presets.
4
+
5
+ import { t } from './i18n.js';
6
+
7
+ function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
8
+
9
+ function hsvToRgb(h, s, v) {
10
+ h = (h % 360 + 360) % 360; s = clamp(s, 0, 1); v = clamp(v, 0, 1);
11
+ const c = v * s, x = c * (1 - Math.abs((h / 60) % 2 - 1)), m = v - c;
12
+ let r = 0, g = 0, b = 0;
13
+ if (h < 60) [r, g, b] = [c, x, 0];
14
+ else if (h < 120) [r, g, b] = [x, c, 0];
15
+ else if (h < 180) [r, g, b] = [0, c, x];
16
+ else if (h < 240) [r, g, b] = [0, x, c];
17
+ else if (h < 300) [r, g, b] = [x, 0, c];
18
+ else [r, g, b] = [c, 0, x];
19
+ return { r: Math.round((r + m) * 255), g: Math.round((g + m) * 255), b: Math.round((b + m) * 255) };
20
+ }
21
+
22
+ function rgbToHsv(r, g, b) {
23
+ r /= 255; g /= 255; b /= 255;
24
+ const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
25
+ let h = 0;
26
+ if (d) {
27
+ if (max === r) h = ((g - b) / d) % 6;
28
+ else if (max === g) h = (b - r) / d + 2;
29
+ else h = (r - g) / d + 4;
30
+ h *= 60; if (h < 0) h += 360;
31
+ }
32
+ return { h, s: max ? d / max : 0, v: max };
33
+ }
34
+
35
+ function hexToRgb(hex) {
36
+ const m = /^#?([0-9a-f]{6})$/i.exec(String(hex || '').trim());
37
+ if (!m) return null;
38
+ const n = parseInt(m[1], 16);
39
+ return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
40
+ }
41
+
42
+ function rgbToHex(r, g, b) {
43
+ return '#' + [r, g, b].map(x => clamp(Math.round(x), 0, 255).toString(16).padStart(2, '0')).join('');
44
+ }
45
+
46
+ let openPop = null; // aktuell offenes Popover (nur eins gleichzeitig)
47
+
48
+ /**
49
+ * @param anchorEl Element, an dem das Popover ausgerichtet wird
50
+ * @param current aktuelle Farbe ('' = keine)
51
+ * @param onPick (color) => void – live bei jeder Änderung
52
+ * @param opts { presets?: string[], allowNone?: bool }
53
+ */
54
+ export function openColorPicker(anchorEl, current, onPick, opts = {}) {
55
+ closeColorPicker();
56
+ const presets = opts.presets || [];
57
+ const start = hexToRgb(current) || { r: 126, g: 87, b: 194 };
58
+ let hsv = rgbToHsv(start.r, start.g, start.b);
59
+
60
+ const pop = document.createElement('div');
61
+ pop.className = 'cp-pop';
62
+
63
+ const sv = document.createElement('div'); sv.className = 'cp-sv';
64
+ const svCur = document.createElement('div'); svCur.className = 'cp-cursor'; sv.appendChild(svCur);
65
+ const hue = document.createElement('div'); hue.className = 'cp-hue';
66
+ const hueCur = document.createElement('div'); hueCur.className = 'cp-hue-cursor'; hue.appendChild(hueCur);
67
+
68
+ const row = document.createElement('div'); row.className = 'cp-row';
69
+ const preview = document.createElement('span'); preview.className = 'cp-preview';
70
+ const hex = document.createElement('input'); hex.type = 'text'; hex.className = 'cp-hex';
71
+ hex.maxLength = 7; hex.spellcheck = false;
72
+ row.append(preview, hex);
73
+
74
+ pop.append(sv, hue, row);
75
+
76
+ if (presets.length) {
77
+ const pr = document.createElement('div'); pr.className = 'cp-presets';
78
+ for (const c of presets) {
79
+ const s = document.createElement('span');
80
+ s.className = 'cp-swatch' + (c ? '' : ' none');
81
+ if (c) s.style.background = c;
82
+ s.title = c || t('color.none');
83
+ s.addEventListener('click', () => {
84
+ if (!c) { emit(''); render(); return; }
85
+ const rgb = hexToRgb(c); hsv = rgbToHsv(rgb.r, rgb.g, rgb.b); render(); emit(c);
86
+ });
87
+ pr.appendChild(s);
88
+ }
89
+ pop.appendChild(pr);
90
+ }
91
+
92
+ function currentHex() {
93
+ const { r, g, b } = hsvToRgb(hsv.h, hsv.s, hsv.v);
94
+ return rgbToHex(r, g, b);
95
+ }
96
+ function emit(val) { onPick(val !== undefined ? val : currentHex()); }
97
+ function render() {
98
+ const hueRgb = hsvToRgb(hsv.h, 1, 1);
99
+ sv.style.background =
100
+ `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, ${rgbToHex(hueRgb.r, hueRgb.g, hueRgb.b)})`;
101
+ svCur.style.left = (hsv.s * 100) + '%';
102
+ svCur.style.top = ((1 - hsv.v) * 100) + '%';
103
+ hueCur.style.left = (hsv.h / 360 * 100) + '%';
104
+ const hx = currentHex();
105
+ preview.style.background = hx;
106
+ svCur.style.background = hx;
107
+ if (document.activeElement !== hex) hex.value = hx;
108
+ }
109
+
110
+ // ---- Pointer-Tracking für SV-Feld und Hue-Slider (touch-tauglich) ----
111
+ function track(el, handler) {
112
+ const move = e => {
113
+ const r = el.getBoundingClientRect();
114
+ const x = clamp((e.clientX - r.left) / r.width, 0, 1);
115
+ const y = clamp((e.clientY - r.top) / r.height, 0, 1);
116
+ handler(x, y); render(); emit();
117
+ };
118
+ el.addEventListener('pointerdown', e => {
119
+ e.preventDefault(); el.setPointerCapture(e.pointerId); move(e);
120
+ const mv = ev => move(ev);
121
+ const up = ev => { el.releasePointerCapture(e.pointerId); el.removeEventListener('pointermove', mv); el.removeEventListener('pointerup', up); };
122
+ el.addEventListener('pointermove', mv); el.addEventListener('pointerup', up);
123
+ });
124
+ }
125
+ track(sv, (x, y) => { hsv.s = x; hsv.v = 1 - y; });
126
+ track(hue, x => { hsv.h = x * 360; });
127
+
128
+ hex.addEventListener('input', () => {
129
+ const rgb = hexToRgb(hex.value);
130
+ if (rgb) { hsv = rgbToHsv(rgb.r, rgb.g, rgb.b); render(); emit(rgbToHex(rgb.r, rgb.g, rgb.b)); }
131
+ });
132
+
133
+ // In einen offenen modalen <dialog> hängen (top-layer), sonst an den body –
134
+ // sonst läge das fixed-Popover hinter dem Dialog-Backdrop.
135
+ const host = anchorEl.closest('dialog[open]') || document.body;
136
+ host.appendChild(pop);
137
+ // Positionierung nahe des Ankers, im Viewport gehalten
138
+ const a = anchorEl.getBoundingClientRect();
139
+ const pw = pop.offsetWidth, ph = pop.offsetHeight;
140
+ let left = clamp(a.left, 6, window.innerWidth - pw - 6);
141
+ let top = a.bottom + 6;
142
+ if (top + ph > window.innerHeight - 6) top = Math.max(6, a.top - ph - 6);
143
+ pop.style.left = left + 'px';
144
+ pop.style.top = top + 'px';
145
+ render();
146
+
147
+ // Schließen bei Klick/Touch außerhalb
148
+ const onDown = e => { if (!pop.contains(e.target) && e.target !== anchorEl) closeColorPicker(); };
149
+ setTimeout(() => document.addEventListener('pointerdown', onDown, true), 0);
150
+ openPop = { pop, onDown };
151
+ return pop;
152
+ }
153
+
154
+ export function closeColorPicker() {
155
+ if (!openPop) return;
156
+ document.removeEventListener('pointerdown', openPop.onDown, true);
157
+ openPop.pop.remove();
158
+ openPop = null;
159
+ }
160
+
161
+ /** Ist gerade ein Farbwähler offen? Dialoge fragen das bei Escape ab, damit die
162
+ * Taste erst das Popover schliesst und nicht gleich den ganzen Dialog verwirft. */
163
+ export function colorPickerOpen() {
164
+ return !!openPop;
165
+ }