1528 lines
55 KiB
HTML
1528 lines
55 KiB
HTML
<style>
|
|
.oc[data-darkmode="true"] .config-editor-modal .CodeMirror {
|
|
background: var(--bg-white);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .config-editor-modal .CodeMirror-gutters {
|
|
background: var(--bg-gray);
|
|
border-right: 1px solid var(--border-light);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .config-editor-modal .CodeMirror-linenumber {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .config-editor-modal .CodeMirror-scrollbar-filler,
|
|
.oc[data-darkmode="true"] .config-editor-modal .CodeMirror-gutter-filler {
|
|
background: var(--bg-gray);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] #config-mode-tabs .mode-tab {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] #config-mode-tabs .mode-tab:hover {
|
|
color: var(--text-primary);
|
|
background: rgba(96, 165, 250, 0.1);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] #config-mode-tabs .mode-tab.active {
|
|
background: var(--primary-color);
|
|
color: white;
|
|
}
|
|
|
|
.oc[data-darkmode="true"] #config-mergeview-container .CodeMirror-merge-gap {
|
|
background: var(--text-secondary) !important;
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .oc .config-editor-content {
|
|
border-bottom: 1px solid var(--border-light);
|
|
border-top: 1px solid var(--border-light);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .overwrite-banner {
|
|
background: rgba(255,80,80,0.18);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .overwrite-banner svg {
|
|
stroke: var(--error-color);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .overwrite-banner svg circle {
|
|
stroke: var(--error-color);
|
|
fill: rgba(255,80,80,0.18);
|
|
}
|
|
|
|
.oc .config-editor-modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 10000;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
backdrop-filter: blur(2px);
|
|
}
|
|
|
|
.oc .config-editor-modal-overlay.show {
|
|
display: flex;
|
|
}
|
|
|
|
.oc .config-editor-modal {
|
|
background: var(--bg-white);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-md);
|
|
width: 90vw;
|
|
height: 85vh;
|
|
max-width: 1200px;
|
|
min-width: 600px;
|
|
min-height: 400px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
border: 1px solid var(--border-light);
|
|
position: relative;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.oc .config-editor-modal.maximized {
|
|
width: 98vw !important;
|
|
height: 95vh !important;
|
|
max-width: none !important;
|
|
}
|
|
|
|
.oc .config-editor-modal.minimized {
|
|
width: 70vw !important;
|
|
height: 70vh !important;
|
|
}
|
|
|
|
.oc .config-editor-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16px 20px;
|
|
border-bottom: 1px solid var(--border-light);
|
|
background: var(--bg-gray);
|
|
flex-shrink: 0;
|
|
cursor: move;
|
|
user-select: none;
|
|
min-width: 0;
|
|
}
|
|
|
|
.oc .config-editor-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
flex: 1 1 auto;
|
|
}
|
|
|
|
.oc .config-editor-title .config-file-name {
|
|
color: var(--primary-color);
|
|
font-weight: 700;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
display: inline-block;
|
|
vertical-align: bottom;
|
|
padding: 0;
|
|
}
|
|
|
|
.oc .config-editor-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.oc .size-btn {
|
|
width: 24px !important;
|
|
height: 24px !important;
|
|
min-width: 24px !important;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
.oc .size-btn svg {
|
|
width: 12px !important;
|
|
height: 12px !important;
|
|
}
|
|
|
|
#config-mergeview-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
position: absolute;
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
background: var(--bg-white);
|
|
z-index: 2;
|
|
}
|
|
|
|
.oc .config-editor-content {
|
|
flex: 1;
|
|
position: relative;
|
|
overflow: hidden;
|
|
border-bottom: 1px solid var(--border-light);
|
|
border-top: 1px solid var(--border-light);
|
|
}
|
|
|
|
.oc .config-editor-loading {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
background: var(--bg-white);
|
|
color: var(--text-secondary);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.oc .loading-spinner {
|
|
width: 24px;
|
|
height: 24px;
|
|
border: 2px solid var(--border-light);
|
|
border-top-color: var(--primary-color);
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.oc .config-editor-footer {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 20px;
|
|
background: var(--bg-gray);
|
|
flex-shrink: 0;
|
|
position: relative;
|
|
}
|
|
|
|
.oc .config-editor-status {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 50%;
|
|
}
|
|
|
|
.oc .config-editor-help {
|
|
font-size: 11px;
|
|
color: var(--text-secondary);
|
|
opacity: 0.8;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 50%;
|
|
}
|
|
|
|
.oc .config-editor-resize-handle {
|
|
position: absolute;
|
|
bottom: 0;
|
|
right: 0;
|
|
width: 20px;
|
|
height: 20px;
|
|
cursor: nw-resize;
|
|
background: linear-gradient(-45deg,
|
|
transparent 0%,
|
|
transparent 40%,
|
|
var(--border-color) 40%,
|
|
var(--border-color) 45%,
|
|
transparent 45%,
|
|
transparent 50%,
|
|
var(--border-color) 50%,
|
|
var(--border-color) 55%,
|
|
transparent 55%,
|
|
transparent 60%,
|
|
var(--border-color) 60%,
|
|
var(--border-color) 65%,
|
|
transparent 65%);
|
|
}
|
|
|
|
.oc #config-editor-textarea {
|
|
width: 100%;
|
|
height: 100%;
|
|
border: none;
|
|
outline: none;
|
|
resize: none;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
padding: 12px;
|
|
background: var(--bg-white);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.oc .config-editor-modal .CodeMirror {
|
|
height: 100%;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.oc #config-mode-tabs {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 8px;
|
|
background: transparent;
|
|
border: none;
|
|
box-shadow: none;
|
|
border-radius: var(--radius-md);
|
|
}
|
|
|
|
.oc #config-mode-tabs .mode-tabs {
|
|
display: flex;
|
|
width: 100%;
|
|
background: var(--bg-gray);
|
|
border-radius: var(--radius-md);
|
|
padding: 4px;
|
|
gap: 4px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.oc #config-mode-tabs .mode-tab {
|
|
flex: 1 1 0;
|
|
min-width: 0;
|
|
width: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
padding: 12px 0;
|
|
border: none;
|
|
border-radius: calc(var(--radius-md) - 2px);
|
|
background: transparent;
|
|
color: var(--text-secondary);
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all var(--transition-fast);
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.oc #config-mode-tabs .mode-tab:hover {
|
|
color: var(--text-primary);
|
|
background: rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.oc #config-mode-tabs .mode-tab.active {
|
|
background: var(--primary-color);
|
|
color: white;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.overwrite-banner {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 10px 20px;
|
|
background: rgba(255,80,80,0.12);
|
|
font-size: 14px;
|
|
text-align: center;
|
|
}
|
|
|
|
.overwrite-banner svg {
|
|
flex-shrink: 0;
|
|
display: block;
|
|
}
|
|
|
|
.overwrite-banner span {
|
|
flex: unset;
|
|
text-align: center;
|
|
display: inline-block;
|
|
color: var(--error-color);
|
|
line-height: 1.5;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.oc .config-editor-modal .CodeMirror.zoom-75 { font-size: 10.5px; }
|
|
.oc .config-editor-modal .CodeMirror.zoom-90 { font-size: 12.6px; }
|
|
.oc .config-editor-modal .CodeMirror.zoom-110 { font-size: 15.4px; }
|
|
.oc .config-editor-modal .CodeMirror.zoom-125 { font-size: 17.5px; }
|
|
.oc .config-editor-modal .CodeMirror.zoom-150 { font-size: 21px; }
|
|
.oc .config-editor-modal .CodeMirror.zoom-200 { font-size: 28px; }
|
|
|
|
#config-mergeview-container .CodeMirror-merge,
|
|
#config-mergeview-container .CodeMirror-merge-pane,
|
|
#config-mergeview-container .CodeMirror,
|
|
#config-mergeview-container .CodeMirror-scroll {
|
|
height: 100% !important;
|
|
min-height: 0 !important;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#config-mergeview-container .CodeMirror-merge-gap {
|
|
height: 100% !important;
|
|
min-height: 0 !important;
|
|
}
|
|
|
|
#config-mergeview-container .CodeMirror-scroll {
|
|
overflow-y: auto !important;
|
|
overflow-x: hidden !important;
|
|
}
|
|
|
|
#config-mergeview-container .CodeMirror-merge-pane {
|
|
overflow: hidden;
|
|
}
|
|
|
|
#config-mergeview-container .CodeMirror-merge-r-chunk {
|
|
background: #0095ff2e !important;
|
|
}
|
|
#config-mergeview-container .CodeMirror-merge-r-connect {
|
|
fill: #0095ff2e !important;
|
|
stroke: #0095ff2e !important;
|
|
}
|
|
|
|
#config-mergeview-container .CodeMirror-merge {
|
|
border: none !important;
|
|
}
|
|
|
|
@media screen and (max-width: 768px) {
|
|
.oc .config-editor-modal {
|
|
width: 95vw;
|
|
height: 80vh;
|
|
min-width: 320px;
|
|
}
|
|
|
|
.oc .config-editor-actions {
|
|
gap: 5px;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="oc">
|
|
<div class="config-editor-modal-overlay" id="config-editor-overlay">
|
|
<div class="config-editor-modal" id="config-editor-modal">
|
|
<div class="config-editor-header">
|
|
<div class="config-editor-title">
|
|
<span id="editTitle"><%:File Edit%>: </span>
|
|
<span class="config-file-name" id="config-file-name"><%:Loading...%></span>
|
|
</div>
|
|
<div class="config-editor-actions">
|
|
<button type="button" class="icon-btn" id="config-editor-layout" title="<%:Compare%>" style="display:none;">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="3" y="3" width="8" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
<rect x="13" y="3" width="8" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
</svg>
|
|
</button>
|
|
<button type="button" class="icon-btn" id="config-editor-download" title="<%:Download%>">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
<polyline points="7,11 12,16 17,11"></polyline>
|
|
<line x1="12" y1="2" x2="12" y2="16"></line>
|
|
</svg>
|
|
</button>
|
|
<button type="button" class="icon-btn" id="config-editor-save" title="<%:Save%>">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
|
|
<polyline points="17,21 17,13 7,13 7,21"></polyline>
|
|
<polyline points="7,3 7,8 15,8"></polyline>
|
|
</svg>
|
|
</button>
|
|
<button type="button" class="icon-btn" id="config-editor-close" title="<%:Close%>">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="overwrite-banner" class="overwrite-banner" style="display:none;">
|
|
<svg style="flex-shrink:0;" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--error-color)" stroke-width="2">
|
|
<circle cx="12" cy="12" r="10" stroke="var(--error-color)" fill="rgba(255,80,80,0.12)"/>
|
|
<line x1="12" y1="5" x2="12" y2="13"/>
|
|
<circle cx="12" cy="16" r="0.5"/>
|
|
</svg>
|
|
<span>
|
|
<%:You are editing the overwrite script, please note that some settings may cause the abnormal, be careful with the modification!%>
|
|
</span>
|
|
</div>
|
|
|
|
<div id="config-mode-tabs" style="display:none;">
|
|
<div class="mode-tabs">
|
|
<button type="button" class="mode-tab active" id="tab-original-config" data-mode="original">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="vertical-align:middle;margin-right:4px;">
|
|
<rect x="4" y="4" width="16" height="16" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
<line x1="8" y1="8" x2="16" y2="8" stroke="currentColor" stroke-width="2"/>
|
|
<line x1="8" y1="12" x2="16" y2="12" stroke="currentColor" stroke-width="2"/>
|
|
<line x1="8" y1="16" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
|
|
</svg>
|
|
<%:Original Config%>
|
|
</button>
|
|
<button type="button" class="mode-tab" id="tab-runtime-config" data-mode="runtime">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="vertical-align:middle;margin-right:4px;">
|
|
<polygon points="13 2 3 14 12 14 11 22 21 10 13 10 13 2" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
</svg>
|
|
<%:Runtime Config%>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="config-editor-content">
|
|
<div class="config-editor-loading" id="config-editor-loading">
|
|
<div class="loading-spinner"></div>
|
|
<span><%:Loading config file...%></span>
|
|
</div>
|
|
<textarea id="config-editor-textarea" style="display: none;"></textarea>
|
|
<div id="config-mergeview-container" style="display:none;width:100%;height:100%;"></div>
|
|
</div>
|
|
|
|
<div class="config-editor-footer">
|
|
<div class="config-editor-status">
|
|
<span id="config-editor-status-text"><%:Ready%></span>
|
|
</div>
|
|
<div class="config-editor-help">
|
|
<span id="config-editor-help"><%:Press F11 for fullscreen, Esc to exit fullscreen, Ctrl+Mouse Wheel to zoom%></span>
|
|
</div>
|
|
<div class="config-editor-resize-handle" id="config-editor-resize-handle"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<link rel="stylesheet" href="/luci-static/resources/openclash/lib/codemirror.css"/>
|
|
<link rel="stylesheet" href="/luci-static/resources/openclash/theme/material.css"/>
|
|
<link rel="stylesheet" href="/luci-static/resources/openclash/theme/material-log.css"/>
|
|
<link rel="stylesheet" href="/luci-static/resources/openclash/theme/idea.css"/>
|
|
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/fold/foldgutter.css"/>
|
|
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/lint/lint.css">
|
|
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/display/fullscreen.css">
|
|
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/dialog/dialog.css">
|
|
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/search/matchesonscrollbar.css">
|
|
<script src="/luci-static/resources/openclash/lib/codemirror.js"></script>
|
|
<script src="/luci-static/resources/openclash/mode/yaml/yaml.js"></script>
|
|
<script src="/luci-static/resources/openclash/mode/lua/lua.js"></script>
|
|
<script src="/luci-static/resources/openclash/mode/shell/shell.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/fold/foldcode.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/fold/foldgutter.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/fold/indent-fold.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/edit/matchbrackets.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/selection/active-line.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/lint/lint.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/lint/yaml-lint.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/lint/js-yaml.min.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/display/fullscreen.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/display/autorefresh.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/dialog/dialog.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/search/searchcursor.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/search/search.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/scroll/annotatescrollbar.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/search/matchesonscrollbar.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/search/jump-to-line.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/merge/diff_match_patch.js"></script>
|
|
<script src="/luci-static/resources/openclash/addon/merge/merge.js"></script>
|
|
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/merge/merge.css">
|
|
|
|
<script type="text/javascript">
|
|
var ConfigEditor = {
|
|
overlay: null,
|
|
modal: null,
|
|
editorInstance: null,
|
|
originalContent: '',
|
|
isModified: false,
|
|
currentZoom: 100,
|
|
currentConfigFile: '',
|
|
zoomLevels: [75, 90, 100, 110, 125, 150, 200],
|
|
isOverwrite: false,
|
|
currentViewMode: 'original',
|
|
runtimeContent: '',
|
|
mergeViewActive: false,
|
|
SVG_COMPARE: `
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="3" y="3" width="8" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
<rect x="13" y="3" width="8" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
</svg>
|
|
`,
|
|
SVG_RESTORE: `
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
</svg>
|
|
`,
|
|
|
|
init: function() {
|
|
this.overlay = document.getElementById('config-editor-overlay');
|
|
this.modal = document.getElementById('config-editor-modal');
|
|
this.mergeViewActive = false;
|
|
|
|
if (!this.overlay || !this.modal) {
|
|
return;
|
|
}
|
|
|
|
this.bindEvents();
|
|
},
|
|
|
|
bindEvents: function() {
|
|
var self = this;
|
|
|
|
document.getElementById('config-editor-save').addEventListener('click', function() {
|
|
self.saveConfigContent();
|
|
});
|
|
|
|
document.getElementById('config-editor-download').addEventListener('click', function() {
|
|
self.downloadConfigContent();
|
|
});
|
|
|
|
document.getElementById('config-editor-close').addEventListener('click', function() {
|
|
self.closeEditor();
|
|
});
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
if (!self.overlay.classList.contains('show')) return;
|
|
|
|
if ((e.ctrlKey || e.metaKey) && (e.key === '=' || e.key === '+')) {
|
|
e.preventDefault();
|
|
self.zoomIn();
|
|
} else if ((e.ctrlKey || e.metaKey) && e.key === '-') {
|
|
e.preventDefault();
|
|
self.zoomOut();
|
|
} else if ((e.ctrlKey || e.metaKey) && e.key === '0') {
|
|
e.preventDefault();
|
|
self.resetZoom();
|
|
} else if (e.key === 'Escape' && (!self.editorInstance || !self.editorInstance.getOption("fullScreen"))) {
|
|
self.closeEditor();
|
|
}
|
|
});
|
|
|
|
this.overlay.addEventListener('wheel', function(e) {
|
|
if (e.ctrlKey || e.metaKey) {
|
|
e.preventDefault();
|
|
|
|
if (e.deltaY < 0) {
|
|
self.zoomIn();
|
|
} else {
|
|
self.zoomOut();
|
|
}
|
|
}
|
|
});
|
|
|
|
var tabOriginal = document.getElementById('tab-original-config');
|
|
var tabRuntime = document.getElementById('tab-runtime-config');
|
|
if (tabOriginal && tabRuntime) {
|
|
tabOriginal.addEventListener('click', function() {
|
|
if (self.currentViewMode !== 'original') {
|
|
self.currentViewMode = 'original';
|
|
self.loadConfigContent();
|
|
self.updateModeTabs();
|
|
}
|
|
});
|
|
tabRuntime.addEventListener('click', function() {
|
|
if (self.currentViewMode !== 'runtime') {
|
|
self.currentViewMode = 'runtime';
|
|
self.loadConfigContent();
|
|
self.updateModeTabs();
|
|
}
|
|
});
|
|
};
|
|
|
|
var layoutBtn = document.getElementById('config-editor-layout');
|
|
if (layoutBtn) {
|
|
layoutBtn.addEventListener('click', function() {
|
|
if (!self.mergeViewActive) {
|
|
self.showMergeView();
|
|
self.currentViewMode = 'original';
|
|
layoutBtn.title = "<%:Restore%>";
|
|
layoutBtn.innerHTML = self.SVG_RESTORE;
|
|
} else {
|
|
self.hideMergeView();
|
|
layoutBtn.title = "<%:Compare%>";
|
|
layoutBtn.innerHTML = self.SVG_COMPARE;
|
|
}
|
|
});
|
|
};
|
|
|
|
this.makeDraggable();
|
|
this.makeResizable();
|
|
},
|
|
|
|
show: function(configFile) {
|
|
this.isOverwrite = false;
|
|
|
|
var banner = document.getElementById('overwrite-banner');
|
|
if (banner) banner.style.display = 'none';
|
|
|
|
var tabs = document.getElementById('config-mode-tabs');
|
|
if (tabs) tabs.style.display = 'flex';
|
|
|
|
var layoutBtn = document.getElementById('config-editor-layout');
|
|
if (layoutBtn) layoutBtn.style.display = 'inline-block';
|
|
|
|
if (!configFile) {
|
|
alert('<%:Please select a config file first%>');
|
|
return;
|
|
}
|
|
|
|
this.currentViewMode = 'original';
|
|
this.runtimeContent = '';
|
|
this.currentConfigFile = configFile;
|
|
this.overlay.classList.add('show');
|
|
|
|
this.modal.classList.remove('maximized');
|
|
this.modal.classList.remove('minimized');
|
|
|
|
var editTitle = document.getElementById('editTitle');
|
|
if (editTitle) {
|
|
editTitle.textContent = '<%:File Edit%>: ';
|
|
}
|
|
|
|
var configNameElement = document.getElementById('config-file-name');
|
|
if (configNameElement) {
|
|
configNameElement.textContent = this.formatDisplayName(configFile);
|
|
}
|
|
|
|
this.isModified = false;
|
|
this.originalContent = '';
|
|
|
|
this.mergeViewActive = false;
|
|
this.hideMergeView();
|
|
this.updateModeTabs();
|
|
},
|
|
|
|
showOverwrite: function() {
|
|
this.isOverwrite = true;
|
|
|
|
var banner = document.getElementById('overwrite-banner');
|
|
if (banner) banner.style.display = 'flex';
|
|
|
|
var tabs = document.getElementById('config-mode-tabs');
|
|
if (tabs) tabs.style.display = 'none';
|
|
|
|
var layoutBtn = document.getElementById('config-editor-layout');
|
|
if (layoutBtn) layoutBtn.style.display = 'none';
|
|
|
|
this.currentConfigFile = '/etc/openclash/custom/openclash_custom_overwrite.sh';
|
|
this.overlay.classList.add('show');
|
|
|
|
this.modal.classList.remove('maximized');
|
|
this.modal.classList.remove('minimized');
|
|
|
|
var editTitle = document.getElementById('editTitle');
|
|
if (editTitle) {
|
|
editTitle.textContent = '<%:Overwrite Edit%>: ';
|
|
}
|
|
|
|
var configNameElement = document.getElementById('config-file-name');
|
|
if (configNameElement) {
|
|
configNameElement.textContent = this.formatDisplayName(this.currentConfigFile);
|
|
}
|
|
this.isModified = false;
|
|
this.originalContent = '';
|
|
|
|
this.mergeViewActive = false;
|
|
this.hideMergeView();
|
|
|
|
this.loadConfigContent();
|
|
},
|
|
|
|
hide: function() {
|
|
this.overlay.classList.remove('show');
|
|
|
|
if (this.editorInstance) {
|
|
if (this.editorInstance.toTextArea) this.editorInstance.toTextArea();
|
|
this.editorInstance = null;
|
|
}
|
|
|
|
this.isModified = false;
|
|
this.originalContent = '';
|
|
this.currentConfigFile = '';
|
|
|
|
var loadingDiv = document.getElementById('config-editor-loading');
|
|
var textarea = document.getElementById('config-editor-textarea');
|
|
var mergeview = document.getElementById('config-mergeview-container');
|
|
var editor_help = document.getElementById('config-editor-help');
|
|
|
|
if (loadingDiv) loadingDiv.style.display = 'flex';
|
|
if (textarea) textarea.style.display = 'none';
|
|
if (mergeview) mergeview.style.display = 'none';
|
|
if (layoutBtn) {
|
|
layoutBtn.classList.remove('active');
|
|
layoutBtn.title = "<%:Compare%>";
|
|
layoutBtn.innerHTML = this.SVG_COMPARE;
|
|
}
|
|
if (editor_help) editor_help.textContent = '<%:Press F11 for fullscreen, Esc to exit fullscreen, Ctrl+Mouse Wheel to zoom%>';
|
|
},
|
|
|
|
formatDisplayName: function(fileName) {
|
|
if (!fileName) return '<%:Unknown%>';
|
|
|
|
if (this.isOverwrite || fileName === '/etc/openclash/custom/openclash_custom_overwrite.sh') {
|
|
return 'openclash_custom_overwrite.sh';
|
|
}
|
|
|
|
var name = fileName.split('/').pop().split('\\').pop();
|
|
|
|
return name;
|
|
},
|
|
|
|
updateModeTabs: function() {
|
|
var tabOriginal = document.getElementById('tab-original-config');
|
|
var tabRuntime = document.getElementById('tab-runtime-config');
|
|
var saveBtn = document.getElementById('config-editor-save');
|
|
if (this.isOverwrite) {
|
|
if (tabOriginal && tabRuntime) {
|
|
tabOriginal.classList.remove('active');
|
|
tabRuntime.classList.remove('active');
|
|
}
|
|
if (saveBtn) {
|
|
saveBtn.disabled = false;
|
|
saveBtn.style.opacity = '1';
|
|
saveBtn.style.cursor = 'pointer';
|
|
}
|
|
return;
|
|
}
|
|
if (tabOriginal && tabRuntime) {
|
|
if (this.currentViewMode === 'original') {
|
|
tabOriginal.classList.add('active');
|
|
tabRuntime.classList.remove('active');
|
|
if (saveBtn) {
|
|
saveBtn.disabled = !this.isModified;
|
|
saveBtn.style.opacity = this.isModified ? '1' : '0.5';
|
|
saveBtn.style.cursor = this.isModified ? 'pointer' : 'not-allowed';
|
|
}
|
|
} else {
|
|
tabOriginal.classList.remove('active');
|
|
tabRuntime.classList.add('active');
|
|
if (saveBtn) {
|
|
saveBtn.disabled = true;
|
|
saveBtn.style.opacity = '0.5';
|
|
saveBtn.style.cursor = 'not-allowed';
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
loadConfigContent: function() {
|
|
var self = this;
|
|
var statusText = document.getElementById('config-editor-status-text');
|
|
var loadingDiv = document.getElementById('config-editor-loading');
|
|
var textarea = document.getElementById('config-editor-textarea');
|
|
var mergeview = document.getElementById('config-mergeview-container');
|
|
if (mergeview) mergeview.style.display = 'none';
|
|
if (textarea) textarea.style.display = 'block';
|
|
|
|
statusText.textContent = '<%:Loading...%>';
|
|
|
|
var url, mode;
|
|
if (this.isOverwrite) {
|
|
url = '/cgi-bin/luci/admin/services/openclash/config_file_read?config_file=' + encodeURIComponent('/etc/openclash/custom/openclash_custom_overwrite.sh');
|
|
mode = "text/x-sh";
|
|
} else if (this.currentViewMode === 'runtime') {
|
|
var runtimePath = '/etc/openclash/' + encodeURIComponent(this.formatDisplayName(this.currentConfigFile));
|
|
url = '/cgi-bin/luci/admin/services/openclash/config_file_read?config_file=' + runtimePath;
|
|
mode = "text/yaml";
|
|
} else {
|
|
url = '/cgi-bin/luci/admin/services/openclash/config_file_read?config_file=' + encodeURIComponent(this.currentConfigFile);
|
|
mode = "text/yaml";
|
|
}
|
|
|
|
function renderEditor(content, mode, readOnly, lint) {
|
|
loadingDiv.style.display = 'none';
|
|
textarea.value = content;
|
|
textarea.style.display = 'block';
|
|
|
|
if (self.editorInstance) {
|
|
if (self.editorInstance.toTextArea) self.editorInstance.toTextArea();
|
|
self.editorInstance = null;
|
|
}
|
|
self.editorInstance = CodeMirror.fromTextArea(textarea, {
|
|
mode: mode,
|
|
autoRefresh: true,
|
|
styleActiveLine: true,
|
|
lineNumbers: true,
|
|
theme: "material",
|
|
lineWrapping: true,
|
|
matchBrackets: true,
|
|
foldGutter: true,
|
|
lint: lint,
|
|
readOnly: readOnly,
|
|
gutters: lint
|
|
? ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"]
|
|
: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
|
extraKeys: {
|
|
"F11": function(cm) {
|
|
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
|
},
|
|
"Esc": function(cm) {
|
|
if (cm.getOption("fullScreen")) {
|
|
cm.setOption("fullScreen", false);
|
|
}
|
|
},
|
|
"Tab": function(cm) {
|
|
if (cm.somethingSelected()) {
|
|
cm.indentSelection('add');
|
|
} else {
|
|
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
|
|
cm.replaceSelection(spaces);
|
|
}
|
|
},
|
|
"Ctrl-S": function(cm) {
|
|
if (!readOnly) self.saveConfigContent();
|
|
}
|
|
}
|
|
});
|
|
self.editorInstance.setSize('100%', '100%');
|
|
self.editorInstance.setValue(content);
|
|
self.editorInstance.refresh();
|
|
if (!readOnly) {
|
|
self.editorInstance.on("change", function() {
|
|
self.isModified = self.editorInstance.getValue() !== self.originalContent;
|
|
self.updateSaveButtonState();
|
|
});
|
|
}
|
|
}
|
|
|
|
if (!this.isOverwrite) {
|
|
if (this.currentViewMode === 'original' && this.originalContent) {
|
|
renderEditor(this.originalContent, "text/yaml", false, true);
|
|
statusText.textContent = '<%:Ready%>';
|
|
self.updateModeTabs();
|
|
return;
|
|
}
|
|
if (this.currentViewMode === 'runtime' && this.runtimeContent) {
|
|
renderEditor(this.runtimeContent, "text/yaml", true, false);
|
|
statusText.textContent = '<%:Runtime config (read only)%>';
|
|
self.updateModeTabs();
|
|
return;
|
|
}
|
|
}
|
|
|
|
fetch(url)
|
|
.then(function(response) {
|
|
if (!response.ok) {
|
|
throw new Error('HTTP error! status: ' + response.status);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(function(data) {
|
|
if (data.content !== undefined) {
|
|
if (self.currentViewMode === 'runtime' && !self.isOverwrite) {
|
|
self.runtimeContent = data.content;
|
|
renderEditor(self.runtimeContent, "text/yaml", true, false);
|
|
statusText.textContent = '<%:Runtime config (read only)%>';
|
|
} else {
|
|
self.originalContent = data.content;
|
|
renderEditor(self.originalContent, self.isOverwrite ? "text/x-sh" : "text/yaml", false, !self.isOverwrite);
|
|
statusText.textContent = '<%:Ready%>';
|
|
}
|
|
self.updateModeTabs();
|
|
} else {
|
|
throw new Error('Invalid response data');
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
loadingDiv.querySelector('span').textContent = '<%:Failed to load config file%>';
|
|
statusText.textContent = '<%:Load failed%>';
|
|
});
|
|
},
|
|
|
|
showMergeView: function() {
|
|
var self = this;
|
|
if (this.isOverwrite) return;
|
|
var container = document.getElementById('config-mergeview-container');
|
|
var textarea = document.getElementById('config-editor-textarea');
|
|
var loadingDiv = document.getElementById('config-editor-loading');
|
|
var tabs = document.getElementById('config-mode-tabs');
|
|
var editor_help = document.getElementById('config-editor-help');
|
|
var statusText = document.getElementById('config-editor-status-text');
|
|
|
|
if (tabs) tabs.style.display = 'none';
|
|
if (textarea) textarea.style.display = 'none';
|
|
if (loadingDiv) loadingDiv.style.display = 'none';
|
|
if (container) container.style.display = 'block';
|
|
if (statusText) statusText.textContent = '<%:Loading...%>';
|
|
if (editor_help) editor_help.textContent = '<%:Press F10 to toggle differences, F11 for fullscreen, Esc to exit fullscreen, Ctrl+Mouse Wheel to zoom%>';
|
|
|
|
var getOriginal = function() {
|
|
return new Promise(function(resolve, reject) {
|
|
if (self.originalContent) return resolve(self.originalContent);
|
|
var url = '/cgi-bin/luci/admin/services/openclash/config_file_read?config_file=' + encodeURIComponent(self.currentConfigFile);
|
|
fetch(url).then(function(r){return r.json()}).then(function(data){
|
|
resolve(data.content || '');
|
|
}).catch(function(){resolve('')});
|
|
});
|
|
};
|
|
var getRuntime = function() {
|
|
return new Promise(function(resolve, reject) {
|
|
if (self.runtimeContent) return resolve(self.runtimeContent);
|
|
var runtimePath = '/etc/openclash/' + encodeURIComponent(self.formatDisplayName(self.currentConfigFile));
|
|
var url = '/cgi-bin/luci/admin/services/openclash/config_file_read?config_file=' + runtimePath;
|
|
fetch(url).then(function(r){return r.json()}).then(function(data){
|
|
resolve(data.content || '');
|
|
}).catch(function(){resolve('')});
|
|
});
|
|
};
|
|
|
|
let showDifferences = true;
|
|
|
|
Promise.all([getOriginal(), getRuntime()]).then(function(contents){
|
|
var original = contents[0] || '';
|
|
var runtime = contents[1] || '';
|
|
container.innerHTML = '';
|
|
if (self.editorInstance && self.editorInstance.toTextArea) self.editorInstance.toTextArea();
|
|
self.editorInstance = CodeMirror.MergeView(container, {
|
|
value: original,
|
|
orig: runtime,
|
|
mode: "text/yaml",
|
|
theme: "material",
|
|
lineNumbers: true,
|
|
autoRefresh: true,
|
|
styleActiveLine: true,
|
|
lineWrapping: true,
|
|
matchBrackets: true,
|
|
foldGutter: true,
|
|
lint: true,
|
|
highlightDifferences: showDifferences,
|
|
connect: null,
|
|
collapseIdentical: false,
|
|
readOnly: false,
|
|
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"],
|
|
extraKeys: {
|
|
"F10": function() {
|
|
showDifferences = !showDifferences;
|
|
if (self.editorInstance && self.editorInstance.setShowDifferences) {
|
|
self.editorInstance.setShowDifferences(showDifferences);
|
|
}
|
|
},
|
|
"F11": function(cm) {
|
|
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
|
},
|
|
"Esc": function(cm) {
|
|
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
|
},
|
|
"Tab": function(cm) {
|
|
if (cm.somethingSelected()) {
|
|
cm.indentSelection('add');
|
|
} else {
|
|
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
|
|
cm.replaceSelection(spaces);
|
|
}
|
|
},
|
|
"Ctrl-S": function(cm) {
|
|
self.saveConfigContent();
|
|
}
|
|
}
|
|
});
|
|
var leftEditor = self.editorInstance.edit;
|
|
if (leftEditor) {
|
|
leftEditor.on("change", function() {
|
|
self.isModified = leftEditor.getValue() !== self.originalContent;
|
|
self.updateSaveButtonState();
|
|
});
|
|
}
|
|
if (self.editorInstance.editor)
|
|
self.editorInstance.editor().setSize('100%', '100%');
|
|
if (self.editorInstance.rightOriginal && self.editorInstance.rightOriginal())
|
|
self.editorInstance.rightOriginal().setSize('100%', '100%');
|
|
self.mergeViewActive = true;
|
|
if (statusText) statusText.textContent = '<%:Compare mode: left(Original Config), right(Runtime Config)%>';
|
|
var layoutBtn = document.getElementById('config-editor-layout');
|
|
if (layoutBtn) layoutBtn.classList.add('active');
|
|
});
|
|
},
|
|
|
|
hideMergeView: function() {
|
|
var container = document.getElementById('config-mergeview-container');
|
|
var textarea = document.getElementById('config-editor-textarea');
|
|
var tabs = document.getElementById('config-mode-tabs');
|
|
var editor_help = document.getElementById('config-editor-help');
|
|
var layoutBtn = document.getElementById('config-editor-layout');
|
|
if (container) {
|
|
container.innerHTML = '';
|
|
container.style.display = 'none';
|
|
}
|
|
if (textarea) textarea.style.display = 'block';
|
|
if (!this.isOverwrite && tabs) tabs.style.display = 'flex';
|
|
this.mergeViewActive = false;
|
|
this.loadConfigContent();
|
|
|
|
if (layoutBtn) {
|
|
layoutBtn.classList.remove('active');
|
|
layoutBtn.title = "<%:Compare%>";
|
|
layoutBtn.innerHTML = this.SVG_COMPARE;
|
|
}
|
|
|
|
if (editor_help) editor_help.textContent = '<%:Press F11 for fullscreen, Esc to exit fullscreen, Ctrl+Mouse Wheel to zoom%>';
|
|
},
|
|
|
|
saveConfigContent: function() {
|
|
if (!this.editorInstance || !this.isModified) {
|
|
return;
|
|
}
|
|
|
|
var self = this;
|
|
var statusText = document.getElementById('config-editor-status-text');
|
|
var saveBtn = document.getElementById('config-editor-save');
|
|
|
|
statusText.textContent = '<%:Saving...%>';
|
|
saveBtn.disabled = true;
|
|
|
|
var content;
|
|
if (this.mergeViewActive && this.editorInstance && this.editorInstance.edit) {
|
|
content = this.editorInstance.edit.getValue();
|
|
} else {
|
|
content = this.editorInstance.getValue();
|
|
}
|
|
|
|
if (!content) {
|
|
saveBtn.disabled = false;
|
|
statusText.textContent = '<%:Save failed%>';
|
|
alert('<%:Config file content is empty%>');
|
|
return;
|
|
}
|
|
|
|
var formData = new FormData();
|
|
if (this.isOverwrite) {
|
|
formData.append('config_file', '/etc/openclash/custom/openclash_custom_overwrite.sh');
|
|
} else {
|
|
formData.append('config_file', this.currentConfigFile);
|
|
}
|
|
formData.append('content', content);
|
|
|
|
fetch('/cgi-bin/luci/admin/services/openclash/config_file_save', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(function(response) {
|
|
if (!response.ok) {
|
|
throw new Error('HTTP error! status: ' + response.status);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(function(data) {
|
|
saveBtn.disabled = false;
|
|
|
|
if (data.status === 'success') {
|
|
self.originalContent = content;
|
|
self.isModified = false;
|
|
self.updateSaveButtonState();
|
|
statusText.textContent = '<%:Saved successfully%>';
|
|
|
|
setTimeout(function() {
|
|
if (statusText && statusText.textContent === '<%:Saved successfully%>') {
|
|
statusText.textContent = '<%:Ready%>';
|
|
}
|
|
}, 3000);
|
|
} else {
|
|
statusText.textContent = '<%:Save failed%>';
|
|
alert('<%:Failed to save config file:%> ' + (data.message || '<%:Unknown error%>'));
|
|
|
|
setTimeout(function() {
|
|
if (statusText && statusText.textContent === '<%:Save failed%>') {
|
|
statusText.textContent = '<%:Ready%>';
|
|
}
|
|
}, 3000);
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
saveBtn.disabled = false;
|
|
statusText.textContent = '<%:Save failed%>';
|
|
alert('<%:Save config failed:%> ' + error.message);
|
|
|
|
setTimeout(function() {
|
|
if (statusText && statusText.textContent === '<%:Save failed%>') {
|
|
statusText.textContent = '<%:Ready%>';
|
|
}
|
|
}, 3000);
|
|
});
|
|
},
|
|
|
|
downloadConfigContent: function() {
|
|
if (!this.editorInstance) {
|
|
alert('<%:Editor not ready%>');
|
|
return;
|
|
}
|
|
|
|
var content;
|
|
if (this.mergeViewActive && this.editorInstance && this.editorInstance.edit) {
|
|
content = this.editorInstance.edit.getValue();
|
|
} else {
|
|
content = this.editorInstance.getValue();
|
|
}
|
|
|
|
var filename;
|
|
if (this.isOverwrite) {
|
|
filename = 'openclash_custom_overwrite.sh';
|
|
} else {
|
|
filename = this.formatDisplayName(this.currentConfigFile);
|
|
if (!filename.toLowerCase().endsWith('.yaml') && !filename.toLowerCase().endsWith('.yml')) {
|
|
filename += '.yaml';
|
|
}
|
|
}
|
|
|
|
try {
|
|
var blob = new Blob([content], { type: 'text/yaml;charset=utf-8' });
|
|
var url = window.URL.createObjectURL(blob);
|
|
var link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = filename;
|
|
link.style.display = 'none';
|
|
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
var statusText = document.getElementById('config-editor-status-text');
|
|
if (statusText) {
|
|
var originalText = statusText.textContent;
|
|
statusText.textContent = '<%:Download started%>';
|
|
|
|
setTimeout(function() {
|
|
if (statusText && statusText.textContent === '<%:Download started%>') {
|
|
statusText.textContent = originalText;
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
} catch (error) {
|
|
alert('<%:Download failed:%> ' + error.message);
|
|
}
|
|
},
|
|
|
|
updateSaveButtonState: function() {
|
|
this.updateModeTabs();
|
|
},
|
|
|
|
closeEditor: function() {
|
|
if (this.isModified) {
|
|
var r = confirm('<%:You have unsaved changes. Are you sure you want to close?%>');
|
|
if (!r) {
|
|
return;
|
|
}
|
|
}
|
|
this.hideMergeView();
|
|
this.hide();
|
|
},
|
|
|
|
updateZoom: function(newZoom) {
|
|
this.currentZoom = newZoom;
|
|
|
|
if (this.editorInstance) {
|
|
var cmWrapper = this.modal.querySelector('.CodeMirror');
|
|
if (cmWrapper) {
|
|
this.zoomLevels.forEach(function(level) {
|
|
cmWrapper.classList.remove('zoom-' + level);
|
|
});
|
|
|
|
if (this.currentZoom !== 100) {
|
|
cmWrapper.classList.add('zoom-' + this.currentZoom);
|
|
}
|
|
|
|
this.editorInstance.refresh();
|
|
}
|
|
}
|
|
},
|
|
|
|
zoomIn: function() {
|
|
var currentIndex = this.zoomLevels.indexOf(this.currentZoom);
|
|
if (currentIndex < this.zoomLevels.length - 1) {
|
|
this.updateZoom(this.zoomLevels[currentIndex + 1]);
|
|
}
|
|
},
|
|
|
|
zoomOut: function() {
|
|
var currentIndex = this.zoomLevels.indexOf(this.currentZoom);
|
|
if (currentIndex > 0) {
|
|
this.updateZoom(this.zoomLevels[currentIndex - 1]);
|
|
}
|
|
},
|
|
|
|
resetZoom: function() {
|
|
this.updateZoom(100);
|
|
},
|
|
|
|
makeDraggable: function() {
|
|
var self = this;
|
|
var header = this.modal.querySelector('.config-editor-header');
|
|
var startX, startY, startLeft, startTop;
|
|
var isDragging = false;
|
|
|
|
header.addEventListener('mousedown', function(e) {
|
|
if (e.target.closest('.config-editor-actions')) {
|
|
return;
|
|
}
|
|
|
|
isDragging = true;
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
|
|
var rect = self.modal.getBoundingClientRect();
|
|
startLeft = rect.left;
|
|
startTop = rect.top;
|
|
|
|
self.modal.style.position = 'fixed';
|
|
self.modal.style.left = startLeft + 'px';
|
|
self.modal.style.top = startTop + 'px';
|
|
self.modal.style.margin = '0';
|
|
self.modal.style.transform = 'none';
|
|
self.modal.style.transition = 'none';
|
|
|
|
document.addEventListener('mousemove', onMouseMove);
|
|
document.addEventListener('mouseup', onMouseUp);
|
|
|
|
e.preventDefault();
|
|
});
|
|
|
|
function onMouseMove(e) {
|
|
if (!isDragging) return;
|
|
|
|
var deltaX = e.clientX - startX;
|
|
var deltaY = e.clientY - startY;
|
|
|
|
var newLeft = startLeft + deltaX;
|
|
var newTop = startTop + deltaY;
|
|
|
|
var modalRect = self.modal.getBoundingClientRect();
|
|
var maxLeft = window.innerWidth - modalRect.width;
|
|
var maxTop = window.innerHeight - modalRect.height;
|
|
|
|
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
|
|
newTop = Math.max(0, Math.min(newTop, maxTop));
|
|
|
|
self.modal.style.left = newLeft + 'px';
|
|
self.modal.style.top = newTop + 'px';
|
|
}
|
|
|
|
function onMouseUp() {
|
|
isDragging = false;
|
|
|
|
setTimeout(function() {
|
|
self.modal.style.transition = 'all 0.3s ease';
|
|
}, 50);
|
|
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
document.removeEventListener('mouseup', onMouseUp);
|
|
}
|
|
|
|
header.addEventListener('touchstart', function(e) {
|
|
if (e.target.closest('.config-editor-actions')) {
|
|
return;
|
|
}
|
|
if (e.touches.length !== 1) return;
|
|
|
|
isDragging = true;
|
|
startX = e.touches[0].clientX;
|
|
startY = e.touches[0].clientY;
|
|
|
|
var rect = self.modal.getBoundingClientRect();
|
|
startLeft = rect.left;
|
|
startTop = rect.top;
|
|
|
|
self.modal.style.position = 'fixed';
|
|
self.modal.style.left = startLeft + 'px';
|
|
self.modal.style.top = startTop + 'px';
|
|
self.modal.style.margin = '0';
|
|
self.modal.style.transform = 'none';
|
|
self.modal.style.transition = 'none';
|
|
|
|
document.addEventListener('touchmove', onTouchMove, {passive: false});
|
|
document.addEventListener('touchend', onTouchEnd);
|
|
|
|
e.preventDefault();
|
|
});
|
|
|
|
function onTouchMove(e) {
|
|
if (!isDragging || e.touches.length !== 1) return;
|
|
|
|
var deltaX = e.touches[0].clientX - startX;
|
|
var deltaY = e.touches[0].clientY - startY;
|
|
|
|
var newLeft = startLeft + deltaX;
|
|
var newTop = startTop + deltaY;
|
|
|
|
var modalRect = self.modal.getBoundingClientRect();
|
|
var maxLeft = window.innerWidth - modalRect.width;
|
|
var maxTop = window.innerHeight - modalRect.height;
|
|
|
|
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
|
|
newTop = Math.max(0, Math.min(newTop, maxTop));
|
|
|
|
self.modal.style.left = newLeft + 'px';
|
|
self.modal.style.top = newTop + 'px';
|
|
|
|
e.preventDefault();
|
|
}
|
|
|
|
function onTouchEnd() {
|
|
isDragging = false;
|
|
setTimeout(function() {
|
|
self.modal.style.transition = 'all 0.3s ease';
|
|
}, 50);
|
|
|
|
document.removeEventListener('touchmove', onTouchMove);
|
|
document.removeEventListener('touchend', onTouchEnd);
|
|
}
|
|
},
|
|
|
|
makeResizable: function() {
|
|
var self = this;
|
|
var resizeHandle = document.getElementById('config-editor-resize-handle');
|
|
var isResizing = false;
|
|
var startX, startY, startWidth, startHeight;
|
|
|
|
resizeHandle.addEventListener('mousedown', function(e) {
|
|
isResizing = true;
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
|
|
var rect = self.modal.getBoundingClientRect();
|
|
startWidth = rect.width;
|
|
startHeight = rect.height;
|
|
|
|
self.modal.style.transition = 'none';
|
|
self.modal.style.width = startWidth + 'px';
|
|
self.modal.style.height = startHeight + 'px';
|
|
|
|
document.addEventListener('mousemove', onMouseMove);
|
|
document.addEventListener('mouseup', onMouseUp);
|
|
|
|
e.preventDefault();
|
|
});
|
|
|
|
function onMouseMove(e) {
|
|
if (!isResizing) return;
|
|
|
|
var deltaX = e.clientX - startX;
|
|
var deltaY = e.clientY - startY;
|
|
|
|
var newWidth = Math.max(400, startWidth + deltaX);
|
|
var newHeight = Math.max(300, startHeight + deltaY);
|
|
|
|
var maxWidth = window.innerWidth * 0.98;
|
|
var maxHeight = window.innerHeight * 0.95;
|
|
|
|
newWidth = Math.min(newWidth, maxWidth);
|
|
newHeight = Math.min(newHeight, maxHeight);
|
|
|
|
self.modal.style.width = newWidth + 'px';
|
|
self.modal.style.height = newHeight + 'px';
|
|
|
|
if (self.editorInstance) {
|
|
requestAnimationFrame(function() {
|
|
if (self.mergeViewActive && self.editorInstance.editor) {
|
|
self.editorInstance.editor().refresh();
|
|
if (self.editorInstance.rightOriginal)
|
|
self.editorInstance.rightOriginal().refresh();
|
|
} else {
|
|
self.editorInstance.refresh();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function onMouseUp() {
|
|
isResizing = false;
|
|
|
|
self.modal.style.transition = 'all 0.3s ease';
|
|
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
document.removeEventListener('mouseup', onMouseUp);
|
|
|
|
if (self.editorInstance) {
|
|
setTimeout(function() {
|
|
if (self.mergeViewActive && self.editorInstance.editor) {
|
|
self.editorInstance.editor().refresh();
|
|
if (self.editorInstance.rightOriginal)
|
|
self.editorInstance.rightOriginal().refresh();
|
|
} else {
|
|
self.editorInstance.refresh();
|
|
}
|
|
}, 50);
|
|
}
|
|
}
|
|
|
|
resizeHandle.addEventListener('touchstart', function(e) {
|
|
if (e.touches.length !== 1) return;
|
|
isResizing = true;
|
|
startX = e.touches[0].clientX;
|
|
startY = e.touches[0].clientY;
|
|
|
|
var rect = self.modal.getBoundingClientRect();
|
|
startWidth = rect.width;
|
|
startHeight = rect.height;
|
|
|
|
self.modal.style.transition = 'none';
|
|
self.modal.style.width = startWidth + 'px';
|
|
self.modal.style.height = startHeight + 'px';
|
|
|
|
document.addEventListener('touchmove', onTouchMove, {passive: false});
|
|
document.addEventListener('touchend', onTouchEnd);
|
|
|
|
e.preventDefault();
|
|
});
|
|
|
|
function onTouchMove(e) {
|
|
if (!isResizing || e.touches.length !== 1) return;
|
|
|
|
var deltaX = e.touches[0].clientX - startX;
|
|
var deltaY = e.touches[0].clientY - startY;
|
|
|
|
var newWidth = Math.max(320, startWidth + deltaX);
|
|
var newHeight = Math.max(200, startHeight + deltaY);
|
|
|
|
var maxWidth = window.innerWidth * 0.98;
|
|
var maxHeight = window.innerHeight * 0.95;
|
|
|
|
newWidth = Math.min(newWidth, maxWidth);
|
|
newHeight = Math.min(newHeight, maxHeight);
|
|
|
|
self.modal.style.width = newWidth + 'px';
|
|
self.modal.style.height = newHeight + 'px';
|
|
|
|
if (self.editorInstance) {
|
|
requestAnimationFrame(function() {
|
|
if (self.mergeViewActive && self.editorInstance.editor) {
|
|
self.editorInstance.editor().refresh();
|
|
if (self.editorInstance.rightOriginal)
|
|
self.editorInstance.rightOriginal().refresh();
|
|
} else {
|
|
self.editorInstance.refresh();
|
|
}
|
|
});
|
|
}
|
|
|
|
e.preventDefault();
|
|
}
|
|
|
|
function onTouchEnd() {
|
|
isResizing = false;
|
|
self.modal.style.transition = 'all 0.3s ease';
|
|
|
|
document.removeEventListener('touchmove', onTouchMove);
|
|
document.removeEventListener('touchend', onTouchEnd);
|
|
|
|
if (self.editorInstance) {
|
|
setTimeout(function() {
|
|
if (self.mergeViewActive && self.editorInstance.editor) {
|
|
self.editorInstance.editor().refresh();
|
|
if (self.editorInstance.rightOriginal)
|
|
self.editorInstance.rightOriginal().refresh();
|
|
} else {
|
|
self.editorInstance.refresh();
|
|
}
|
|
}, 50);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
ConfigEditor.init();
|
|
});
|
|
</script> |