Прошу помочь уже несколько дней не понимаю в чем дело.
Я написал онлайн редактор кода на основе 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 и не хранил там текущий обьект с данными а просто при клике на саму вкладку менял модель внутри эдитора своего таба. Мне хотя бы найти причину ошибок