xref: /plugin/diagrams/script/DiagramsForm.js (revision b30cbf767a5dc6dca1f946a5f761282e43bc7419)
1/**
2 * ProseMirror Form for editing diagram attribute
3 */
4class DiagramsForm extends KeyValueForm {
5
6    #attributes = {
7        id: '',
8        svg: '',
9        type: '',
10        title: '',
11        url: '',
12        width: null,
13        height: null,
14        align: ''
15    };
16
17    #onsubmitCB = null;
18    #oncloseCB = null;
19
20    /**
21     * Initialize the KeyValue form with fields and event handlers
22     */
23    constructor(attributes, onsubmit, onclose = null) {
24        const name = LANG.plugins.diagrams.formtitle;
25        const fields = DiagramsForm.#getFields(attributes);
26
27        super(name, fields);
28        this.#attributes = {
29            ...this.#attributes,
30            ...attributes
31        };
32        this.#onsubmitCB = onsubmit;
33        this.#oncloseCB = onclose;
34
35        // attach handlers
36        this.$form.on('submit', (event) => {
37            event.preventDefault(); // prevent form submission
38            this.#onsubmitCB(this.#attributes);
39            this.hide(); // close dialog
40        });
41
42        this.$form.on('dialogclose', (event) => {
43            if (this.#oncloseCB) this.#oncloseCB();
44            this.destroy();
45        });
46
47        this.$form.on('change', 'input,select', this.updateInternalState.bind(this));
48
49        this.#getButtonsMediaManager(this.#attributes);
50        this.#getButtonsEditor(this.#attributes);
51
52        this.updateFormState();
53    }
54
55    #getButtonsEditor(attributes) {
56        if (attributes.type === 'embed' || attributes.id) {
57            const editButton = document.createElement('button');
58            editButton.className = 'diagrams-btn-edit';
59            editButton.id = 'diagrams__btn-edit';
60            editButton.innerText = LANG.plugins.diagrams.editButton;
61            editButton.type = 'button';
62            this.$form.find('fieldset').prepend(editButton);
63
64            editButton.addEventListener('click', event => {
65                event.preventDefault(); // prevent form submission
66
67                if (attributes.type === 'mediafile') {
68                    const diagramsEditor = new DiagramsEditor(this.onSavedMediaFile.bind(this, attributes.id));
69                    diagramsEditor.editMediaFile(attributes.id);
70                } else {
71                    const diagramsEditor = new DiagramsEditor();
72                    diagramsEditor.editMemory(attributes.svg, this.onSaveEmbed.bind(this));
73                }
74            });
75        }
76    }
77
78    #getButtonsMediaManager(attributes) {
79        // media manager button
80        if (attributes.type === 'mediafile') {
81            const selectButton = document.createElement('button');
82            selectButton.innerText = LANG.plugins.diagrams.selectSource;
83            selectButton.className = 'diagrams-btn-select';
84            selectButton.type = 'button';
85            selectButton.addEventListener('click', () =>
86                window.open(
87                    `${DOKU_BASE}lib/exe/mediamanager.php?ns=${encodeURIComponent(JSINFO.namespace)}&onselect=dMediaSelect`,
88                    'mediaselect',
89                    'width=750,height=500,left=20,top=20,scrollbars=yes,resizable=yes',
90                )
91            );
92            this.$form.find('fieldset').prepend(selectButton);
93            window.dMediaSelect = this.mediaSelect.bind(this); // register as global function
94        }
95    }
96
97    /**
98     * Define form fields depending on type
99     * @returns {object}
100     */
101    static #getFields(attributes) {
102        const fields = [
103            {
104                type: 'select', 'label': LANG.plugins.diagrams.alignment, 'name': 'align', 'options':
105                    [
106                        {value: '', label: ''},
107                        {value: 'left', label: LANG.plugins.diagrams.left},
108                        {value: 'right', label: LANG.plugins.diagrams.right},
109                        {value: 'center', label: LANG.plugins.diagrams.center}
110                    ]
111            },
112            {
113                label: LANG.plugins.diagrams.title, name: 'title'
114            }
115        ];
116
117        if (attributes.type === 'mediafile') {
118            fields.unshift(
119                {
120                    label: LANG.plugins.diagrams.mediaSource,
121                    name: 'id'
122                }
123            );
124        }
125        return fields;
126    }
127
128    /**
129     * Updates the form to reflect the current internal attributes
130     */
131    updateFormState() {
132        for (const [key, value] of Object.entries(this.#attributes)) {
133            this.$form.find('[name="' + key + '"]').val(value);
134        }
135        this.updateInternalUrl();
136    }
137
138    /**
139     * Update the internal attributes to reflect the current form state
140     */
141    updateInternalState() {
142        for (const [key, value] of Object.entries(this.#attributes)) {
143            const $elem = this.$form.find('[name="' + key + '"]');
144            if ($elem.length) {
145                this.#attributes[key] = $elem.val();
146            }
147        }
148        this.updateInternalUrl();
149    }
150
151    /**
152     * Calculate the Display URL for the current mediafile or SVG embed
153     */
154    updateInternalUrl() {
155        if (this.#attributes.type === 'embed') {
156            this.#attributes.url = 'data:image/svg+xml;base64,' + btoa(this.#attributes.svg);
157        } else {
158            this.#attributes.url = `${DOKU_BASE}lib/exe/fetch.php?media=${this.#attributes.id}`;
159        }
160    }
161
162    /**
163     * After svaing a media file reload the src for all images using it
164     *
165     * @see https://stackoverflow.com/a/66312176
166     * @param {string} mediaid
167     * @returns {Promise<void>}
168     */
169    async onSavedMediaFile(mediaid) {
170        const url = `${DOKU_BASE}lib/exe/fetch.php?cache=nocache&media=${mediaid}`;
171        await fetch(url, {cache: 'reload', mode: 'no-cors'});
172        document.body.querySelectorAll(`img[src='${url}']`)
173            .forEach(img => img.src = url)
174    }
175
176    /**
177     * Save an embedded diagram back to the editor
178     */
179    onSaveEmbed(svg) {
180        this.#attributes.svg = svg;
181        this.updateFormState();
182        return true;
183    }
184
185    /**
186     * Callback called from the media popup on selecting a file
187     *
188     * This is globally registered as window.dMediaSelect
189     *
190     * @todo if the given mediaid is not a diagram we need to show an error and ignore it
191     * @param {string} edid ignored
192     * @param {string} mediaid the picked media ID
193     */
194    mediaSelect(edid, mediaid) {
195        this.#attributes.id = mediaid;
196        this.updateFormState();
197    }
198}
199