xref: /plugin/diagrams/script/DiagramsEditor.js (revision f8b6517f9a85fcc767800c35eae29f269205b16f)
1/**
2 * FIXME should messages be sent to diagramsEditor.contentWindow instead of diagramsEditor?
3 */
4class DiagramsEditor {
5    /** @type {HTMLIFrameElement} the editor iframe */
6    diagramsEditor = null;
7
8    /** @type {CallableFunction} the method to call for saving the diagram */
9    handleSave = null;
10
11    /**
12     * Initialize the editor for editing a media file
13     *
14     * @param {string} mediaid The media ID to edit, if 404 a new file will be created
15     */
16    async editMediaFile(mediaid) {
17        this.#createEditor();
18
19        this.handleSave = (svg) => this.#saveMediaFile(mediaid, svg);
20
21        const response = await fetch(DOKU_BASE + 'lib/exe/fetch.php?media=' + mediaid, {
22            method: 'GET',
23            cache: 'no-cache',
24        });
25
26        let svg = '';
27        if (response.ok) {
28            // if not 404, load the SVG data
29            svg = await response.text();
30        }
31
32        this.#loadDocument(svg);
33    }
34
35    /**
36     * Initialize the editor for editing an embedded diagram
37     *
38     * @param {string} pageid The page ID to on which the diagram is embedded
39     * @param {int} index The index of the diagram on the page (0-based)
40     */
41    async editEmbed(pageid, index) {
42        this.#createEditor();
43
44        this.handleSave = (svg) => this.#saveEmbed(pageid, index, svg);
45
46        const response = await fetch(DOKU_BASE + 'lib/exe/fetch.php?media=' + mediaid, {
47            method: 'GET',
48            cache: 'no-cache',
49        });
50
51        let svg = '';
52        if (response.ok) {
53            // if not 404, load the SVG data
54            svg = await response.text();
55        } else {
56            // a 404 for an embedded diagram should not happen
57            alert(LANG.plugins.diagrams.errorLoading);
58            this.#removeEditor();
59            return;
60        }
61
62        this.#loadDocument(svg);
63    }
64
65    /**
66     * Initialize the editor for editing a diagram in memory
67     *
68     * @param {string} svg The SVG raw data to edit, empty for new file
69     * @param {CallableFunction} callback The callback to call when the editor is closed
70     */
71    editMemory(svg, callback) {
72        this.#createEditor();
73        this.handleSave = callback;
74        this.#loadDocument(svg);
75    }
76
77    #saveMediaFile(mediaid, svg) {
78        // FIXME save to media file
79
80
81    }
82
83    #saveEmbed(pageid, index, svg) {
84        // FIXME save to page
85    }
86
87    /**
88     * Create the editor iframe and attach the message listener
89     */
90    #createEditor() {
91        this.diagramsEditor = document.createElement('iframe');
92        this.diagramsEditor.id = 'diagrams-frame';
93        this.diagramsEditor.style = {
94            border: 0,
95            position: 'fixed',
96            top: 0,
97            left: 0,
98            right: 0,
99            bottom: 0,
100            width: '100%',
101            height: '100%',
102            zIndex: 9999,
103        }; // FIXME assign these via CSS
104        this.diagramsEditor.src = JSINFO['plugins']['diagrams']['service_url'];
105        document.body.appendChild(this.diagramsEditor);
106        window.addEventListener('message', this.#handleMessage.bind(this)); // FIXME might need to be public
107    }
108
109    /**
110     * Remove the editor iframe and detach the message listener
111     */
112    #removeEditor() {
113        this.diagramsEditor.remove();
114        this.diagramsEditor = null;
115        window.removeDiagramsEditor(this.#handleMessage.bind(this));
116    }
117
118    /**
119     * Load the given SVG document into the editor
120     *
121     * @param {string} svg
122     */
123    #loadDocument(svg) {
124        this.diagramsEditor.postMessage(JSON.stringify({action: 'load', xml: svg}), '*');
125    }
126
127    /**
128     * Handle messages from diagramming service
129     *
130     * @param {Event} event
131     */
132    #handleMessage(event) {
133        // FIXME do we need this originalEvent here? or is that jQuery specific?
134        const msg = JSON.parse(event.originalEvent.data);
135
136        switch (msg.event) {
137            case 'init':
138                // FIXME do we need to wait for this? before we can call #loadDocument?
139                break;
140            case 'save':
141                // Save triggers an export to SVG action
142                this.diagramsEditor.postMessage(
143                    JSON.stringify({
144                        action: 'export',
145                        format: 'xmlsvg',
146                        spin: LANG.plugins.diagrams.saving
147                    }),
148                    '*'
149                );
150                break;
151            case 'export':
152                if (msg.format !== 'svg') {
153                    alert(LANG.plugins.diagrams.errorUnsupportedFormat);
154                    return;
155                }
156                this.handleSave(
157                    // FIXME we used to prepend a doctype, but doctypes are no longer recommended for SVG
158                    // FIXME we may need to add a XML header though?
159                    decodeURIComponent(atob(
160                        msg.data.split(',')[1])
161                        .split('')
162                        .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
163                        .join('')
164                    )
165                );
166                break;
167            case 'exit':
168                this.#removeEditor();
169                break;
170        }
171    }
172
173}
174