Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Z
zeroing-editor
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
劳工
zeroing-editor
Commits
5e7b8896
Commit
5e7b8896
authored
Apr 28, 2020
by
rockyl
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'dev' into res-group
parents
b5e40cbb
2858b006
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
727 additions
and
389 deletions
+727
-389
zh-CN.json
src/locales/zh-CN.json
+44
-11
project.js
src/store/modules/project.js
+9
-6
editor.scss
src/themes/light/editor.scss
+22
-1
index.js
src/utils/index.js
+1
-1
Editor.vue
src/views/Editor.vue
+6
-3
CodeConflictResolveDialog.vue
src/views/Editor/dialogs/CodeConflictResolveDialog.vue
+438
-0
ProjectConflictResolveDialog.vue
src/views/Editor/dialogs/ProjectConflictResolveDialog.vue
+148
-367
ListCompareView.vue
.../dialogs/ProjectConflictResolveDialog/ListCompareView.vue
+59
-0
No files found.
src/locales/zh-CN.json
View file @
5e7b8896
...
...
@@ -9,6 +9,7 @@
"Still Close"
:
"直接关闭"
,
"Save"
:
"保存"
,
"Pack"
:
"打包"
,
"Commit"
:
"提交"
,
"Save And Preview"
:
"保存并预览"
,
"Save And Close"
:
"保存并关闭"
,
"Reset"
:
"重置"
,
...
...
@@ -235,7 +236,8 @@
"Merge conflicts"
:
"合并冲突"
,
"There are still unresolved conflicts"
:
"还有冲突未解决,不能保存!"
,
"The format of the JSON document is wrong"
:
"JSON文档格式有误,请先更正!"
,
"The conflict has been resolved"
:
"冲突已解决,确定保存吗?"
,
"The conflict has been resolved"
:
"冲突已解决,确定提交吗?"
,
"All conflict has been resolved"
:
"所有冲突已解决,确定保存吗?"
,
"Failed to update operator"
:
"更新权限列表失败"
,
"Error delete self"
:
"连自己都删? 不可以!"
,
"No permission"
:
"没有权限喔!"
,
...
...
@@ -355,15 +357,6 @@
"test"
:
"测试环境"
,
"prod"
:
"线上环境"
},
"QAList"
:[
"崔立强"
,
"李培培"
,
"劳奇峰"
,
"任建锋"
,
"张婷婷"
,
"王炽"
,
"王鸿源"
],
"tplOperates"
:
[
"新增"
,
"修改"
,
...
...
@@ -405,5 +398,45 @@
"查看冲突"
,
"手动合并"
,
"完成"
]
],
"categories"
:
{
"options"
:
{
"label"
:
"项目配置"
,
"simple"
:
true
},
"dependencies"
:
{
"label"
:
"依赖"
,
"simple"
:
true
},
"dataMapping"
:
{
"label"
:
"数据映射"
,
"key"
:
"name"
,
"name"
:
"name"
},
"customs"
:
{
"label"
:
"自定义模块"
,
"key"
:
"id"
,
"name"
:
"id"
},
"mock"
:
{
"label"
:
"Mock"
,
"key"
:
"path"
,
"name"
:
"path"
},
"views"
:
{
"label"
:
"视图"
,
"key"
:
"uuid"
,
"name"
:
"name"
},
"assets"
:
{
"label"
:
"素材"
,
"key"
:
"uuid"
,
"name"
:
"name"
},
"processes"
:
{
"label"
:
"过程"
,
"key"
:
"id"
,
"name"
:
"name"
}
}
}
\ No newline at end of file
src/store/modules/project.js
View file @
5e7b8896
...
...
@@ -706,7 +706,7 @@ export const projectStore = {
//localStorage.removeItem('project-' + projectID);
commit
(
'setDirty'
,
false
);
},
async
loadFromRemote
({
commit
,
dispatch
},
projectID
)
{
async
loadFromRemote
({
commit
,
dispatch
},
{
projectID
}
)
{
await
dispatch
(
'loadPackageInfos'
);
const
project
=
await
projectApi
.
fetchOne
(
projectID
);
if
(
project
)
{
...
...
@@ -716,18 +716,21 @@ export const projectStore = {
throw
new
Error
(
'Project does not exist'
)
}
},
async
loadFromDataUrl
({
commit
,
dispatch
},
{
project
,
dataUrl
})
{
await
dispatch
(
'loadPackageInfos'
);
project
=
await
projectApi
.
fetchOne
(
project
.
id
,
false
);
async
loadFromDataUrl
({
commit
,
dispatch
},
{
projectID
,
dataUrl
})
{
const
projectData
=
await
projectApi
.
fetchOneFromDataUrl
(
dataUrl
);
if
(
projectData
)
{
project
.
data
=
projectData
;
commit
(
'updateProject'
,
project
);
await
dispatch
(
'loadFromData'
,
{
projectID
,
projectData
})
dispatch
(
'saveToLocal'
);
}
else
{
throw
new
Error
(
'Project does not exist'
)
}
},
async
loadFromData
({
commit
,
dispatch
},
{
projectID
,
projectData
})
{
await
dispatch
(
'loadPackageInfos'
);
let
project
=
await
projectApi
.
fetchOne
(
projectID
,
false
);
project
.
data
=
projectData
;
commit
(
'updateProject'
,
project
);
},
async
saveToRemote
({
state
,
dispatch
,
getters
,
commit
},
{
remark
,
data
})
{
let
project
=
Object
.
assign
({},
getters
.
project
);
if
(
data
)
{
...
...
src/themes/light/editor.scss
View file @
5e7b8896
...
...
@@ -488,7 +488,7 @@
width
:
80vw
!
important
;
}
.
project
-conflict-resolve-editor
{
.
code
-conflict-resolve-editor
{
.wrapper
{
height
:
100%
;
padding
:
10px
;
...
...
@@ -506,6 +506,27 @@
}
}
.project-conflict-resolve-editor
{
.wrapper
{
height
:
50vh
;
padding
:
10px
;
display
:
flex
;
flex-direction
:
column
;
.category-tab
{
flex
:
1
;
.el-tabs__content
{
height
:
100%
;
}
}
.compare-view
{
}
}
}
.remote-change-class
{
background-color
:
rgba
(
64
,
200
,
174
,
0
.2
);
}
...
...
src/utils/index.js
View file @
5e7b8896
...
...
@@ -427,5 +427,5 @@ export function deleteAssetsDepConfig(data) {
}
export
function
formatJson
(
source
){
return
JSON
.
stringify
(
JSON
.
parse
(
source
),
null
,
'
\
t'
)
return
source
?
JSON
.
stringify
(
JSON
.
parse
(
source
),
null
,
'
\
t'
)
:
''
;
}
src/views/Editor.vue
View file @
5e7b8896
...
...
@@ -170,10 +170,10 @@
this
.
ready
=
true
;
});
},
async
loadRemoteVersion
(
projectID
,
project
,
dataUrl
)
{
async
loadRemoteVersion
(
projectID
,
project
,
dataUrl
,
projectData
)
{
if
(
projectID
)
{
this
.
ready
=
false
;
let
p
=
dataUrl
?
this
.
loadFromDataUrl
({
project
,
dataUrl
})
:
this
.
loadFromRemote
(
projectID
);
let
p
=
projectData
?
(
this
.
loadFromData
({
projectID
,
projectData
}))
:
dataUrl
?
this
.
loadFromDataUrl
({
projectID
,
dataUrl
})
:
this
.
loadFromRemote
({
projectID
}
);
await
playWaiting
(
p
,
this
.
$t
(
'Preparing'
)).
catch
(
e
=>
{
this
.
$alert
(
this
.
$t
(
'Project does not exist'
),
this
.
$t
(
'Alert'
),
{
confirmButtonText
:
this
.
$t
(
'Confirm'
),
...
...
@@ -230,7 +230,9 @@
return
cancel
;
},
async
onConflictResolved
(
data
)
{
this
.
saveProject
(
true
,
data
);
const
{
projectID
,
project
}
=
this
.
$route
.
params
;
await
this
.
saveProject
(
true
,
data
);
this
.
loadRemoteVersion
(
projectID
,
project
,
undefined
,
data
);
},
checkAuth
()
{
let
checkStatus
=
this
.
operators
.
includes
(
this
.
currentOperator
);
...
...
@@ -377,6 +379,7 @@
'loadFromLocal'
,
'loadFromRemote'
,
'loadFromDataUrl'
,
'loadFromData'
,
"saveToLocal"
,
"saveToRemote"
,
'updateEnv'
,
...
...
src/views/Editor/dialogs/CodeConflictResolveDialog.vue
0 → 100644
View file @
5e7b8896
<
template
>
<el-dialog
:title=
"$t('Project conflict resolver')"
width=
"70%"
:visible
.
sync=
"visible"
@
opened=
"onOpen"
@
close=
"onClose"
:close-on-click-modal=
"false"
:append-to-body=
"true"
fullscreen
custom-class=
"flex-dialog code-conflict-resolve-editor"
>
<div
class=
"wrapper"
>
<el-steps
:active=
"step"
finish-status=
"success"
align-center
>
<el-step
v-for=
"(item, index) in steps"
:title=
"item"
:key=
"index"
></el-step>
</el-steps>
<div
class=
"container"
>
<monaco-editor
ref=
"diffEditor"
class=
"editor"
v-model=
"localData"
language=
"json"
:options=
"monacoEditorOptions"
diff-editor
:original=
"remoteData"
v-show=
"step===0"
/>
<monaco-editor
ref=
"editor"
class=
"editor"
v-model=
"localData"
language=
"json"
:options=
"monacoEditorOptions"
v-show=
"step===1"
@
change=
"onChange"
/>
</div>
</div>
<div
slot=
"footer"
class=
"dialog-footer"
>
<div>
<el-button
size=
"mini"
@
click=
"mergeConflicts"
type=
"success"
v-show=
"step===0"
>
{{
$t
(
'Merge conflicts'
)
}}
</el-button>
<el-button-group
v-show=
"step===1"
>
<el-button
size=
"mini"
@
click=
"revealPreviousConflict(-1)"
type=
"success"
>
{{
$t
(
'Previous Conflict'
)
}}
</el-button>
<el-button
size=
"mini"
@
click=
"revealPreviousConflict(1)"
type=
"success"
>
{{
$t
(
'Next Conflict'
)
}}
</el-button>
</el-button-group>
</div>
<div>
<el-button
size=
"mini"
@
click=
"onCancel"
>
{{
$t
(
'Cancel'
)
}}
</el-button>
<el-button
size=
"mini"
@
click=
"onSave"
type=
"primary"
:disabled=
"step===0"
>
{{
$t
(
'Commit'
)
}}
</el-button>
</div>
</div>
</el-dialog>
</
template
>
<
script
>
import
{
mapMutations
}
from
'vuex'
;
import
MonacoEditor
from
"vue-monaco"
;
import
{
formatJson
,
monacoEditorOptions
}
from
"../../../utils"
;
import
SplitPanes
from
'splitpanes'
import
i18n
from
"@/i18n"
;
const
startHeaderMarker
=
'<<<<<<< REMOTE'
;
const
splitterMarker
=
'======='
;
const
endFooterMarker
=
'>>>>>>> LOCAL'
;
const
conflictMarkers
=
[
startHeaderMarker
,
splitterMarker
,
endFooterMarker
];
const
lensTitles
=
[
i18n
.
t
(
'Accept Remote'
),
i18n
.
t
(
'Accept Local'
),
i18n
.
t
(
'Accept Both'
)];
const
delayTrackDuration
=
500
;
export
default
{
name
:
"CodeConflictResolveDialog"
,
components
:
{
MonacoEditor
,
SplitPanes
},
data
()
{
let
monacoEditorOptionsSelf
=
Object
.
assign
({
readOnly
:
true
,
},
monacoEditorOptions
);
return
{
monacoEditorOptions
:
monacoEditorOptionsSelf
,
visible
:
false
,
step
:
0
,
remoteData
:
''
,
localData
:
''
,
steps
:
this
.
$t
(
'project-conflicts-resolve-steps'
),
}
},
mounted
()
{
this
.
codeLensProviders
=
[];
this
.
conflicts
=
[];
this
.
conflictIndex
=
0
;
this
.
decrations
=
[];
},
destroyed
()
{
},
methods
:
{
show
(
remoteData
,
localData
)
{
this
.
remoteData
=
''
;
this
.
localData
=
''
;
this
.
$nextTick
(()
=>
{
this
.
remoteData
=
this
.
formatJson
(
remoteData
||
''
);
this
.
localData
=
this
.
formatJson
(
localData
||
''
);
this
.
step
=
0
;
this
.
visible
=
true
;
this
.
monacoEditorOptions
.
readOnly
=
true
;
this
.
diff
=
null
;
this
.
$nextTick
(()
=>
{
if
(
!
this
.
inited
)
{
this
.
inited
=
true
;
let
diffEditor
=
this
.
$refs
.
diffEditor
;
let
editor
=
diffEditor
.
getEditor
();
editor
.
onDidUpdateDiff
(()
=>
{
if
(
!
this
.
diff
)
{
this
.
diff
=
editor
.
getLineChanges
();
}
});
let
resolveEditorIns
=
this
.
$refs
.
editor
.
getEditor
();
this
.
mergeConflictCommand
=
resolveEditorIns
.
addCommand
(
0
,
(
getService
,
action
,
conflict
)
=>
{
this
.
resolveConflict
(
action
,
conflict
);
},
''
);
}
});
});
},
async
onSave
()
{
let
resolved
=
true
;
for
(
let
conflictFlag
of
conflictMarkers
)
{
if
(
this
.
localData
.
includes
(
conflictFlag
))
{
resolved
=
false
;
break
;
}
}
if
(
resolved
)
{
try
{
JSON
.
parse
(
this
.
localData
);
await
this
.
$confirm
(
this
.
$t
(
'The conflict has been resolved'
),
this
.
$t
(
'Alert'
),
{
confirmButtonText
:
this
.
$t
(
'Confirm'
),
cancelButtonText
:
this
.
$t
(
'Cancel'
),
type
:
'warning'
}).
then
(()
=>
{
this
.
step
++
;
this
.
visible
=
false
;
this
.
$emit
(
'resolved'
,
this
.
localData
);
}).
catch
((
e
)
=>
{
});
}
catch
(
e
)
{
this
.
$alert
(
this
.
$t
(
'The format of the JSON document is wrong'
),
{
type
:
'warning'
});
}
}
else
{
this
.
$alert
(
this
.
$t
(
'There are still unresolved conflicts'
),
{
type
:
'warning'
});
}
},
onCancel
()
{
this
.
visible
=
false
;
},
onClose
()
{
this
.
removeAllCodeLensProviders
();
this
.
removeAllDecorations
();
},
onOpen
()
{
},
formatJson
(
source
)
{
return
formatJson
(
source
);
},
onChange
(
t
)
{
if
(
this
.
timerTrack
)
{
clearTimeout
(
this
.
timerTrack
);
this
.
timerTrack
=
null
;
}
this
.
timerTrack
=
setTimeout
(()
=>
{
this
.
delayTrack
()
},
delayTrackDuration
);
},
removeAllCodeLensProviders
()
{
while
(
this
.
codeLensProviders
.
length
>
0
)
{
let
codeLensProvider
=
this
.
codeLensProviders
.
pop
();
codeLensProvider
.
dispose
();
}
},
removeAllDecorations
()
{
let
editor
=
this
.
$refs
.
editor
;
let
resolveEditor
=
editor
.
getEditor
();
resolveEditor
.
deltaDecorations
(
this
.
decrations
,
[]);
this
.
decrations
.
splice
(
0
);
},
delayTrack
()
{
console
.
log
(
'delayTrack'
);
let
lines
=
this
.
localData
.
split
(
'
\
n'
);
this
.
conflicts
.
splice
(
0
);
let
currentConflict
=
null
;
let
conflicts
=
this
.
conflicts
;
for
(
let
i
=
0
,
li
=
lines
.
length
;
i
<
li
;
i
++
)
{
const
line
=
lines
[
i
];
if
(
!
line
)
{
continue
;
}
if
(
line
.
startsWith
(
startHeaderMarker
))
{
if
(
currentConflict
!==
null
)
{
currentConflict
=
null
;
break
;
}
currentConflict
=
{
remoteLine
:
i
};
}
else
if
(
currentConflict
&&
!
currentConflict
.
splitLine
&&
line
.
startsWith
(
splitterMarker
))
{
currentConflict
.
splitLine
=
i
;
}
else
if
(
currentConflict
&&
currentConflict
.
splitLine
&&
line
.
startsWith
(
endFooterMarker
))
{
currentConflict
.
localLine
=
i
;
conflicts
.
push
(
currentConflict
);
currentConflict
=
null
;
}
}
this
.
removeAllCodeLensProviders
();
this
.
removeAllDecorations
();
for
(
let
conflict
of
conflicts
)
{
const
{
remoteLine
,
splitLine
,
localLine
}
=
conflict
;
let
line
=
remoteLine
+
1
;
let
range
=
{
startLineNumber
:
line
,
startColumn
:
line
,
endLineNumber
:
line
,
endColumn
:
line
};
let
id
=
line
.
toString
();
let
lenses
=
[];
for
(
let
i
=
0
,
li
=
lensTitles
.
length
;
i
<
li
;
i
++
)
{
const
lensTitle
=
lensTitles
[
i
];
lenses
.
push
({
range
,
id
,
command
:
{
id
:
this
.
mergeConflictCommand
,
title
:
lensTitle
,
arguments
:
[
i
,
conflict
],
}
})
}
let
editor
=
this
.
$refs
.
editor
;
let
resolveEditor
=
editor
.
getEditor
();
let
codeLensProvider
=
editor
.
monaco
.
languages
.
registerCodeLensProvider
(
'json'
,
{
provideCodeLenses
:
function
(
model
,
token
)
{
return
{
lenses
,
dispose
()
{
}
};
},
resolveCodeLens
:
function
(
model
,
codeLens
,
token
)
{
return
codeLens
;
}
});
this
.
codeLensProviders
.
push
(
codeLensProvider
);
this
.
revealPreviousConflict
();
this
.
$nextTick
(()
=>
{
let
deltaDecorations
=
[
{
range
:
{
startLineNumber
:
remoteLine
+
1
,
endLineNumber
:
remoteLine
+
1
,
},
options
:
{
isWholeLine
:
true
,
className
:
'remote-change-marker-class'
}
},
];
if
(
splitLine
-
remoteLine
>
1
)
{
deltaDecorations
.
push
({
range
:
{
startLineNumber
:
remoteLine
+
2
,
endLineNumber
:
splitLine
,
},
options
:
{
isWholeLine
:
true
,
className
:
'remote-change-class'
}
},)
}
if
(
localLine
-
splitLine
>
1
)
{
deltaDecorations
.
push
({
range
:
{
startLineNumber
:
splitLine
+
2
,
endLineNumber
:
localLine
,
},
options
:
{
isWholeLine
:
true
,
className
:
'local-change-class'
}
},)
}
deltaDecorations
.
push
({
range
:
{
startLineNumber
:
localLine
+
1
,
endLineNumber
:
localLine
+
1
,
},
options
:
{
isWholeLine
:
true
,
className
:
'local-change-marker-class'
}
});
let
decorations
=
resolveEditor
.
deltaDecorations
([],
deltaDecorations
);
this
.
decrations
.
push
(...
decorations
);
});
}
},
revealPreviousConflict
(
dir
=
0
)
{
this
.
conflictIndex
+=
dir
;
if
(
this
.
conflictIndex
<
0
)
{
this
.
conflictIndex
+=
this
.
conflicts
.
length
;
}
this
.
conflictIndex
=
this
.
conflictIndex
%
this
.
conflicts
.
length
;
let
conflict
=
this
.
conflicts
[
this
.
conflictIndex
];
let
resolveEditorIns
=
this
.
$refs
.
editor
.
getEditor
();
resolveEditorIns
.
revealLineInCenter
(
conflict
.
remoteLine
);
},
mergeConflicts
()
{
this
.
monacoEditorOptions
.
readOnly
=
false
;
this
.
$nextTick
(()
=>
{
this
.
step
++
;
});
let
remoteCodeLines
=
this
.
remoteData
.
split
(
'
\
n'
);
let
localCodeLines
=
this
.
localData
.
split
(
'
\
n'
);
let
mergedCodeLines
=
localCodeLines
.
concat
();
//console.log(this.diff);
let
offset
=
0
;
for
(
let
{
modifiedStartLineNumber
,
modifiedEndLineNumber
,
originalStartLineNumber
,
originalEndLineNumber
}
of
this
.
diff
)
{
let
remotePart
=
[];
for
(
let
i
=
originalStartLineNumber
,
li
=
originalEndLineNumber
;
i
<=
li
;
i
++
)
{
remotePart
.
push
(
remoteCodeLines
[
i
-
1
]);
}
let
localPart
=
[];
for
(
let
i
=
modifiedStartLineNumber
,
li
=
modifiedEndLineNumber
;
i
<=
li
;
i
++
)
{
localPart
.
push
(
localCodeLines
[
i
-
1
]);
}
let
merged
=
[];
merged
.
push
(
conflictMarkers
[
0
]);
merged
.
push
(...
remotePart
);
merged
.
push
(
conflictMarkers
[
1
]);
merged
.
push
(...
localPart
);
merged
.
push
(
conflictMarkers
[
2
]);
//console.log(merged);
let
startLine
=
modifiedStartLineNumber
+
offset
+
(
modifiedEndLineNumber
===
0
?
1
:
0
);
mergedCodeLines
.
splice
(
startLine
-
1
,
modifiedEndLineNumber
-
modifiedStartLineNumber
+
1
,
...
merged
);
offset
+=
3
+
remotePart
.
length
;
}
this
.
localData
=
mergedCodeLines
.
join
(
'
\
n'
);
this
.
delayTrack
();
},
resolveConflict
(
action
,
conflict
)
{
let
resolveEditorIns
=
this
.
$refs
.
editor
.
getEditor
();
let
model
=
resolveEditorIns
.
getModel
();
let
lines
=
this
.
localData
.
split
(
'
\
n'
);
let
replacer
;
switch
(
action
)
{
case
0
:
replacer
=
getTextWithRange
(
conflict
.
remoteLine
,
conflict
.
splitLine
);
break
;
case
1
:
replacer
=
getTextWithRange
(
conflict
.
splitLine
,
conflict
.
localLine
);
break
;
case
2
:
let
remoteReplacer
=
getTextWithRange
(
conflict
.
remoteLine
,
conflict
.
splitLine
);
let
localReplacer
=
getTextWithRange
(
conflict
.
splitLine
,
conflict
.
localLine
);
if
(
remoteReplacer
===
undefined
)
{
if
(
localReplacer
!==
undefined
)
{
replacer
=
localReplacer
;
}
}
else
{
if
(
localReplacer
===
undefined
)
{
replacer
=
remoteReplacer
;
}
else
{
replacer
=
remoteReplacer
+
'
\
n'
+
localReplacer
;
}
}
break
;
}
/*if (replacer !== undefined) {
replaceText(replacer);
//this.localData = lines.join('\n');
}*/
replaceText
(
replacer
);
this
.
delayTrack
();
this
.
conflictIndex
--
;
this
.
conflictIndex
=
Math
.
max
(
this
.
conflictIndex
,
0
);
function
getTextWithRange
(
start
,
end
)
{
if
(
end
-
start
>
1
)
{
let
resultLines
=
[];
for
(
let
i
=
start
+
1
;
i
<
end
;
i
++
)
{
resultLines
.
push
(
lines
[
i
]);
}
return
resultLines
.
join
(
'
\
n'
);
}
return
null
;
}
function
replaceText
(
replacer
)
{
let
op
=
{
range
:
{
startLineNumber
:
conflict
.
remoteLine
+
1
,
startColumn
:
1
,
endLineNumber
:
conflict
.
localLine
+
1
,
endColumn
:
Number
.
MAX_VALUE
,
},
text
:
replacer
,
};
if
(
replacer
===
null
)
{
op
.
range
.
startLineNumber
=
conflict
.
remoteLine
;
op
.
range
.
startColumn
=
Number
.
MAX_VALUE
;
}
model
.
pushEditOperations
([],
[
op
],
()
=>
[]);
}
},
...
mapMutations
([
'modifyProjectDetails'
,
]),
}
}
</
script
>
<
style
scoped
>
</
style
>
\ No newline at end of file
src/views/Editor/dialogs/ProjectConflictResolveDialog.vue
View file @
5e7b8896
<
template
>
<el-dialog
:title=
"$t('Project conflict resolver')"
width=
"
70%
"
:visible
.
sync=
"visible"
@
opened=
"onOpen"
<el-dialog
:title=
"$t('Project conflict resolver')"
width=
"
80vw
"
:visible
.
sync=
"visible"
@
opened=
"onOpen"
@
close=
"onClose"
:close-on-click-modal=
"false"
:append-to-body=
"true"
fullscreen
custom-class=
"flex-dialog project-conflict-resolve-editor"
>
<div
class=
"wrapper"
>
<el-steps
:active=
"step"
finish-status=
"success"
align-center
>
<el-step
v-for=
"(item, index) in steps"
:title=
"item"
:key=
"index"
></el-step>
</el-steps>
<div
class=
"container"
>
<monaco-editor
ref=
"diffEditor"
class=
"editor"
v-model=
"localData"
language=
"json"
:options=
"monacoEditorOptions"
diff-editor
:original=
"remoteData"
v-show=
"step===0"
/>
<monaco-editor
ref=
"editor"
class=
"editor"
v-model=
"localData"
language=
"json"
:options=
"monacoEditorOptions"
v-show=
"step===1"
@
change=
"onChange"
/>
</div>
<span>
冲突数:
{{
conflictCounting
}}
</span>
<el-tabs
tab-position=
"left"
class=
"category-tab"
>
<el-tab-pane
v-for=
"(categoryConfig, category) in categories"
:key=
"category"
:label=
"`$
{categoryConfig.label}(${conflictCountingMap[category]})`">
<list-compare-view
class=
"compare-view"
:compares=
"compareGroup[category]"
:category=
"category"
@
merge-conflict=
"mergeConflict"
/>
</el-tab-pane>
</el-tabs>
</div>
<div
slot=
"footer"
class=
"dialog-footer"
>
<div>
<el-button
size=
"mini"
@
click=
"mergeConflicts"
type=
"success"
v-show=
"step===0"
>
{{
$t
(
'Merge conflicts'
)
}}
</el-button>
<el-button-group
v-show=
"step===1"
>
<el-button
size=
"mini"
@
click=
"revealPreviousConflict(-1)"
type=
"success"
>
{{
$t
(
'Previous Conflict'
)
}}
</el-button>
<el-button
size=
"mini"
@
click=
"revealPreviousConflict(1)"
type=
"success"
>
{{
$t
(
'Next Conflict'
)
}}
</el-button>
</el-button-group>
</div>
<div>
<el-button
size=
"mini"
@
click=
"onCancel"
>
{{
$t
(
'Cancel'
)
}}
</el-button>
<el-button
size=
"mini"
@
click=
"onSave"
type=
"primary"
:disabled=
"step===0"
>
{{
$t
(
'Save'
)
}}
</el-button>
<el-button
size=
"mini"
@
click=
"onSave"
type=
"primary"
:disabled=
"conflictCounting > 0"
>
{{
$t
(
'Save'
)
}}
</el-button>
</div>
</div>
<code-conflict-resolve-dialog
ref=
"codeConflictResolveDialog"
@
resolved=
"onCodeConflictResolved"
/>
</el-dialog>
</
template
>
<
script
>
import
{
mapMutations
}
from
'vuex'
;
import
MonacoEditor
from
"vue-monaco"
;
import
{
formatJson
,
monacoEditorOptions
}
from
"../../../utils"
;
import
SplitPanes
from
'splitpanes'
import
i18n
from
"@/i18n"
;
const
startHeaderMarker
=
'<<<<<<< REMOTE'
;
const
splitterMarker
=
'======='
;
const
endFooterMarker
=
'>>>>>>> LOCAL'
;
const
conflictMarkers
=
[
startHeaderMarker
,
splitterMarker
,
endFooterMarker
];
const
lensTitles
=
[
i18n
.
t
(
'Accept Remote'
),
i18n
.
t
(
'Accept Local'
),
i18n
.
t
(
'Accept Both'
)];
const
delayTrackDuration
=
500
;
import
ListCompareView
from
"./ProjectConflictResolveDialog/ListCompareView"
;
import
CodeConflictResolveDialog
from
"./CodeConflictResolveDialog"
;
export
default
{
name
:
"ProjectConflictResolveDialog"
,
components
:
{
MonacoEditor
,
SplitPanes
},
components
:
{
CodeConflictResolveDialog
,
ListCompareView
},
data
()
{
let
monacoEditorOptionsSelf
=
Object
.
assign
({
readOnly
:
true
,
},
monacoEditorOptions
);
let
categories
=
this
.
$t
(
'categories'
);
let
conflictCountingMap
=
{};
for
(
let
key
in
categories
)
{
conflictCountingMap
[
key
]
=
0
;
}
return
{
monacoEditorOptions
:
monacoEditorOptionsSelf
,
visible
:
false
,
step
:
0
,
remoteData
:
''
,
localData
:
''
,
steps
:
this
.
$t
(
'project-conflicts-resolve-steps'
),
categories
,
compareGroup
:
{},
conflictCountingMap
,
}
},
mounted
()
{
this
.
codeLensProviders
=
[];
this
.
conflicts
=
[]
;
this
.
conflictIndex
=
0
;
this
.
decrations
=
[]
;
},
destroyed
()
{
computed
:
{
conflictCounting
()
{
let
t
=
0
;
for
(
let
count
of
Object
.
values
(
this
.
conflictCountingMap
))
{
t
+=
count
;
}
return
t
;
}
},
methods
:
{
show
(
remoteData
,
localData
)
{
this
.
remoteData
=
''
;
this
.
localData
=
''
;
this
.
$nextTick
(()
=>
{
this
.
remoteData
=
this
.
formatJson
(
remoteData
);
this
.
localData
=
this
.
formatJson
(
localData
);
this
.
step
=
0
;
this
.
visible
=
true
;
this
.
monacoEditorOptions
.
readOnly
=
true
;
this
.
diff
=
null
;
this
.
$nextTick
(()
=>
{
if
(
!
this
.
inited
)
{
this
.
inited
=
true
;
let
diffEditor
=
this
.
$refs
.
diffEditor
;
let
editor
=
diffEditor
.
getEditor
();
editor
.
onDidUpdateDiff
(()
=>
{
if
(
!
this
.
diff
)
{
this
.
diff
=
editor
.
getLineChanges
();
}
});
let
resolveEditorIns
=
this
.
$refs
.
editor
.
getEditor
();
this
.
mergeConflictCommand
=
resolveEditorIns
.
addCommand
(
0
,
(
getService
,
action
,
conflict
)
=>
{
this
.
resolveConflict
(
action
,
conflict
);
},
''
);
}
});
});
},
async
onSave
()
{
let
resolved
=
true
;
for
(
let
conflictFlag
of
conflictMarkers
)
{
if
(
this
.
localData
.
includes
(
conflictFlag
))
{
resolved
=
false
;
break
;
}
try
{
this
.
analyseCompare
(
remoteData
,
localData
);
}
catch
(
e
)
{
console
.
log
(
e
);
}
if
(
resolved
)
{
try
{
JSON
.
parse
(
this
.
localData
);
await
this
.
$confirm
(
this
.
$t
(
'The conflict has been resolved'
),
this
.
$t
(
'Alert'
),
{
confirmButtonText
:
this
.
$t
(
'Confirm'
),
cancelButtonText
:
this
.
$t
(
'Cancel'
),
type
:
'warning'
}).
then
(()
=>
{
this
.
step
++
;
this
.
visible
=
false
;
this
.
$emit
(
'resolved'
,
this
.
localData
);
}).
catch
((
e
)
=>
{
});
}
catch
(
e
)
{
this
.
$alert
(
this
.
$t
(
'The format of the JSON document is wrong'
),
{
type
:
'warning'
});
}
}
else
{
this
.
$alert
(
this
.
$t
(
'There are still unresolved conflicts'
),
{
type
:
'warning'
});
}
},
onCancel
()
{
this
.
visible
=
false
;
this
.
visible
=
true
;
},
onClose
()
{
this
.
removeAllCodeLensProviders
();
this
.
removeAllDecorations
();
},
onOpen
()
{
},
formatJson
(
source
)
{
return
formatJson
(
source
);
},
onChange
(
t
)
{
if
(
this
.
timerTrack
)
{
clearTimeout
(
this
.
timerTrack
);
this
.
timerTrack
=
null
;
}
this
.
timerTrack
=
setTimeout
(()
=>
{
this
.
delayTrack
()
},
delayTrackDuration
);
},
removeAllCodeLensProviders
()
{
while
(
this
.
codeLensProviders
.
length
>
0
)
{
let
codeLensProvider
=
this
.
codeLensProviders
.
pop
();
codeLensProvider
.
dispose
();
}
async
onSave
()
{
await
this
.
$confirm
(
this
.
$t
(
'All conflict has been resolved'
),
this
.
$t
(
'Alert'
),
{
confirmButtonText
:
this
.
$t
(
'Confirm'
),
cancelButtonText
:
this
.
$t
(
'Cancel'
),
type
:
'warning'
}).
then
(()
=>
{
this
.
step
++
;
this
.
visible
=
false
;
this
.
$emit
(
'resolved'
,
JSON
.
stringify
(
this
.
localObj
));
}).
catch
((
e
)
=>
{
});
this
.
visible
=
false
;
},
removeAllDecorations
()
{
let
editor
=
this
.
$refs
.
editor
;
let
resolveEditor
=
editor
.
getEditor
();
onCancel
()
{
resolveEditor
.
deltaDecorations
(
this
.
decrations
,
[]);
this
.
decrations
.
splice
(
0
);
this
.
visible
=
false
;
},
delayTrack
()
{
console
.
log
(
'delayTrack'
);
let
lines
=
this
.
localData
.
split
(
'
\
n'
);
analyseCompare
(
remoteData
,
localData
)
{
let
remoteJson
=
JSON
.
parse
(
remoteData
);
let
localJson
=
JSON
.
parse
(
localData
);
this
.
conflicts
.
splice
(
0
);
let
currentConflict
=
null
;
let
conflicts
=
this
.
conflicts
;
for
(
let
i
=
0
,
li
=
lines
.
length
;
i
<
li
;
i
++
)
{
const
line
=
lines
[
i
];
if
(
!
line
)
{
continue
;
}
if
(
line
.
startsWith
(
startHeaderMarker
))
{
if
(
currentConflict
!==
null
)
{
currentConflict
=
null
;
break
;
}
currentConflict
=
{
remoteLine
:
i
};
}
else
if
(
currentConflict
&&
!
currentConflict
.
splitLine
&&
line
.
startsWith
(
splitterMarker
))
{
currentConflict
.
splitLine
=
i
;
}
else
if
(
currentConflict
&&
currentConflict
.
splitLine
&&
line
.
startsWith
(
endFooterMarker
))
{
currentConflict
.
localLine
=
i
;
this
.
localObj
=
localJson
;
let
that
=
this
;
conflicts
.
push
(
currentConflict
);
currentConflict
=
null
;
}
for
(
let
key
in
this
.
categories
)
{
this
.
conflictCountingMap
[
key
]
=
0
;
}
this
.
removeAllCodeLensProviders
();
this
.
removeAllDecorations
();
for
(
let
conflict
of
conflicts
)
{
const
{
remoteLine
,
splitLine
,
localLine
}
=
conflict
;
let
line
=
remoteLine
+
1
;
let
range
=
{
startLineNumber
:
line
,
startColumn
:
line
,
endLineNumber
:
line
,
endColumn
:
line
};
let
id
=
line
.
toString
();
let
lenses
=
[];
for
(
let
i
=
0
,
li
=
lensTitles
.
length
;
i
<
li
;
i
++
)
{
const
lensTitle
=
lensTitles
[
i
];
lenses
.
push
({
range
,
id
,
command
:
{
id
:
this
.
mergeConflictCommand
,
title
:
lensTitle
,
arguments
:
[
i
,
conflict
],
}
})
}
let
editor
=
this
.
$refs
.
editor
;
let
resolveEditor
=
editor
.
getEditor
();
let
codeLensProvider
=
editor
.
monaco
.
languages
.
registerCodeLensProvider
(
'json'
,
{
provideCodeLenses
:
function
(
model
,
token
)
{
return
{
lenses
,
dispose
()
{
}
};
},
resolveCodeLens
:
function
(
model
,
codeLens
,
token
)
{
return
codeLens
;
}
});
this
.
codeLensProviders
.
push
(
codeLensProvider
);
this
.
revealPreviousConflict
();
this
.
$nextTick
(()
=>
{
let
deltaDecorations
=
[
{
range
:
{
startLineNumber
:
remoteLine
+
1
,
endLineNumber
:
remoteLine
+
1
,
},
options
:
{
isWholeLine
:
true
,
className
:
'remote-change-marker-class'
}
},
];
if
(
splitLine
-
remoteLine
>
1
)
{
deltaDecorations
.
push
({
range
:
{
startLineNumber
:
remoteLine
+
2
,
endLineNumber
:
splitLine
,
},
options
:
{
isWholeLine
:
true
,
className
:
'remote-change-class'
}
},)
let
compareGroup
=
{};
for
(
let
category
in
this
.
categories
)
{
let
categoryConfig
=
this
.
categories
[
category
];
let
compares
=
[];
if
(
categoryConfig
.
simple
)
{
addCompare
(
category
,
compares
,
remoteJson
[
category
],
localJson
[
category
]);
}
else
{
let
{
key
,
name
}
=
categoryConfig
;
let
remote
=
remoteJson
[
category
]
||
[];
let
local
=
localJson
[
category
]
||
[];
let
analysedUUIDs
=
[];
for
(
let
remoteItem
of
remote
)
{
const
localItem
=
local
.
find
(
item
=>
item
[
key
]
===
remoteItem
[
key
]);
addCompare
(
category
,
compares
,
remoteItem
,
localItem
,
remoteItem
[
key
],
name
);
analysedUUIDs
.
push
(
remoteItem
[
key
]);
}
if
(
localLine
-
splitLine
>
1
)
{
deltaDecorations
.
push
({
range
:
{
startLineNumber
:
splitLine
+
2
,
endLineNumber
:
localLine
,
},
options
:
{
isWholeLine
:
true
,
className
:
'local-change-class'
}
},)
for
(
let
localItem
of
local
)
{
if
(
analysedUUIDs
.
includes
(
localItem
[
key
]))
{
continue
;
}
const
remoteItem
=
remote
.
find
(
item
=>
item
[
key
]
===
localItem
[
key
]);
addCompare
(
category
,
compares
,
remoteItem
,
localItem
,
localItem
[
key
],
name
);
analysedUUIDs
.
push
(
localItem
[
key
]);
}
deltaDecorations
.
push
({
range
:
{
startLineNumber
:
localLine
+
1
,
endLineNumber
:
localLine
+
1
,
},
options
:
{
isWholeLine
:
true
,
className
:
'local-change-marker-class'
}
});
let
decorations
=
resolveEditor
.
deltaDecorations
([],
deltaDecorations
);
this
.
decrations
.
push
(...
decorations
);
});
}
},
revealPreviousConflict
(
dir
=
0
)
{
this
.
conflictIndex
+=
dir
;
if
(
this
.
conflictIndex
<
0
)
{
this
.
conflictIndex
+=
this
.
conflicts
.
length
;
}
compareGroup
[
category
]
=
compares
;
}
this
.
conflictIndex
=
this
.
conflictIndex
%
this
.
conflicts
.
length
;
let
conflict
=
this
.
conflicts
[
this
.
conflictIndex
];
let
resolveEditorIns
=
this
.
$refs
.
editor
.
getEditor
();
resolveEditorIns
.
revealLineInCenter
(
conflict
.
remoteLine
);
},
mergeConflicts
()
{
this
.
monacoEditorOptions
.
readOnly
=
false
;
this
.
$nextTick
(()
=>
{
this
.
step
++
;
});
let
remoteCodeLines
=
this
.
remoteData
.
split
(
'
\
n'
);
let
localCodeLines
=
this
.
localData
.
split
(
'
\
n'
);
let
mergedCodeLines
=
localCodeLines
.
concat
();
this
.
compareGroup
=
compareGroup
;
//console.log(this.diff);
let
offset
=
0
;
for
(
let
{
modifiedStartLineNumber
,
modifiedEndLineNumber
,
originalStartLineNumber
,
originalEndLineNumber
}
of
this
.
diff
)
{
let
remotePart
=
[];
for
(
let
i
=
originalStartLineNumber
,
li
=
originalEndLineNumber
;
i
<=
li
;
i
++
)
{
remotePart
.
push
(
remoteCodeLines
[
i
-
1
]);
function
addCompare
(
category
,
compares
,
remote
,
local
,
key
,
name
=
'name'
)
{
let
remoteStr
=
remote
?
JSON
.
stringify
(
remote
)
:
''
;
let
localStr
=
local
?
JSON
.
stringify
(
local
)
:
''
;
let
compare
=
{
resolved
:
!
((
!
remote
||
!
local
)
||
remoteStr
!==
localStr
),
};
if
(
remote
)
{
compare
.
remote
=
{
name
:
remote
[
name
],
key
,
data
:
remoteStr
,
obj
:
remote
};
}
let
localPart
=
[];
for
(
let
i
=
modifiedStartLineNumber
,
li
=
modifiedEndLineNumber
;
i
<=
li
;
i
++
)
{
localPart
.
push
(
localCodeLines
[
i
-
1
]);
if
(
local
)
{
compare
.
local
=
{
name
:
local
[
name
],
key
,
data
:
localStr
,
obj
:
local
};
}
let
merged
=
[];
merged
.
push
(
conflictMarkers
[
0
]);
merged
.
push
(...
remotePart
);
merged
.
push
(
conflictMarkers
[
1
]);
merged
.
push
(...
localPart
);
merged
.
push
(
conflictMarkers
[
2
]);
//console.log(merged);
compares
.
push
(
compare
);
let
startLine
=
modifiedStartLineNumber
+
offset
+
(
modifiedEndLineNumber
===
0
?
1
:
0
);
mergedCodeLines
.
splice
(
startLine
-
1
,
modifiedEndLineNumber
-
modifiedStartLineNumber
+
1
,
...
merged
)
;
offset
+=
3
+
remotePart
.
length
;
if
(
!
compare
.
resolved
)
{
that
.
conflictCountingMap
[
category
]
++
;
}
}
this
.
localData
=
mergedCodeLines
.
join
(
'
\
n'
);
this
.
delayTrack
();
},
resolveConflict
(
action
,
conflict
)
{
let
resolveEditorIns
=
this
.
$refs
.
editor
.
getEditor
();
let
model
=
resolveEditorIns
.
getModel
();
let
lines
=
this
.
localData
.
split
(
'
\
n'
);
let
replacer
;
mergeConflict
(
action
,
conflict
,
category
)
{
const
{
remote
,
local
}
=
conflict
;
switch
(
action
)
{
case
0
:
replacer
=
getTextWithRange
(
conflict
.
remoteLine
,
conflict
.
splitLine
);
case
'remote'
:
this
.
reSaveConflict
(
conflict
,
remote
,
category
);
break
;
case
1
:
replacer
=
getTextWithRange
(
conflict
.
splitLine
,
conflict
.
localLine
);
case
'local'
:
this
.
reSaveConflict
(
conflict
,
local
,
category
);
break
;
case
2
:
let
remoteReplacer
=
getTextWithRange
(
conflict
.
remoteLine
,
conflict
.
splitLine
);
let
localReplacer
=
getTextWithRange
(
conflict
.
splitLine
,
conflict
.
localLine
);
if
(
remoteReplacer
===
undefined
)
{
if
(
localReplacer
!==
undefined
)
{
replacer
=
localReplacer
;
}
}
else
{
if
(
localReplacer
===
undefined
)
{
replacer
=
remoteReplacer
;
}
else
{
replacer
=
remoteReplacer
+
'
\
n'
+
localReplacer
;
}
}
case
'manual'
:
this
.
lastConflict
=
conflict
;
this
.
lastCategory
=
category
;
this
.
$refs
.
codeConflictResolveDialog
.
show
(
remote
?
remote
.
data
:
''
,
local
?
local
.
data
:
''
);
break
;
}
/*if (replacer !== undefined) {
replaceText(replacer);
//this.localData = lines.join('\n');
}*/
replaceText
(
replacer
);
this
.
delayTrack
();
this
.
conflictIndex
--
;
this
.
conflictIndex
=
Math
.
max
(
this
.
conflictIndex
,
0
);
function
getTextWithRange
(
start
,
end
)
{
if
(
end
-
start
>
1
)
{
let
resultLines
=
[];
for
(
let
i
=
start
+
1
;
i
<
end
;
i
++
)
{
resultLines
.
push
(
lines
[
i
]);
}
return
resultLines
.
join
(
'
\
n'
);
},
onCodeConflictResolved
(
data
)
{
if
(
this
.
lastConflict
)
{
this
.
reSaveConflict
(
this
.
lastConflict
,
data
,
this
.
lastCategory
);
}
},
reSaveConflict
(
conflict
,
result
,
category
)
{
let
resultData
;
if
(
result
)
{
if
(
typeof
result
.
obj
===
'string'
)
{
resultData
=
JSON
.
parse
(
result
.
obj
);
}
else
{
resultData
=
result
.
obj
;
}
return
null
;
}
function
replaceText
(
replacer
)
{
let
op
=
{
range
:
{
startLineNumber
:
conflict
.
remoteLine
+
1
,
startColumn
:
1
,
endLineNumber
:
conflict
.
localLine
+
1
,
endColumn
:
Number
.
MAX_VALUE
,
},
text
:
replacer
,
};
if
(
replacer
===
null
)
{
op
.
range
.
startLineNumber
=
conflict
.
remoteLine
;
op
.
range
.
startColumn
=
Number
.
MAX_VALUE
;
let
data
=
this
.
localObj
[
category
];
let
categoryConfig
=
this
.
categories
[
category
];
if
(
categoryConfig
.
simple
)
{
if
(
resultData
)
{
this
.
localObj
[
category
]
=
resultData
;
}
else
{
delete
this
.
localObj
[
category
];
}
}
else
{
if
(
conflict
.
local
)
{
data
.
some
((
item
,
index
)
=>
{
if
(
item
[
categoryConfig
.
key
]
===
conflict
.
local
.
key
)
{
if
(
resultData
)
{
data
[
index
]
=
resultData
;
}
else
{
data
.
splice
(
index
,
1
);
}
return
true
;
}
});
}
model
.
pushEditOperations
([],
[
op
],
()
=>
[]);
}
},
...
mapMutations
([
'modifyProjectDetails'
,
]),
this
.
$set
(
conflict
,
'resolved'
,
true
);
this
.
conflictCountingMap
[
category
]
--
;
}
}
}
</
script
>
...
...
src/views/Editor/dialogs/ProjectConflictResolveDialog/ListCompareView.vue
0 → 100644
View file @
5e7b8896
<
template
>
<el-table
:data=
"compares"
stripe
size=
"mini"
height=
"100%"
>
<el-table-column
prop=
"remote.name"
label=
"远程版本"
>
</el-table-column>
<el-table-column
prop=
"local.name"
label=
"本地版本"
>
</el-table-column>
<el-table-column
label=
"操作"
width=
"280px"
>
<template
slot-scope=
"scope"
>
<template
v-if=
"!scope.row.resolved"
>
<el-button
size=
"mini"
type=
"success"
@
click=
"mergeConflict('remote', scope.row)"
>
接受远程
</el-button>
<el-button
size=
"mini"
type=
"success"
@
click=
"mergeConflict('local', scope.row)"
>
接受本地
</el-button>
<el-button
size=
"mini"
type=
"warning"
@
click=
"mergeConflict('manual', scope.row)"
>
手动合并
</el-button>
</
template
>
<span
v-else
>
无冲突
</span>
</template>
</el-table-column>
</el-table>
</template>
<
script
>
export
default
{
name
:
"ListCompareView"
,
props
:
[
'category'
,
'compares'
],
data
()
{
return
{}
},
mounted
()
{
this
.
update
();
},
watch
:
{
conflicts
()
{
this
.
update
();
},
},
methods
:
{
update
()
{
},
mergeConflict
(
action
,
conflict
)
{
this
.
$emit
(
'merge-conflict'
,
action
,
conflict
,
this
.
category
);
},
}
}
</
script
>
<
style
scoped
>
</
style
>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment