Monaco editor удаление таба

16 Views Asked by At

Прошу помочь уже несколько дней не понимаю в чем дело.

Я написал онлайн редактор кода на основе monaco editor.Начал делать функции вкладок и теперь моем коде либо не правильно работает удаления одного монако эдитора или что то не так делаю. В общем вкладки у меня построены так

[все линии
 [ линия
   [ таб
     {} и обьекты которые хранят модель для эдитора и еще данные
   ],
 ],
]

проблема в том что когда я удаляю tab который не является последним должен по идеи просто удалиться.

[
 [ 
   [ 
     {} 
   ],
   [ удаляю вот этот например
     {} 
   ],
   [ 
     {} 
   ],
 ],
]

то консоль выдает ошибку model disposed хотя я же просто должен был удалить массив.

Body от него все идет

import { useSelector } from "react-redux";
import Tab from "./Tab";
import { useEffect, useState } from "react";
import {tabs} from './getItems'

const Body = () => {

    const themeValue = useSelector(state => state.options.theme);
    const plan = useSelector(state => state.editorAll.tabs)
    const check = useSelector(state => state.editorAll.check)

    useEffect(() => {
        if(plan[0][0].length > 0){
            localStorage.setItem('tabs', JSON.stringify(plan.map(tab => tab.map(innerArray => innerArray.map(obj => ({ ...obj, model: null }))))));
        }
    },[plan])

    return (
        <div className={`body ${themeValue}`}>
            {
                plan.map((line,i) => (
                    <div className="body_line" key={i}>
                        {
                            line.map((e,tabI) => (
                                <Tab key={tabI} plan={tabs} index={{line:i,tab:tabI}}/>
                            ))
                        }
                    </div>
                ))
            }
            <div className={"body_check "+check}>
                <div className="body_check_right"></div>
                <div className="body_check_bottom"></div>
            </div>
        </div>
    );
};

export default Body;

import { useEffect, useRef, useState} from 'react';
import Editor from '@monaco-editor/react';
import jsonpath from 'jsonpath';
import { useDispatch, useSelector } from 'react-redux';
import { addRefAction } from '../../store/reducers/ref';
import { changeAllBoolean } from '../../store/reducers/boolean/allBoolean';
import { changeCursorInfo } from '../../store/reducers/components/editorCursorLine';
import { changeEditorAll } from '../../store/reducers/components/editorAll';
import Inset from './Inset';


export default function Tab({plan,index}) {

    const editRef = useRef(null);
    const monacoRef = useRef(null);
    const dispatch = useDispatch();
    const themeValue = useSelector(state => state.options.theme);
    const autoSaveValue = useSelector(state => state.options.saveBoolean);
    const minimapValue = useSelector(state => state.options.minimap);
    const indentationValue = useSelector(state => state.options.indentation);
    const wrapTextValue = useSelector(state => state.options.wrapText);
    const showIndentationValue = useSelector(state => state.options.showIndent)
    const fontSizeValue = useSelector(state => state.options.fontSize);
    const tabSizeValue = useSelector(state => state.options.tabSize);
    const insertSpaceValue = useSelector(state => state.options.insertSpace);
    const tabs = useSelector(state => state.editorAll.tabs[index.line][index.tab])
    const allTabs = useSelector(state => state.editorAll.tabs)
    const ownId = index
    const id = useSelector(state => state.editorAll.activeInset)
    const languageValue = useSelector(state => state.editorAll.tabs[id.line][id.tab][id.inset]?.lang)
    const ref = useSelector(state => state.ref.ref)

    const [actualIndex, setactualIndex] = useState(0)
    const [actualObj, setactualObj] = useState({name: 'untilted', lang: 'json', model: null, code:'',cursor:{column:0,lineNumber:0}})
    const [insetObj, setinsetObj] = useState(null)

    function handleEditorDidMount(editor, monaco) {
        editRef.current = editor;
        monacoRef.current = monaco;
        }
        editor.onDidChangeModelContent(() => {
            if(editor.getModel()){
                dispatch(changeAllBoolean('CAN_UNDO',editor.getModel().canUndo()))
                dispatch(changeAllBoolean('CAN_REDO',editor.getModel().canRedo()))
            }
        });
        editor.onDidChangeModel(() => {
            if(editor.getModel()){
                dispatch(changeAllBoolean('CAN_UNDO',editor.getModel().canUndo()))
                dispatch(changeAllBoolean('CAN_REDO',editor.getModel().canRedo()))
            }
        });
        if (monaco && tabs.length === 0) {
            if(plan && autoSaveValue){
                createSaveInsets(plan[index.line][index.tab])
            } else{
                dispatch(changeEditorAll('ADD_OBJECT',{newId:ownId,newObj:{...actualObj,model:editor.getModel()}}))
            }
        }
        if(tabs.length>0){
            const newModel = monacoRef.current.editor.createModel(tabs[0].code, tabs[0].lang)
            dispatch(changeEditorAll('REPLACE',{id:{...index,inset:0},obj:{...tabs[0],model: newModel}}))
            setinsetObj({...tabs[0],model: newModel});
        }
    }
    const changeEditor = () => {
        if (editRef.current) {
            if (editRef.current.getValue().trim() === '') {
                dispatch(changeEditorAll('EMPTY',false));
            } else{
                dispatch(changeEditorAll('EMPTY',true));
            }
        }
    };
    const createInset = () => { 
        let newName = 'untilted'
        const untitledObject = tabs.find(obj => obj.name === 'untilted');
        if(untitledObject){
            let numbers = tabs
            .filter(obj => obj.name.startsWith('untilted'))
            .map(obj => {
                const match = obj.name.match(/untilted \((\d+)\)/);
                return match?parseInt(match[1]):0;
            });
            let number = 1;
            while (numbers.includes(number)) {
                number++;
            }
            newName = `untilted (${number})`;
        }
        const newModel = monacoRef.current.editor.createModel('', 'json')
        dispatch(changeEditorAll('ADD_OBJECT',{newId:ownId,newObj:{ name: newName, lang: 'json', model: newModel, code:'',cursor:{column:0,lineNumber:0}}}))
    }
    const deleteInset = (name,boolean) => {
        const lineLength = allTabs[index.line].length
        if (tabs.length == 1 && lineLength > 1){
            monacoRef.current.editor.getModels().forEach(e => {
                if (e.id === tabs[0]?.model?.id) {
                    e.dispose();
                }
            });
            dispatch(changeEditorAll('REMOVE_TAB',index))
            console.log(monacoRef.current.editor.getModels())
        } else if (tabs.length > 1 ) {
            const modelId = tabs.find(e => e.name === name)?.model?.id
            monacoRef.current.editor.getModels().forEach(e => {
                if(e.id == modelId) {
                    e.dispose()
                }
            })
            dispatch(changeEditorAll('REMOVE_OBJECT',{oldId:id,oldName:name}))
            if (boolean) {
                if(actualIndex!==0){
                    setactualIndex(actualIndex-1);
                }
            } else{
                setactualIndex(tabs.findIndex(e => e.name == name)>actualIndex?actualIndex:actualIndex-1);
            }
        }
    }
    const changeRef = () => {
        console.log(editRef.current.getModel().id)
        dispatch(addRefAction({ref:editRef,monaco:monacoRef}));
        dispatch(changeAllBoolean('ACTIVE_INSET_CHANGE', {line:index.line,tab:index.tab,inset:actualIndex}));
    }
    const createSaveInsets = (plan) => { 
        plan.forEach((e) => {
            const newModel = monacoRef.current.editor.createModel(e.code, e.lang)
            dispatch(changeEditorAll('ADD_OBJECT',{newId:ownId,newObj:{ name: e.name, lang: e.lang, model: newModel, code:e.code,cursor:e.cursor}}))
        })
    }

    useEffect(() => {       
        if(id.tab === index.tab && editRef.current){
            dispatch(addRefAction({ref:editRef,monaco:monacoRef}));
            console.log(monacoRef.current.editor.getModels())
        }
    });

    useEffect(() => {
        if (tabs.length > 0) {
            const names = tabs.map(e => e?.model?.id == insetObj?.model?.id)
            if(insetObj == null || !names[0]){
                // setinsetObj({...tabs[0]});
            }
        } 
    }, [tabs]);

    useEffect(() => {
        if(editRef.current){
            const name = editRef.current?.getModel()?.id
            if(insetObj && name !== insetObj?.model?.id){ 
                editRef.current.setModel(insetObj.model)
            }
        }
    }, [insetObj]);
    
    useEffect(() => {
        if (languageValue && editRef && id.tab == index.tab && id.line == index.line ) {
            const model = editRef.current.getModel();
            monacoRef.current.editor.setModelLanguage(model,languageValue)
        }
    }, [languageValue]);
    
    useEffect(() => {
        if(id.tab == index.tab && editRef.current){
            if(editRef.current.getModel()){
                dispatch(changeAllBoolean('CAN_UNDO',editRef.current.getModel().canUndo()))
                dispatch(changeAllBoolean('CAN_REDO',editRef.current.getModel().canRedo()))
            }
        }
    },[id])

    return (
        <div className='body_tab' onClick={changeRef}>
            <div className='body_tab_insets'>
                {
                    tabs.map((e,i) => (
                        <Inset inset={insetObj} setInset={setinsetObj} tabIndex={ownId} editRef={editRef.current} object={e} ownIndex={i} actual={actualIndex} setIndex={setactualIndex} deleting={deleteInset} key={i}/>
                    ))
                }
                <div className='body_tab_insets_add'>
                    <svg onClick={() => createInset()} xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22"><path d="M464-464H280v-32h184v-184h32v184h184v32H496v184h-32v-184Z"/></svg>
                </div>
            </div>
            <div className="body_tab_editor">
                <Editor
                    className="editor"
                    width={'100%'}
                    height={'100%'}
                    theme={themeValue === 'black' ? 'customTheme' : ''}
                    onMount={handleEditorDidMount}
                    onChange={changeEditor}
                    options={{
                        renderWhitespace: indentationValue ? 'all' : '',
                        automaticLayout: true,
                        minimap: { enabled: minimapValue },
                        guides: {
                            indentation: showIndentationValue, 
                        }
                    }}
                />
            </div>
        </div>
    )
}
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useDrag } from 'react-dnd';

