broadlistening-viewer 0.2.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.
@@ -0,0 +1,108 @@
1
+ import { icon, escapeHtml } from "./decidim_core_shim";
2
+ import { t } from "./i18n";
3
+ import Toolbar, { VIEW_MODES } from "./toolbar";
4
+ class FullscreenModal {
5
+ constructor(options = {}) {
6
+ this.options = {
7
+ viewMode: VIEW_MODES.SCATTER_ALL,
8
+ hasDensityData: false,
9
+ isDenseGroupEnabled: true,
10
+ ...options
11
+ };
12
+ this.isOpen = false;
13
+ }
14
+ open() {
15
+ if (this.isOpen) return;
16
+ this.isOpen = true;
17
+ this.toolbar = new Toolbar({
18
+ viewMode: this.options.viewMode,
19
+ hasDensityData: this.options.hasDensityData,
20
+ isDenseGroupEnabled: this.options.isDenseGroupEnabled,
21
+ showSettings: false,
22
+ showFullscreen: false,
23
+ onViewModeChange: (mode) => {
24
+ this.options.onViewModeChange?.(mode);
25
+ this.updateToolbarState({ viewMode: mode });
26
+ }
27
+ });
28
+ this.modal = document.createElement("div");
29
+ this.modal.className = "fixed inset-0 z-[9999] flex flex-col bg-white";
30
+ this.modal.innerHTML = `
31
+ <div data-blv="fullscreen-header" class="flex justify-between items-center px-4 py-3 border-b border-gray-200 bg-gray-50 shrink-0">
32
+ ${this.toolbar.render()}
33
+ <button class="flex items-center justify-center w-10 h-10 p-0 bg-transparent border-none rounded-lg cursor-pointer text-gray-500 transition-all duration-150 hover:bg-gray-200 hover:text-gray-900 [&_svg]:w-6 [&_svg]:h-6" data-action="close" title="${escapeHtml(t("common.close"))}">
34
+ ${icon("close-line")}
35
+ </button>
36
+ </div>
37
+ <div data-blv="fullscreen-breadcrumb" class="px-4"></div>
38
+ <div class="flex-1 overflow-hidden p-4">
39
+ <div data-blv="chart-plot" class="w-full h-full"></div>
40
+ </div>
41
+ `;
42
+ document.body.appendChild(this.modal);
43
+ document.body.style.overflow = "hidden";
44
+ this.bindEvents();
45
+ this.renderBreadcrumb();
46
+ this.renderChart();
47
+ }
48
+ bindEvents() {
49
+ if (!this.modal) return;
50
+ const headerContainer = this.modal.querySelector('[data-blv="fullscreen-header"]');
51
+ this.toolbar.bindEvents(headerContainer);
52
+ this.modal.querySelector("[data-action='close']").addEventListener("click", () => {
53
+ this.close();
54
+ });
55
+ this._escapeHandler = (e) => {
56
+ if (e.key === "Escape") {
57
+ this.close();
58
+ }
59
+ };
60
+ document.addEventListener("keydown", this._escapeHandler);
61
+ }
62
+ getChartContainer() {
63
+ if (!this.modal) return void 0;
64
+ return this.modal.querySelector('[data-blv="chart-plot"]');
65
+ }
66
+ getBreadcrumbContainer() {
67
+ if (!this.modal) return void 0;
68
+ return this.modal.querySelector('[data-blv="fullscreen-breadcrumb"]');
69
+ }
70
+ renderChart() {
71
+ if (!this.modal || !this.options.renderChart) return;
72
+ const container = this.getChartContainer();
73
+ if (container) {
74
+ container.innerHTML = "";
75
+ this.options.renderChart(container);
76
+ }
77
+ }
78
+ renderBreadcrumb() {
79
+ if (!this.modal || !this.options.renderBreadcrumb) return;
80
+ const container = this.getBreadcrumbContainer();
81
+ if (container) {
82
+ this.options.renderBreadcrumb(container);
83
+ }
84
+ }
85
+ updateToolbarState(state) {
86
+ if (!this.modal || !this.toolbar) return;
87
+ const headerContainer = this.modal.querySelector('[data-blv="fullscreen-header"]');
88
+ this.toolbar.updateState(headerContainer, state);
89
+ Object.assign(this.options, state);
90
+ }
91
+ close() {
92
+ if (!this.isOpen) return;
93
+ this.isOpen = false;
94
+ if (this.modal) {
95
+ document.body.removeChild(this.modal);
96
+ this.modal = void 0;
97
+ }
98
+ document.body.style.overflow = "";
99
+ if (this._escapeHandler) {
100
+ document.removeEventListener("keydown", this._escapeHandler);
101
+ this._escapeHandler = void 0;
102
+ }
103
+ this.options.onClose?.();
104
+ }
105
+ }
106
+ export {
107
+ FullscreenModal as default
108
+ };
data/js/shared/i18n.js ADDED
@@ -0,0 +1,37 @@
1
+ const getViewMessages = () => {
2
+ const element = document.getElementById("broadlistening-view-i18n");
3
+ if (!element || !element.dataset.messages) {
4
+ return {};
5
+ }
6
+ try {
7
+ return JSON.parse(element.dataset.messages);
8
+ } catch (e) {
9
+ console.error("[broadlistening_view] Failed to parse i18n messages:", e);
10
+ return {};
11
+ }
12
+ };
13
+ const t = (key, interpolations = {}) => {
14
+ const messages = getViewMessages();
15
+ let value = messages;
16
+ for (const part of key.split(".")) {
17
+ if (value && typeof value === "object") {
18
+ value = value[part];
19
+ } else {
20
+ value = void 0;
21
+ break;
22
+ }
23
+ }
24
+ if (value == null) {
25
+ console.warn(`[broadlistening_view] Missing translation: ${key}`);
26
+ return key;
27
+ }
28
+ if (typeof value === "string" && Object.keys(interpolations).length > 0) {
29
+ return value.replace(/%\{(\w+)\}/g, (match, name) => {
30
+ return interpolations[name] !== void 0 ? String(interpolations[name]) : match;
31
+ });
32
+ }
33
+ return String(value);
34
+ };
35
+ export {
36
+ t
37
+ };