bnomei/kirby3-janitor

View on GitHub
src/components/fields/Janitor.vue

Summary

Maintainability
Test Coverage
<template>
    <div class="janitor-wrapper">
        <k-button
            :id="id"
            :class="['janitor', button.state]"
            :icon="currentIcon"
            :command="command"
            :disabled="!unsaved && !isUnsaved && hasChanges"
            :style="buttonStyle"
            @click="runJanitor"
        >
            {{ button.label || label }}
        </k-button>
        <k-text
            v-if="button.help || help"
            theme="help"
            class="k-field-help"
            :html="button.help || help"
        />
        <a
            v-show="downloadRequest"
            ref="downloadAnchor"
            class="visually-hidden"
            :href="downloadRequest"
            download
        />
        <a
            v-show="urlRequest"
            ref="tabAnchor"
            class="visually-hidden"
            :href="urlRequest"
            target="_blank"
        />
    </div>
</template>

<script>
const STORAGE_ID = "janitor.runAfterAutosave";

export default {
    props: {
        autosave: Boolean,
        backgroundColor: String,
        clipboard: Boolean,
        color: String,
        confirm: String,
        command: String,
        cooldown: Number,
        error: String,
        icon: String,
        intab: Boolean,
        help: String,
        label: String,
        progress: String,
        success: String,
        status: String,
        unsaved: Boolean
    },

    data() {
        return {
            button: {
                label: null,
                state: null,
                help: null,
                style: null
            },
            clipboardRequest: null,
            downloadRequest: null,
            icons: {
                "is-running": "janitorLoader",
                "is-success": "check",
                "has-error": "alert"
            },
            isUnsaved: false,
            urlRequest: null
        };
    },

    computed: {
        buttonStyle() {
            return (
                this.button.style || {
                    color: this.color,
                    backgroundColor: this.backgroundColor
                }
            );
        },
        currentIcon() {
            return this.icons[this.status] ?? this.icon;
        },
        id() {
            return (
                "janitor-" +
                this.hashCode(
                    this.command + (this.button.label ?? "") + this.label
                )
            );
        },
        hasChanges() {
            return this.$store.getters["content/hasChanges"]();
        }
    },

    created() {
        this.$events.$on(
            "model.update",
            () => sessionStorage.getItem(STORAGE_ID) && location.reload()
        );

        if (sessionStorage.getItem(STORAGE_ID) === this.id) {
            sessionStorage.removeItem(STORAGE_ID);
            this.runJanitor();
        }
    },

    methods: {
        /**
         * Source: https://stackoverflow.com/a/8831937
         */
        hashCode(str) {
            let hash = 0;

            if (str.length === 0) {
                return hash;
            }

            for (const i of str) {
                hash = (hash << 5) - hash + str.charCodeAt(i);
                // convert to 32bit integer
                hash = hash & hash;
            }

            return hash;
        },

        async runJanitor() {
            if (this.confirm && !window.confirm(this.confirm)) {
                return;
            }

            if (this.autosave && this.hasChanges) {
                // lock janitor button, press save and listen to `model.update` event
                const saveButton = document.querySelector(
                    ".k-panel .k-form-buttons .k-view"
                ).lastChild;

                // revert & save
                if (saveButton) {
                    this.isUnsaved = false;
                    sessionStorage.setItem(STORAGE_ID, this.id);
                    this.simulateClick(saveButton);
                    return;
                }
            }

            if (this.clipboardRequest) {
                await this.copyToClipboard(this.clipboardRequest);
                this.resetButton();
                this.clipboardRequest = null;
                return;
            }

            if (this.status) {
                return;
            }

            this.postRequest("plugin-janitor", { command: this.command });
        },

        async postRequest(path, data) {
            this.button.label = this.progress ?? `${this.label} …`;
            this.button.state = "is-running";

            const {
                label,
                message,
                status,
                reload,
                open,
                download,
                clipboard,
                success,
                error,
                icon,
                help,
                color,
                backgroundColor,
                resetStyle
            } = await this.$api.post(path, data);

            if (status === 200) {
                this.button.label = success ?? this.success;
            } else {
                this.button.label = error ?? this.error;
            }

            if (label) {
                this.label = label;
            }

            if (message) {
                this.button.label = message;
            }

            if (help) {
                this.button.help = help;
            }

            if (icon) {
                this.icon = icon;
            }

            this.button.style = {
                color: "white",
                reset: true
            };
            if (status) {
                this.button.state = status === 200 ? "is-success" : "has-error";
                this.button.style.backgroundColor =
                    status === 200
                        ? "var(--color-positive)"
                        : "var(--color-negative-light)";
            } else {
                this.button.state = "has-response";
                this.button.style.backgroundColor = "var(--color-text)";
            }

            if (color) {
                this.button.style.reset = false;
                this.button.style.color = color;
            }

            if (backgroundColor) {
                this.button.style.reset = false;
                this.button.style.backgroundColor = backgroundColor;
            }

            if (resetStyle) {
                this.button.style.reset = resetStyle;
            }

            if (reload) {
                location.reload();
            }

            if (open) {
                if (this.intab) {
                    this.urlRequest = open;
                    this.$nextTick(() => {
                        this.simulateClick(this.$refs.tabAnchor);
                    });
                } else {
                    location.href = open;
                }
            }

            if (download) {
                this.downloadRequest = download;
                this.$nextTick(() => {
                    this.simulateClick(this.$refs.downloadAnchor);
                });
            }

            if (clipboard) {
                this.clipboardRequest = clipboard;
                this.button.label = this.progress;
                this.button.state = "is-success";

                setTimeout(this.resetButton, this.cooldown);

                this.$nextTick(() => {
                    this.copyToClipboard(this.clipboardRequest);
                });
            } else {
                setTimeout(this.resetButton, this.cooldown);
            }
        },

        resetButton() {
            this.button.label = null;
            this.button.state = null;
            this.button.style = this.button.style?.reset
                ? null
                : this.button.style;
        },

        simulateClick(element) {
            const evt = new MouseEvent("click", {
                bubbles: true,
                cancelable: true,
                view: window
            });

            element.dispatchEvent(evt);
        },

        async copyToClipboard(content) {
            try {
                await navigator.clipboard.writeText(content);
            } catch (err) {
                console.error("navigator.clipboard is not available");
            }
        }
    }
};
</script>

<style>
.janitor {
    background-color: var(--color-text);
    color: white;
    border-radius: 3px;
    padding: 0.5rem 1rem;
    line-height: 1.25rem;
    text-align: left;
}

.janitor:hover {
    background-color: #222;
}

.janitor .k-button-text {
    opacity: 1;
}

.janitor.is-running {
    background-color: var(--color-border) !important;
    color: white;
    cursor: wait;
}

.janitor[aria-disabled="true"] {
    background-color: var(--color-border) !important;
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    border: 0;
    padding: 0;
    margin: 0;
    clip-path: inset(50%);
    overflow: hidden;
    white-space: nowrap;
}
</style>