mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 10:45:47 +08:00
feat: support advanced editor in model edit page (#3176)
* feat: integrate external model editor and handle message events for model updates * feat: add CasbinEditor and IframeEditor components for model editing * feat: add tabbed editor interface for CasbinEditor * fix: Synchronize content between basic and advanced editors * refactor: simplify CasbinEditor and ModelEditPage components * refactor: Refactor CasbinEditor for improved iframe initialization and model synchronization * refactor: update default state of CasbinEditor active tab to "advanced * chore: add Apache License header to CasbinEditor.js and IframeEditor.js files * refactor: update CasbinEditor class names for consistency
This commit is contained in:
parent
8df965b98d
commit
ed158d4981
97
web/src/CasbinEditor.js
Normal file
97
web/src/CasbinEditor.js
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React, {useCallback, useEffect, useRef, useState} from "react";
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/mode/properties/properties";
|
||||
import * as Setting from "./Setting";
|
||||
import IframeEditor from "./IframeEditor";
|
||||
import {Tabs} from "antd";
|
||||
|
||||
const {TabPane} = Tabs;
|
||||
|
||||
const CasbinEditor = ({model, onModelTextChange}) => {
|
||||
const [activeKey, setActiveKey] = useState("advanced");
|
||||
const iframeRef = useRef(null);
|
||||
const [localModelText, setLocalModelText] = useState(model.modelText);
|
||||
|
||||
const handleModelTextChange = useCallback((newModelText) => {
|
||||
if (!Setting.builtInObject(model)) {
|
||||
setLocalModelText(newModelText);
|
||||
onModelTextChange(newModelText);
|
||||
}
|
||||
}, [model, onModelTextChange]);
|
||||
|
||||
const syncModelText = useCallback(() => {
|
||||
return new Promise((resolve) => {
|
||||
if (activeKey === "advanced" && iframeRef.current) {
|
||||
const handleSyncMessage = (event) => {
|
||||
if (event.data.type === "modelUpdate") {
|
||||
window.removeEventListener("message", handleSyncMessage);
|
||||
handleModelTextChange(event.data.modelText);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", handleSyncMessage);
|
||||
iframeRef.current.getModelText();
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}, [activeKey, handleModelTextChange]);
|
||||
|
||||
const handleTabChange = (key) => {
|
||||
syncModelText().then(() => {
|
||||
setActiveKey(key);
|
||||
if (key === "advanced" && iframeRef.current) {
|
||||
iframeRef.current.updateModelText(localModelText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLocalModelText(model.modelText);
|
||||
}, [model.modelText]);
|
||||
|
||||
return (
|
||||
<div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column"}}>
|
||||
<Tabs activeKey={activeKey} onChange={handleTabChange} style={{flex: "0 0 auto", marginTop: "-10px"}}>
|
||||
<TabPane tab="Basic Editor" key="basic" />
|
||||
<TabPane tab="Advanced Editor" key="advanced" />
|
||||
</Tabs>
|
||||
<div style={{flex: "1 1 auto", overflow: "hidden"}}>
|
||||
{activeKey === "advanced" ? (
|
||||
<IframeEditor
|
||||
ref={iframeRef}
|
||||
initialModelText={localModelText}
|
||||
onModelTextChange={handleModelTextChange}
|
||||
style={{width: "100%", height: "100%"}}
|
||||
/>
|
||||
) : (
|
||||
<CodeMirror
|
||||
value={localModelText}
|
||||
className="full-height-editor no-horizontal-scroll-editor"
|
||||
options={{mode: "properties", theme: "default"}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
handleModelTextChange(value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CasbinEditor;
|
66
web/src/IframeEditor.js
Normal file
66
web/src/IframeEditor.js
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react";
|
||||
|
||||
const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => {
|
||||
const iframeRef = useRef(null);
|
||||
const [iframeReady, setIframeReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = (event) => {
|
||||
if (event.origin !== "https://editor.casbin.org") {return;}
|
||||
|
||||
if (event.data.type === "modelUpdate") {
|
||||
onModelTextChange(event.data.modelText);
|
||||
} else if (event.data.type === "iframeReady") {
|
||||
setIframeReady(true);
|
||||
iframeRef.current?.contentWindow.postMessage({
|
||||
type: "initializeModel",
|
||||
modelText: initialModelText,
|
||||
}, "*");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handleMessage);
|
||||
return () => window.removeEventListener("message", handleMessage);
|
||||
}, [onModelTextChange, initialModelText]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getModelText: () => {
|
||||
iframeRef.current?.contentWindow.postMessage({type: "getModelText"}, "*");
|
||||
},
|
||||
updateModelText: (newModelText) => {
|
||||
if (iframeReady) {
|
||||
iframeRef.current?.contentWindow.postMessage({
|
||||
type: "updateModelText",
|
||||
modelText: newModelText,
|
||||
}, "*");
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src="https://editor.casbin.org/model-editor"
|
||||
frameBorder="0"
|
||||
width="100%"
|
||||
height="500px"
|
||||
title="Casbin Model Editor"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default IframeEditor;
|
@ -18,11 +18,7 @@ import * as ModelBackend from "./backend/ModelBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
|
||||
require("codemirror/mode/properties/properties");
|
||||
import ModelEditor from "./CasbinEditor";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -147,16 +143,10 @@ class ModelEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<div style={{width: "100%"}} >
|
||||
<CodeMirror
|
||||
value={this.state.model.modelText}
|
||||
options={{mode: "properties", theme: "default"}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
if (Setting.builtInObject(this.state.model)) {
|
||||
return;
|
||||
}
|
||||
this.updateModelField("modelText", value);
|
||||
}}
|
||||
<div style={{position: "relative", height: "500px"}} >
|
||||
<ModelEditor
|
||||
model={this.state.model}
|
||||
onModelTextChange={(value) => this.updateModelField("modelText", value)}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
|
@ -51,3 +51,19 @@ code {
|
||||
.custom-link:hover {
|
||||
color: rgb(64 64 64) !important;
|
||||
}
|
||||
|
||||
.full-height-editor {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.full-height-editor [class*="CodeMirror"] {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.no-horizontal-scroll-editor [class*="CodeMirror-hscrollbar"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.no-horizontal-scroll-editor [class*="CodeMirror-scroll"] {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user