components_share_dialog.js

/**
 * Modal dialog for managing project presentation sharing.
 * Allows owners to toggle "Anyone with link" access and copy the presentation URL.
 */
export class ShareDialog {
    /**
     * @param {string} projectId - The ID of the project to share.
     * @param {object} server - Server instance (for token + baseUrl)
     */
    constructor(projectId, server) {
        this.projectId = projectId;
        this.server    = server;
        this._anyWithLink = false;

        this._buildDOM();
        document.body.appendChild(this.root);
        this._load();
    }

    /** Builds the dialog DOM and appends it to the document body. */
    _buildDOM() {
        // Scrim
        this.scrim = document.createElement('div');
        this.scrim.className = 'share-scrim';
        this.scrim.addEventListener('click', () => this.close());

        // Dialog
        this.root = document.createElement('div');
        this.root.className = 'share-dialog';
        this.root.innerHTML = `
            <div class="share-dialog-header">
                <h2 class="share-dialog-title">Share Presentation</h2>
                <button class="share-close-btn" title="Close">&#x2715;</button>
            </div>
            <div class="share-dialog-body">
                <div class="share-section">
                    <div class="share-row">
                        <div class="share-row-text">
                            <span class="share-label">Anyone with link</span>
                            <span class="share-desc">Anyone who has this link can view the presentation without signing in.</span>
                        </div>
                        <label class="share-toggle">
                            <input type="checkbox" id="shareAnyWithLink" />
                            <span class="share-toggle-track"></span>
                        </label>
                    </div>
                </div>
                <div class="share-link-section" id="shareLinkSection" style="display:none">
                    <div class="share-link-label">Presentation link</div>
                    <div class="share-link-row">
                        <input class="share-link-input" id="shareLinkInput" type="text" readonly />
                        <button class="share-link-copy" id="shareLinkCopy">Copy</button>
                    </div>
                </div>
                <div class="share-actions">
                    <button class="share-open-btn" id="shareOpenBtn">Open Presentation</button>
                </div>
            </div>
            <div class="share-status" id="shareStatus" style="display:none"></div>
        `;

        document.body.appendChild(this.scrim);

        this.root.querySelector('.share-close-btn').addEventListener('click', () => this.close());
        this._toggle    = this.root.querySelector('#shareAnyWithLink');
        this._linkSection = this.root.querySelector('#shareLinkSection');
        this._linkInput = this.root.querySelector('#shareLinkInput');
        this._copyBtn   = this.root.querySelector('#shareLinkCopy');
        this._openBtn   = this.root.querySelector('#shareOpenBtn');
        this._status    = this.root.querySelector('#shareStatus');

        const presUrl = `${window.location.origin}/presentation/${this.projectId}`;
        this._linkInput.value = presUrl;

        this._toggle.addEventListener('change', () => this._onToggle());
        this._copyBtn.addEventListener('click',  () => this._copyLink());
        this._openBtn.addEventListener('click',  () => window.open(presUrl, '_blank'));
    }

    /** Fetches the current sharing permissions from the server and updates the UI. */
    async _load() {
        try {
            const token = await this.server.getToken();
            const base  = this.server.baseUrl;
            const res   = await fetch(`${base}/api/projects/${this.projectId}/permissions`, {
                headers: token ? { 'X-Auth-Token': token } : {},
                credentials: 'include',
            });
            if (!res.ok) throw new Error();
            const perms = await res.json();
            this._anyWithLink = !!perms.any_with_link;
        } catch {
            this._anyWithLink = false;
        }
        this._applyState();
    }

    /** Syncs the toggle and link-section visibility to the current `_anyWithLink` state. */
    _applyState() {
        this._toggle.checked = this._anyWithLink;
        this._linkSection.style.display = this._anyWithLink ? '' : 'none';
    }

    /** Handles the "Anyone with link" toggle change: PUTs updated permissions to the server. */
    async _onToggle() {
        const newVal = this._toggle.checked;
        this._toggle.disabled = true;
        try {
            const token = await this.server.getToken();
            const base  = this.server.baseUrl;

            // GET current permissions first
            const getRes = await fetch(`${base}/api/projects/${this.projectId}/permissions`, {
                headers: token ? { 'X-Auth-Token': token } : {},
                credentials: 'include',
            });
            const currentPerms = getRes.ok ? await getRes.json() : {};

            // Merge change
            const updated = { ...currentPerms, any_with_link: newVal };
            const putRes = await fetch(`${base}/api/projects/${this.projectId}/permissions`, {
                method:  'PUT',
                headers: {
                    'Content-Type': 'application/json',
                    ...(token ? { 'X-Auth-Token': token } : {}),
                },
                credentials: 'include',
                body: JSON.stringify({ permissions: updated }),
            });
            if (!putRes.ok) throw new Error();
            this._anyWithLink = newVal;
            this._applyState();
        } catch {
            // Revert toggle on failure
            this._toggle.checked = this._anyWithLink;
            this._showStatus('Could not update sharing settings.', true);
        } finally {
            this._toggle.disabled = false;
        }
    }

    /** Copies the presentation link to the clipboard and briefly updates the button label. */
    _copyLink() {
        navigator.clipboard.writeText(this._linkInput.value).then(() => {
            this._copyBtn.textContent = 'Copied!';
            setTimeout(() => { this._copyBtn.textContent = 'Copy'; }, 2000);
        });
    }

    /**
     * Briefly displays a status message at the bottom of the dialog.
     * @param {string} msg - The message to display.
     * @param {boolean} [isError=false] - If true, renders the message in error colour.
     */
    _showStatus(msg, isError = false) {
        this._status.textContent = msg;
        this._status.style.display = '';
        this._status.style.color = isError ? 'var(--danger)' : 'var(--success)';
        setTimeout(() => { this._status.style.display = 'none'; }, 3000);
    }

    /** Removes the dialog and scrim from the DOM. */
    close() {
        this.root.remove();
        this.scrim.remove();
    }
}