import { changeAllBoolean } from "../../store/reducers/boolean/allBoolean";
import { changeEditorAll } from "../../store/reducers/components/editorAll";

export default function Inset({inset,setInset,tabIndex,editRef,object,ownIndex,actual,setIndex,deleting}) {  

    const [menuShow, setmenuShow] = useState(false)
    const tabs = useSelector(state => state.editorAll.tabs[tabIndex.line][tabIndex.tab])
    const id = useSelector(state => state.editorAll.activeInset)
    const check = useSelector(state => state.editorAll.check)
    const allTabs = useSelector(state => state.editorAll.tabs)

    const dispatch = useDispatch()

    const changeMenuShow = (event) => {
        event.preventDefault(); 
        if (!menuShow) { 
            setmenuShow(true)
        }
    }
    const setModel = () => {
        setIndex(ownIndex);
    }
    const remove = (e) => {
        e.stopPropagation(); 
        setmenuShow(false)
        const lineLength = allTabs[tabIndex.line].length
        if(tabs.length == 1 && lineLength > 1){
            let newTab = tabIndex.tab+1==lineLength?(lineLength-2):tabIndex.tab
            dispatch(changeAllBoolean('ACTIVE_INSET_CHANGE', {line:tabIndex.line,tab:newTab,inset:0}));
        }
        deleting(object.name,actual==ownIndex?true:false);
    }
    const removeAll = (e) => {
        e.stopPropagation(); 
        setmenuShow(false)
        dispatch(changeEditorAll('REMOVE_ALL_OBJECT',{RMid:tabIndex,RMobj:object}))
        setIndex(0);
        editRef.setModel(object.model)
        dispatch(changeAllBoolean('ACTIVE_INSET_CHANGE',{...tabIndex,inset:ownIndex}))
    }
    const removeRight = (e,side) => {
        if(ownIndex !== tabs.length) {
            e.stopPropagation();
            setmenuShow(false)
            dispatch(changeEditorAll('REMOVE_ALL_SIDES_OBJECT', { RSid: tabIndex, RSown: ownIndex, side: side }));
        }
        if(ownIndex<actual && side == 'right'){
            setIndex(ownIndex);
            editRef.setModel(object.model)
            dispatch(changeAllBoolean('ACTIVE_INSET_CHANGE',{...tabIndex,inset:ownIndex}))
        } 
        if(ownIndex>=actual && side == 'left'){
            setIndex(0);
            editRef.setModel(object.model)
            dispatch(changeAllBoolean('ACTIVE_INSET_CHANGE',{...tabIndex,inset:ownIndex}))
        } 
        if (ownIndex < actual && side === 'left') {
            setIndex(actual-ownIndex);
            editRef.setModel(object.model);
            dispatch(changeAllBoolean('ACTIVE_INSET_CHANGE', { ...tabIndex, inset: ownIndex }));
        }
    }

    const handleDragEnd = (item, monitor) => {
        const clientOffset = monitor.getClientOffset();
        const clickedElement = document.elementFromPoint(clientOffset.x, clientOffset.y);
        if(clickedElement.classList[0] == 'body_check_right'){
            if(tabs.length > 1){
                dispatch(changeEditorAll('ADD_TAB',{tabObj:object,tabId:tabIndex.line}))
                setmenuShow(false)
                deleting(object.name,actual==ownIndex?true:false);
            }
        } else if(clickedElement.classList[0] == 'body_check_bottom') {
            console.log(1)
        }
        dispatch(changeEditorAll('CHANGE_CHECK', false));
    };

    const [{ isDragging }, drag] = useDrag({
        type:'box',
        item: () => {
            dispatch(changeEditorAll('CHANGE_CHECK',true))
            return { type: 'box'.BOX, id }
        },
        collect: (monitor) => ({
            isDragging: monitor.isDragging(),
        }),
        end:handleDragEnd,
    });

    useEffect(() => {
        if (menuShow) {
            const handleOutsideClick = (event) => {
                const div = document.querySelector('.body_tab_insets_inset_menu')
                const array = event.composedPath().includes(div)
                if (!array) {
                    setmenuShow(false);
                    document.removeEventListener("click", handleOutsideClick)
                }
            };
            // const handleContextMenu = (event) => {
            //     if (event.button === 2 && menuShow) {
            //         const div = document.querySelector('.body_tab_insets_inset_menu')
            //         const array = event.composedPath().includes(div)
            //         if (!array) {
            //             setmenuShow(false);
            //             document.removeEventListener("contextmenu", handleContextMenu)
            //         }
            //     }
            // };
            document.addEventListener("click", handleOutsideClick);
            // document.addEventListener("contextmenu", handleContextMenu);
        }
    }, [menuShow]);

    useEffect(() => {
        if(editRef){
            const modelChangeListener = editRef.onDidChangeModelContent(() => {
                const newCursorPos = editRef.getPosition();
                if(actual === ownIndex && id.tab == tabIndex.tab){
                    dispatch(changeEditorAll('REPLACE',{id:{...id,inset:actual},obj:{...tabs[id.inset],cursor:newCursorPos,code:editRef.getValue()}}))
                }
            });
            return () => {
                modelChangeListener.dispose(); 
            };
        }
    }, [editRef, actual, ownIndex, id, tabs]);
    
    useEffect(() => {
        if (actual === ownIndex && id.tab == tabIndex.tab && inset?.model?.id !== object.model.id) {
            setInset(object)
            editRef.focus()
            editRef.setPosition(tabs[actual].cursor);
            dispatch(changeAllBoolean('ACTIVE_INSET_CHANGE', {line:tabIndex.line,tab:tabIndex.tab, inset: ownIndex}));
        }
    }, [actual]);

    return (
        <div 
            ref={drag} 
            style={{opacity: isDragging ? 0.5 : 1}}
            className={`body_tab_insets_inset ${actual==ownIndex?'active':''}`} 
            onClick={() => setModel()} 
            onContextMenu={(e) => changeMenuShow(e)}
        >
            <div className='body_tab_insets_inset_line'></div>
            <h5>{object.lang ? object.lang.toUpperCase() : ''}</h5>
            <h4>{object.name}</h4>
            <svg onClick={(event) => remove(event)} xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 -960 960 960" width="18"><path d="M291-267.692 267.692-291l189-189-189-189L291-692.308l189 189 189-189L692.308-669l-189 189 189 189L669-267.692l-189-189-189 189Z"/></svg>
            <div className={"body_tab_insets_inset_menu "+menuShow}>
                <h3 onClick={(event) => remove(event)}>Close Tab</h3>
                <h3 onClick={(event) => removeAll(event)}>Close Other Tab</h3>
                <h3 onClick={(event) => removeRight(event,'right')}>Close Tabs to the Right</h3>
                <h3 onClick={(event) => removeRight(event,'left')}>Close Tabs to the Left</h3>
                <div className="body_tab_insets_inset_menu_line"></div>
                <h3>Split Up</h3>
                <h3>Split Down</h3>
                <h3>Split Left</h3>
                <h3>Split Right</h3>
            </div>
        </div>
    )
}

также так выглядит мой redux

state

activeInset:{line:0,tab:0,inset:0},
tabs:[
        [
            [
                
            ],
        ],
],

actions

case 'REMOVE_TAB':
            return {
                ...state,
                tabs: state.tabs.map((tabLine, lineIndex) => {
                    if (lineIndex === action.payload.line) {
                        return tabLine.filter((_, tabIndex) => tabIndex !== action.payload.tab);
                    }
                    return [...tabLine];
                })
            };

Это мой код на данный момент но раньше я использовал не useState и не хранил там текущий обьект с данными а просто при клике на саму вкладку менял модель внутри эдитора своего таба. Мне хотя бы найти причину ошибок

0

There are 0 best solutions below