なん愛ch お絵描き機能

なんIにお絵かき機能を追加するユーザースクリプトです。

// ==UserScript==
// @name         なん愛ch お絵描き機能
// @namespace    http://tampermonkey.net/
// @version      1.0.5
// @description  なんIにお絵かき機能を追加するユーザースクリプトです。
// @author       You
// @match        https://openlive2ch.pages.dev/*
// @grant        GM.addStyle
// @grant        GM.addElement
// @license      MIT
// ==/UserScript==

(async function() {
    'use strict';
    const [
        mdi,
        oekaki
    ] = await Promise.all([
        'https://cdn.jsdelivr.net/npm/@mdi/[email protected]/mdi.js',
        'https://cdn.jsdelivr.net/npm/@onjmin/oekaki/dist/index.min.mjs'
    ].map(v => import(v)));

    GM.addStyle(`
        .grid .upper-canvas {
            opacity: 0.4;
            background-image: linear-gradient(to right, gray 1px, transparent 1px),
                              linear-gradient(to bottom, gray 1px, transparent 1px);
            background-size: var(--grid-cell-size) var(--grid-cell-size);
        }
        .tool-options {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 10px;
        }
        .size-display {
            width: 50px;
            text-align: center;
        }
        .slider-wrapper {
            flex-grow: 1;
        }
        .slider-input {
            width: 100%;
        }
        .palette {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            border: 2px solid #fff;
            cursor: pointer;
            padding: 0;
        }
    `);

    const uploadImageBtn = document.getElementById('upload-image-btn');
    if (!uploadImageBtn) {
        console.error('#upload-image-btn not found.');
        return;
    }

    const parent = uploadImageBtn.parentElement;
    if (parent) {
        const oekakiButton = await GM.addElement(parent, 'button', {
            id: 'oekaki-btn',
            type: 'button',
            class: 'smallbtn',
            textContent: 'お絵描き'
        });

        const imagePreview = document.getElementById('image-preview');
        const imageThumbnail = document.getElementById('image-thumbnail');

        if (!imagePreview || !imageThumbnail) {
            console.error('#image-preview or #image-thumbnail not found.');
            return;
        }

        const oekakiUIWrapper = document.createElement('div');
        oekakiUIWrapper.id = 'oekaki-ui-wrapper';
        oekakiUIWrapper.style.display = 'none';
        oekakiUIWrapper.style.position = 'fixed';
        oekakiUIWrapper.style.top = '0';
        oekakiUIWrapper.style.left = '0';
        oekakiUIWrapper.style.width = '100vw';
        oekakiUIWrapper.style.height = '100vh';
        oekakiUIWrapper.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
        oekakiUIWrapper.style.zIndex = '9999';
        oekakiUIWrapper.style.flexDirection = 'column';
        oekakiUIWrapper.style.justifyContent = 'center';
        oekakiUIWrapper.style.alignItems = 'center';
        oekakiUIWrapper.style.color = '#fff';
        document.body.appendChild(oekakiUIWrapper);

        const toolPanel = document.createElement('div');
        toolPanel.style.padding = '10px';
        toolPanel.style.backgroundColor = '#333';
        toolPanel.style.borderRadius = '5px';
        toolPanel.style.display = 'flex';
        toolPanel.style.flexWrap = 'wrap';
        toolPanel.style.gap = '10px';
        toolPanel.style.marginBottom = '10px';
        oekakiUIWrapper.appendChild(toolPanel);

        const toolOptionsPanel = document.createElement('div');
        toolOptionsPanel.className = 'tool-options';
        oekakiUIWrapper.appendChild(toolOptionsPanel);

        const layerPanel = document.createElement('div');
        layerPanel.style.padding = '10px';
        layerPanel.style.backgroundColor = '#333';
        layerPanel.style.borderRadius = '5px';
        layerPanel.style.position = 'absolute';
        layerPanel.style.top = '10px';
        layerPanel.style.right = '10px';
        layerPanel.style.zIndex = '10000';
        layerPanel.style.display = 'flex';
        layerPanel.style.flexDirection = 'column';
        layerPanel.style.gap = '5px';
        layerPanel.style.maxHeight = '80vh';
        layerPanel.style.overflowY = 'auto';
        oekakiUIWrapper.appendChild(layerPanel);

        const oekakiContainer = document.createElement('div');
        oekakiContainer.style.border = '1px solid white';
        oekakiContainer.style.boxSizing = 'content-box';
        oekakiUIWrapper.appendChild(oekakiContainer);

        let activeLayer = null;
        let oekakiInitialized = false;
        let width, height;
        let choicedTool = 'ペン'; // 初期値をペンに設定
        let isErasable = false;
        let isFlipped = false;
        let isGrid = false;
        let currentColor = '#000000';
        let brushSize = 16;
        let penSize = 4;
        let eraserSize = 16;
        let dotPenScale = 1;
        oekaki.color.value = currentColor;
        oekaki.penSize.value = penSize;
        oekaki.brushSize.value = brushSize;
        oekaki.eraserSize.value = eraserSize;

        const recentColors = ['#000000', '#FFFFFF'];
        const addRecentColors = () => {
            if (choicedTool === tools.translate.label) return;
            const idx = recentColors.indexOf(currentColor);
            if (idx === 0) return;
            if (idx !== -1) recentColors.splice(idx, 1);
            recentColors.unshift(currentColor);
            if (recentColors.length > 8) recentColors.pop();
        };

        const updateToolOptions = () => {
            toolOptionsPanel.innerHTML = '';
            const choiced = Object.values(tools).find(v => v.label === choicedTool);

            const colorPickerInput = document.createElement('input');
            colorPickerInput.type = 'color';
            colorPickerInput.value = currentColor;
            colorPickerInput.oninput = (e) => {
                currentColor = e.target.value;
                oekaki.color.value = currentColor;
            };
            toolOptionsPanel.appendChild(colorPickerInput);

            recentColors.forEach(color => {
                const colorBtn = document.createElement('button');
                colorBtn.className = 'palette';
                colorBtn.style.backgroundColor = color;
                colorBtn.onclick = () => {
                    currentColor = color;
                    oekaki.color.value = currentColor;
                    colorPickerInput.value = currentColor;
                };
                toolOptionsPanel.appendChild(colorBtn);
            });

            const sizeDisplay = document.createElement('span');
            sizeDisplay.className = 'size-display';
            toolOptionsPanel.appendChild(sizeDisplay);

            const sliderWrapper = document.createElement('div');
            sliderWrapper.className = 'slider-wrapper';

            if (choicedTool === tools.brush.label) {
                sizeDisplay.textContent = `${brushSize}px`;
                const slider = document.createElement('input');
                slider.type = 'range';
                slider.min = '1';
                slider.max = '64';
                slider.value = brushSize;
                slider.className = 'slider-input';
                slider.oninput = (e) => {
                    brushSize = parseInt(e.target.value);
                    oekaki.brushSize.value = brushSize;
                    sizeDisplay.textContent = `${brushSize}px`;
                };
                sliderWrapper.appendChild(slider);
            } else if (isGrid) {
                sizeDisplay.textContent = `${dotPenScale}倍`;
                const slider = document.createElement('input');
                slider.type = 'range';
                slider.min = '1';
                slider.max = '8';
                slider.value = dotPenScale;
                slider.className = 'slider-input';
                slider.oninput = (e) => {
                    dotPenScale = parseInt(e.target.value);
                    oekaki.setDotSize(dotPenScale);
                    document.documentElement.style.setProperty("--grid-cell-size", `${oekaki.getDotSize()}px`);
                    sizeDisplay.textContent = `${dotPenScale}倍`;
                };
                sliderWrapper.appendChild(slider);
            } else if (choicedTool === tools.pen.label) {
                sizeDisplay.textContent = `${penSize}px`;
                const slider = document.createElement('input');
                slider.type = 'range';
                slider.min = '1';
                slider.max = '64';
                slider.value = penSize;
                slider.className = 'slider-input';
                slider.oninput = (e) => {
                    penSize = parseInt(e.target.value);
                    oekaki.penSize.value = penSize;
                    sizeDisplay.textContent = `${penSize}px`;
                };
                sliderWrapper.appendChild(slider);
            } else if (choicedTool === tools.eraser.label) {
                sizeDisplay.textContent = `${eraserSize}px`;
                const slider = document.createElement('input');
                slider.type = 'range';
                slider.min = '1';
                slider.max = '64';
                slider.value = eraserSize;
                slider.className = 'slider-input';
                slider.oninput = (e) => {
                    eraserSize = parseInt(e.target.value);
                    oekaki.eraserSize.value = eraserSize;
                    sizeDisplay.textContent = `${eraserSize}px`;
                };
                sliderWrapper.appendChild(slider);
            }

            if (choicedTool !== tools.dropper.label && choicedTool !== tools.fill.label) {
                toolOptionsPanel.appendChild(sliderWrapper);
            }
        };

        const updateThumbnail = () => {
            const dataURL = oekaki.render().toDataURL();
            if (dataURL) {
                imageThumbnail.src = dataURL;
                document.getElementById("image-status").innerText = "プレビュー";
            }
        };

        const updateLayerPanel = () => {
            layerPanel.innerHTML = '';
            const layers = oekaki.getLayers();
            layers.forEach((layer, index) => {
                const layerItem = document.createElement('div');
                layerItem.style.display = 'flex';
                layerItem.style.alignItems = 'center';
                layerItem.style.color = '#fff';
                layerItem.style.cursor = 'pointer';
                layerItem.style.padding = '5px';
                layerItem.style.backgroundColor = activeLayer === layer ? '#555' : 'transparent';
                layerItem.style.gap = '10px';

                // レイヤー名の表示
                const layerNameSpan = document.createElement('span');
                layerNameSpan.textContent = layer.name;
                layerNameSpan.onclick = () => {
                    activeLayer = layer;
                    updateLayerPanel();
                };
                layerItem.appendChild(layerNameSpan);

                // 表示/非表示ボタン
                const visibilityButton = document.createElement('button');
                visibilityButton.style.cssText = `
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    padding: 8px;
                    border: 1px solid #555;
                    background-color: #444;
                    color: #fff;
                    cursor: pointer;
                    border-radius: 4px;
                    width: 40px;
                    height: 40px;
              `;
                visibilityButton.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="${layer.visible ? mdi.mdiEye : mdi.mdiEyeOff}"/></svg>`;
                visibilityButton.onclick = (e) => {
                    e.stopPropagation();
                    layer.visible = !layer.visible;
                    updateThumbnail();
                    updateLayerPanel();
                };
                layerItem.appendChild(visibilityButton);

                // 削除ボタン
                const deleteButton = document.createElement('button');
                deleteButton.style.cssText = `
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    padding: 8px;
                    border: 1px solid #555;
                    background-color: #444;
                    color: #fff;
                    cursor: pointer;
                    border-radius: 4px;
                    width: 40px;
                    height: 40px;
              `;
                deleteButton.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="${mdi.mdiDelete}" /></svg>`;
                deleteButton.onclick = (e) => {
                    e.stopPropagation();
                    // レイヤー削除の確認ロジック
                    if (layer.locked || (layer.used && !confirm(`${layer.name}を削除しますか?`))) {
                        return;
                    }
                    activeLayer.delete();
                    const { prev, next } = activeLayer;
                    if (next) activeLayer = next;
                    else if (prev) activeLayer = prev;
                    else {
                        // 全てのレイヤーが削除された場合の初期化
                        const newLayer = new oekaki.LayeredCanvas("レイヤー #1");
                        activeLayer = newLayer;
                    }
                    updateLayerPanel();
                    updateThumbnail();
                };
                layerItem.appendChild(deleteButton);

                layerPanel.appendChild(layerItem);
            });
            const addLayerButton = document.createElement('button');
            addLayerButton.textContent = 'レイヤー追加';
            addLayerButton.style.cssText = 'margin-top: 10px; padding: 5px; cursor: pointer;';
            addLayerButton.onclick = () => {
                const layers = oekaki.getLayers();
                const newLayer = new oekaki.LayeredCanvas(`レイヤー #${layers.length + 1}`);
                activeLayer = newLayer;
                updateLayerPanel();
            };
            layerPanel.appendChild(addLayerButton);
        };

        const mdi2DataUrl = (mdi) => {
            const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="${mdi}" fill="black" stroke="white" stroke-width="1"/></svg>`;
            const base64 = btoa(svg);
            return `data:image/svg+xml;base64,${base64}`;
        };

        const createToolButton = (label, iconName, onClick) => {
            const button = document.createElement('button');
            button.title = label;
            button.style.cssText = `
                display: flex;
                align-items: center;
                justify-content: center;
                padding: 8px;
                border: 1px solid #555;
                background-color: #444;
                color: #fff;
                cursor: pointer;
                border-radius: 4px;
                width: 40px;
                height: 40px;
            `;
            const svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            svgIcon.setAttribute('width', '24');
            svgIcon.setAttribute('height', '24');
            svgIcon.setAttribute('viewBox', '0 0 24 24');
            const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            path.setAttribute('fill', 'currentColor');
            path.setAttribute('d', iconName);
            svgIcon.appendChild(path);
            button.appendChild(svgIcon);
            button.onclick = onClick;
            return button;
        };

        const updateToolButtons = () => {
            const buttons = toolPanel.querySelectorAll('button');
            buttons.forEach(button => {
                const buttonLabel = button.title;
                if (buttonLabel === choicedTool ||
                    (buttonLabel === '常に消しゴム' && isErasable) ||
                    (buttonLabel === '左右反転' && isFlipped) ||
                    (buttonLabel === 'グリッド線' && isGrid)) {
                    button.style.backgroundColor = '#6A1B9A';
                } else {
                    button.style.backgroundColor = '#444';
                }
            });
            updateToolOptions();
        };

        const tools = {
            brush: { label: "ブラシ", icon: mdi.mdiBrush },
            pen: { label: "ペン", icon: mdi.mdiPen },
            eraser: { label: "消しゴム", icon: mdi.mdiEraser },
            dropper: { label: "カラーピッカー", icon: mdi.mdiEyedropper },
            fill: { label: "塗りつぶし", icon: mdi.mdiFormatColorFill },
            translate: { label: "ハンドツール", icon: mdi.mdiHandBackRight },
            erasable: { label: "常に消しゴム", icon: mdi.mdiEraserVariant },
            flip: { label: "左右反転", icon: mdi.mdiFlipHorizontal },
            grid: { label: "グリッド線", icon: mdi.mdiGrid },
            undo: { label: "戻る", icon: mdi.mdiUndo },
            redo: { label: "進む", icon: mdi.mdiRedo },
            save: { label: "画像を保存", icon: mdi.mdiContentSaveOutline },
            clear: { label: "全消し", icon: mdi.mdiTrashCanOutline }
        };

        const notDrawing = (e) => {
            const target = e.target;
            return (
                !getSelection()?.isCollapsed ||
                target.tagName === "INPUT" ||
                target.tagName === "TEXTAREA" ||
                target.isContentEditable
            );
        };

        const handleKeyDown = async (e) => {
            if (notDrawing(e)) return;
            if (!e.ctrlKey) return;
            let key = e.key.toLowerCase();
            if (e.getModifierState("CapsLock")) {
                key = /[a-z]/.test(key) ? key.toUpperCase() : key.toLowerCase();
            }

            switch (key) {
                case "1": { e.preventDefault(); choicedTool = tools.brush.label; break; }
                case "2": { e.preventDefault(); choicedTool = tools.pen.label; break; }
                case "3": { e.preventDefault(); choicedTool = tools.eraser.label; break; }
                case "4": { e.preventDefault(); choicedTool = tools.dropper.label; break; }
                case "5": { e.preventDefault(); choicedTool = tools.fill.label; break; }
                case "6": { e.preventDefault(); choicedTool = tools.translate.label; break; }
                case "e": { e.preventDefault(); isErasable = !isErasable; updateToolButtons(); break; }
                case "f": { e.preventDefault(); isFlipped = !isFlipped; oekaki.flipped.value = isFlipped; updateToolButtons(); break; }
                case "g": { e.preventDefault(); isGrid = !isGrid; oekakiContainer.classList.toggle('grid', isGrid); updateToolButtons(); break; }
                case "z": { e.preventDefault(); if (e.shiftKey) { if (activeLayer) activeLayer.redo(); } else { if (activeLayer) activeLayer.undo(); } updateThumbnail(); break; }
                case "s": {
                    e.preventDefault();
                    const dataURL = oekaki.render().toDataURL("image/png");
                    const link = document.createElement("a");
                    link.href = dataURL;
                    link.download = "drawing.png";
                    link.click();
                    break;
                }
                case "c": {
                    e.preventDefault();
                    if (activeLayer) {
                        const blob = await new Promise(resolve => activeLayer.canvas.toBlob(resolve));
                        if (blob) {
                            await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
                        }
                    }
                    break;
                }
            }
            updateToolButtons();
        };

        window.addEventListener('paste', async (e) => {
            if (!activeLayer || activeLayer.locked) return;
            const imageItem = Array.from(e.clipboardData?.items || []).find(v => v.kind === "file" && v.type.startsWith("image/"));
            if (!imageItem) return;
            const blob = imageItem.getAsFile();
            if (!blob) return;
            const bitmap = await createImageBitmap(blob);
            activeLayer.paste(bitmap);
            activeLayer.trace();
            updateThumbnail();
        });

        const dropper = (x, y) => {
            if (!activeLayer) return;
            const result = oekaki.dropper(x, y);
            if (!result) return;
            const [r, g, b, a] = result;
            if (a > 0) {
                isErasable = false;
                const hex = `#${[r, g, b]
                .map((v) => v.toString(16).padStart(2, "0"))
                .join("")}`;
                currentColor = hex;
                oekaki.color.value = currentColor;
            } else {
                isErasable = true;
            }
            updateToolOptions();
        };

        const fill = async (x, y) => {
            if (!activeLayer) return;
            const rgb = currentColor
            .slice(1)
            .match(/.{2}/g)
            ?.map((v) => Number.parseInt(v, 16));
            if (rgb?.length !== 3) return;
            const [r, g, b] = rgb;
            const data = oekaki.floodFill(
                activeLayer.data,
                width,
                height,
                x,
                y,
                isErasable ? [0, 0, 0, 0] : [r, g, b, 255],
            );
            if (data) activeLayer.data = data;
        };

        const initOekaki = async () => {
            if (oekakiInitialized) return;
            oekakiInitialized = true;

            width = Math.min(window.innerWidth * 0.8, 800);
            height = Math.min(window.innerHeight * 0.8, 600);

            oekaki.init(oekakiContainer, width, height);
            const upperCanvas = oekaki.upperLayer.value.canvas;
            if (upperCanvas) {
                upperCanvas.classList.add("upper-canvas");
            }
            document.documentElement.style.setProperty("--grid-cell-size", `${oekaki.getDotSize()}px`);

            const bgLayer = new oekaki.LayeredCanvas("白背景");
            bgLayer.fill("#FFF");
            bgLayer.trace();

            const firstLayer = new oekaki.LayeredCanvas("レイヤー #2");
            activeLayer = firstLayer;

            updateLayerPanel();

            const separator = "separator";

            [
                tools.brush, tools.pen, tools.eraser, tools.dropper, tools.fill, tools.translate,
                separator,
                tools.erasable, tools.flip, tools.grid,
                separator,
                tools.undo, tools.redo, tools.save, tools.clear
            ].forEach(t => {
                if (t === separator) {
                    const separatorElement = document.createElement('div');
                    separatorElement.style.width = '1px';
                    separatorElement.style.height = '48px';
                    separatorElement.style.backgroundColor = '#555';
                    toolPanel.appendChild(separatorElement);
                    return;
                }
                const button = createToolButton(t.label, t.icon, async () => {
                    const toggleTools = [tools.erasable, tools.flip, tools.grid];
                    if (toggleTools.includes(t)) {
                        switch (t.label) {
                            case '常に消しゴム': isErasable = !isErasable; break;
                            case '左右反転': isFlipped = !isFlipped; oekaki.flipped.value = isFlipped; break;
                            case 'グリッド線':
                                isGrid = !isGrid;
                                oekakiContainer.classList.toggle('grid', isGrid);
                                if (isGrid) {
                                    dotPenScale = 4;
                                    oekaki.setDotSize(dotPenScale);
                                    document.documentElement.style.setProperty("--grid-cell-size", `${oekaki.getDotSize()}px`);
                                }
                                break;
                        }
                    } else if (t.label === '戻る') {
                        if (activeLayer) activeLayer.undo();
                        updateThumbnail();
                    } else if (t.label === '進む') {
                        if (activeLayer) activeLayer.redo();
                        updateThumbnail();
                    } else if (t.label === '画像を保存') {
                        const dataURL = oekaki.render().toDataURL("image/png");
                        const link = document.createElement("a");
                        link.href = dataURL;
                        link.download = "drawing.png";
                        link.click();
                    } else if (t.label === '全消し') {
                        if (activeLayer) activeLayer.clear();
                        updateThumbnail();
                    } else {
                        choicedTool = t.label;
                        const choiced = Object.values(tools).find(v => v.label === choicedTool);
                        const xy = choiced.label === tools.fill.label ? "21 19" : "3 21";
                        if (oekaki.upperLayer.value.canvas) {
                            oekaki.upperLayer.value.canvas.style.cursor = `url('${mdi2DataUrl(choiced.icon)}') ${xy}, auto`;
                        }
                    }
                    updateToolButtons();
                });
                toolPanel.appendChild(button);
            });

            const choiced = Object.values(tools).find(v => v.label === choicedTool);
            const xy = choiced.label === tools.fill.label ? "21 19" : "3 21";
            oekaki.upperLayer.value.canvas.style.cursor = `url('${mdi2DataUrl(choiced.icon)}') ${xy}, auto`;
            updateToolButtons();

            let prevX = null;
            let prevY = null;
            let dropping = false;

            oekaki.onDraw((x, y, buttons) => {
                if (!activeLayer || activeLayer.locked) return;
                if (prevX === null) prevX = x;
                if (prevY === null) prevY = y;
                if (choicedTool === tools.dropper.label || (buttons & 2) !== 0) {
                    dropper(x, y);
                    dropping = true;
                } else {
                    if (choicedTool === tools.brush.label) {
                        activeLayer.drawLine(x, y, prevX, prevY);
                    } else if (choicedTool === tools.translate.label) {
                        if (isGrid) {
                            activeLayer.translateByDot(x - prevX, y - prevY);
                        } else {
                            activeLayer.translate(x - prevX, y - prevY);
                        }
                    } else {
                        const lerps = oekaki.lerp(x, y, prevX, prevY);
                        switch (choicedTool) {
                            case tools.pen.label:
                                for (const [lx, ly] of lerps) {
                                    const isEraseMode = isErasable;
                                    if (isGrid) {
                                        isEraseMode ? activeLayer.eraseByDot(lx, ly) : activeLayer.drawByDot(lx, ly);
                                    } else {
                                        isEraseMode ? activeLayer.erase(lx, ly) : activeLayer.draw(lx, ly);
                                    }
                                }
                                break;
                            case tools.eraser.label:
                                for (const [lx, ly] of lerps) {
                                    if (isGrid) {
                                        activeLayer.eraseByDot(lx, ly);
                                    } else {
                                        activeLayer.erase(lx, ly);
                                    }
                                }
                                break;
                        }
                    }
                }
                prevX = x;
                prevY = y;
            });

            const fin = () => {
                if (activeLayer?.modified()) {
                    activeLayer.trace();
                    addRecentColors();
                    updateToolOptions();
                    updateThumbnail();
                }
            };

            oekaki.onDrawn((x, y, buttons) => {
                prevX = null;
                prevY = null;
                if (activeLayer?.locked) return;
                if (choicedTool === tools.fill.label && !dropping) fill(x, y);
                dropping = false;
                fin();
            });

            oekaki.upperLayer.value.canvas.addEventListener('contextmenu', (e) => {
                e.preventDefault();
            });

            window.addEventListener('keydown', handleKeyDown);
            window.addEventListener('paste', handleKeyDown);
        };

        oekakiButton.addEventListener('click', () => {
            imagePreview.style.display = 'block';
            oekakiUIWrapper.style.display = 'flex';
            initOekaki();
        });

        const closeButton = await GM.addElement(toolPanel, 'button', {
            textContent: '閉じる',
            style: 'padding: 5px 10px; cursor: pointer;'
        });
        closeButton.addEventListener('click', async () => {
            oekakiUIWrapper.style.display = 'none';
            const canvas = oekaki.render();
            const blob = await (new Promise((resolve) => {
                canvas.toBlob(resolve, 'image/png');
            }));
            if (!blob) return;
            const file = new File([blob], "image.png", { type: blob.type });

            // DataTransferオブジェクトを利用してFileListを作成
            const dataTransfer = new DataTransfer();
            dataTransfer.items.add(file);

            // input要素にFileListを設定
            const fileInput = document.getElementById("image-upload");
            fileInput.files = dataTransfer.files;

            // changeイベントを発火させる
            const event = new Event('change', { bubbles: true });
            fileInput.dispatchEvent(event);
        });
    }

})();