Commit 5e7b8896 authored by rockyl's avatar rockyl

Merge branch 'dev' into res-group

parents b5e40cbb 2858b006
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
"Still Close": "直接关闭", "Still Close": "直接关闭",
"Save": "保存", "Save": "保存",
"Pack": "打包", "Pack": "打包",
"Commit": "提交",
"Save And Preview": "保存并预览", "Save And Preview": "保存并预览",
"Save And Close": "保存并关闭", "Save And Close": "保存并关闭",
"Reset": "重置", "Reset": "重置",
...@@ -235,7 +236,8 @@ ...@@ -235,7 +236,8 @@
"Merge conflicts": "合并冲突", "Merge conflicts": "合并冲突",
"There are still unresolved conflicts": "还有冲突未解决,不能保存!", "There are still unresolved conflicts": "还有冲突未解决,不能保存!",
"The format of the JSON document is wrong": "JSON文档格式有误,请先更正!", "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": "更新权限列表失败", "Failed to update operator": "更新权限列表失败",
"Error delete self": "连自己都删? 不可以!", "Error delete self": "连自己都删? 不可以!",
"No permission": "没有权限喔!", "No permission": "没有权限喔!",
...@@ -355,15 +357,6 @@ ...@@ -355,15 +357,6 @@
"test": "测试环境", "test": "测试环境",
"prod": "线上环境" "prod": "线上环境"
}, },
"QAList":[
"崔立强",
"李培培",
"劳奇峰",
"任建锋",
"张婷婷",
"王炽",
"王鸿源"
],
"tplOperates": [ "tplOperates": [
"新增", "新增",
"修改", "修改",
...@@ -405,5 +398,45 @@ ...@@ -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
...@@ -706,7 +706,7 @@ export const projectStore = { ...@@ -706,7 +706,7 @@ export const projectStore = {
//localStorage.removeItem('project-' + projectID); //localStorage.removeItem('project-' + projectID);
commit('setDirty', false); commit('setDirty', false);
}, },
async loadFromRemote({commit, dispatch}, projectID) { async loadFromRemote({commit, dispatch}, {projectID}) {
await dispatch('loadPackageInfos'); await dispatch('loadPackageInfos');
const project = await projectApi.fetchOne(projectID); const project = await projectApi.fetchOne(projectID);
if (project) { if (project) {
...@@ -716,18 +716,21 @@ export const projectStore = { ...@@ -716,18 +716,21 @@ export const projectStore = {
throw new Error('Project does not exist') throw new Error('Project does not exist')
} }
}, },
async loadFromDataUrl({commit, dispatch}, {project, dataUrl}) { async loadFromDataUrl({commit, dispatch}, {projectID, dataUrl}) {
await dispatch('loadPackageInfos');
project = await projectApi.fetchOne(project.id, false);
const projectData = await projectApi.fetchOneFromDataUrl(dataUrl); const projectData = await projectApi.fetchOneFromDataUrl(dataUrl);
if (projectData) { if (projectData) {
project.data = projectData; await dispatch('loadFromData', {projectID, projectData})
commit('updateProject', project);
dispatch('saveToLocal'); dispatch('saveToLocal');
} else { } else {
throw new Error('Project does not exist') 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}) { async saveToRemote({state, dispatch, getters, commit}, {remark, data}) {
let project = Object.assign({}, getters.project); let project = Object.assign({}, getters.project);
if (data) { if (data) {
......
...@@ -488,7 +488,7 @@ ...@@ -488,7 +488,7 @@
width: 80vw !important; width: 80vw !important;
} }
.project-conflict-resolve-editor{ .code-conflict-resolve-editor{
.wrapper{ .wrapper{
height: 100%; height: 100%;
padding: 10px; padding: 10px;
...@@ -506,6 +506,27 @@ ...@@ -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 { .remote-change-class {
background-color: rgba(64, 200, 174, 0.2); background-color: rgba(64, 200, 174, 0.2);
} }
......
...@@ -427,5 +427,5 @@ export function deleteAssetsDepConfig(data) { ...@@ -427,5 +427,5 @@ export function deleteAssetsDepConfig(data) {
} }
export function formatJson(source){ export function formatJson(source){
return JSON.stringify(JSON.parse(source), null, '\t') return source ? JSON.stringify(JSON.parse(source), null, '\t') : '';
} }
...@@ -170,10 +170,10 @@ ...@@ -170,10 +170,10 @@
this.ready = true; this.ready = true;
}); });
}, },
async loadRemoteVersion(projectID, project, dataUrl) { async loadRemoteVersion(projectID, project, dataUrl, projectData) {
if (projectID) { if (projectID) {
this.ready = false; 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 => { await playWaiting(p, this.$t('Preparing')).catch(e => {
this.$alert(this.$t('Project does not exist'), this.$t('Alert'), { this.$alert(this.$t('Project does not exist'), this.$t('Alert'), {
confirmButtonText: this.$t('Confirm'), confirmButtonText: this.$t('Confirm'),
...@@ -230,7 +230,9 @@ ...@@ -230,7 +230,9 @@
return cancel; return cancel;
}, },
async onConflictResolved(data) { 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() { checkAuth() {
let checkStatus = this.operators.includes(this.currentOperator); let checkStatus = this.operators.includes(this.currentOperator);
...@@ -377,6 +379,7 @@ ...@@ -377,6 +379,7 @@
'loadFromLocal', 'loadFromLocal',
'loadFromRemote', 'loadFromRemote',
'loadFromDataUrl', 'loadFromDataUrl',
'loadFromData',
"saveToLocal", "saveToLocal",
"saveToRemote", "saveToRemote",
'updateEnv', 'updateEnv',
......
<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
<template> <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="onClose"
:close-on-click-modal="false" :close-on-click-modal="false"
:append-to-body="true" :append-to-body="true"
fullscreen
custom-class="flex-dialog project-conflict-resolve-editor" custom-class="flex-dialog project-conflict-resolve-editor"
> >
<div class="wrapper"> <div class="wrapper">
<el-steps :active="step" finish-status="success" align-center> <span>冲突数:{{conflictCounting}}</span>
<el-step v-for="(item, index) in steps" :title="item" :key="index"></el-step> <el-tabs tab-position="left" class="category-tab">
</el-steps> <el-tab-pane v-for="(categoryConfig, category) in categories" :key="category"
<div class="container"> :label="`${categoryConfig.label}(${conflictCountingMap[category]})`">
<monaco-editor <list-compare-view class="compare-view" :compares="compareGroup[category]" :category="category"
ref="diffEditor" @merge-conflict="mergeConflict"/>
class="editor" </el-tab-pane>
v-model="localData" </el-tabs>
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>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<div> <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>
<div> <div>
<el-button size="mini" @click="onCancel">{{$t('Cancel')}}</el-button> <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>
</div> </div>
<code-conflict-resolve-dialog ref="codeConflictResolveDialog" @resolved="onCodeConflictResolved"/>
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import {mapMutations} from 'vuex'; import ListCompareView from "./ProjectConflictResolveDialog/ListCompareView";
import MonacoEditor from "vue-monaco"; import CodeConflictResolveDialog from "./CodeConflictResolveDialog";
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 { export default {
name: "ProjectConflictResolveDialog", name: "ProjectConflictResolveDialog",
components: {MonacoEditor, SplitPanes}, components: {CodeConflictResolveDialog, ListCompareView},
data() { data() {
let monacoEditorOptionsSelf = Object.assign({ let categories = this.$t('categories');
readOnly: true, let conflictCountingMap = {};
}, monacoEditorOptions); for (let key in categories) {
conflictCountingMap[key] = 0;
}
return { return {
monacoEditorOptions: monacoEditorOptionsSelf,
visible: false, visible: false,
step: 0, categories,
remoteData: '', compareGroup: {},
localData: '', conflictCountingMap,
steps: this.$t('project-conflicts-resolve-steps'),
} }
}, },
mounted() { computed: {
this.codeLensProviders = []; conflictCounting() {
this.conflicts = []; let t = 0;
this.conflictIndex = 0; for (let count of Object.values(this.conflictCountingMap)) {
this.decrations = []; t += count;
}, }
destroyed() { return t;
}
}, },
methods: { methods: {
show(remoteData, localData) { show(remoteData, localData) {
this.remoteData = ''; try {
this.localData = ''; this.analyseCompare(remoteData, localData);
} catch (e) {
this.$nextTick(() => { console.log(e);
this.remoteData = this.formatJson(remoteData); }
this.localData = this.formatJson(localData);
this.step = 0;
this.visible = true; this.visible = true;
this.monacoEditorOptions.readOnly = true; },
this.diff = null; onClose() {
this.$nextTick(() => {
if (!this.inited) {
this.inited = true;
let diffEditor = this.$refs.diffEditor; },
let editor = diffEditor.getEditor(); onOpen() {
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() { async onSave() {
let resolved = true; await this.$confirm(this.$t('All conflict has been resolved'), this.$t('Alert'), {
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'), confirmButtonText: this.$t('Confirm'),
cancelButtonText: this.$t('Cancel'), cancelButtonText: this.$t('Cancel'),
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.step++; this.step++;
this.visible = false; this.visible = false;
this.$emit('resolved', this.localData); this.$emit('resolved', JSON.stringify(this.localObj));
}).catch((e) => { }).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 = false;
}, },
onClose() { onCancel() {
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.visible = false;
this.decrations.splice(0);
}, },
delayTrack() { analyseCompare(remoteData, localData) {
console.log('delayTrack'); let remoteJson = JSON.parse(remoteData);
let localJson = JSON.parse(localData);
let lines = this.localData.split('\n');
this.conflicts.splice(0); this.localObj = localJson;
let currentConflict = null; let that = this;
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)) { for (let key in this.categories) {
if (currentConflict !== null) { this.conflictCountingMap[key] = 0;
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); let compareGroup = {};
for (let category in this.categories) {
let categoryConfig = this.categories[category];
let compares = [];
currentConflict = null; 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]);
}
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]);
} }
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],
} }
}) compareGroup[category] = compares;
} }
let editor = this.$refs.editor; this.compareGroup = compareGroup;
let resolveEditor = editor.getEditor();
let codeLensProvider = editor.monaco.languages.registerCodeLensProvider('json', {
provideCodeLenses: function (model, token) {
return {
lenses,
dispose() {
} 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) {
resolveCodeLens: function (model, codeLens, token) { compare.remote = {name: remote[name], key, data: remoteStr, obj: remote};
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) { if (local) {
deltaDecorations.push({ compare.local = {name: local[name], key, data: localStr, obj: local};
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'); compares.push(compare);
let localCodeLines = this.localData.split('\n');
let mergedCodeLines = localCodeLines.concat();
//console.log(this.diff); if (!compare.resolved) {
let offset = 0; that.conflictCountingMap[category]++;
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) { mergeConflict(action, conflict, category) {
let resolveEditorIns = this.$refs.editor.getEditor(); const {remote, local} = conflict;
let model = resolveEditorIns.getModel();
let lines = this.localData.split('\n');
let replacer;
switch (action) { switch (action) {
case 0: case 'remote':
replacer = getTextWithRange(conflict.remoteLine, conflict.splitLine); this.reSaveConflict(conflict, remote, category);
break;
case 'local':
this.reSaveConflict(conflict, local, category);
break; break;
case 1: case 'manual':
replacer = getTextWithRange(conflict.splitLine, conflict.localLine); this.lastConflict = conflict;
this.lastCategory = category;
this.$refs.codeConflictResolveDialog.show(remote ? remote.data : '', local ? local.data : '');
break; 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;
} }
},
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 { } else {
if (localReplacer === undefined) { resultData = result.obj;
replacer = remoteReplacer;
} else {
replacer = remoteReplacer + '\n' + localReplacer;
} }
} }
break; let data = this.localObj[category];
let categoryConfig = this.categories[category];
if (categoryConfig.simple) {
if (resultData) {
this.localObj[category] = resultData;
} else {
delete this.localObj[category];
} }
/*if (replacer !== undefined) { } else {
replaceText(replacer); if (conflict.local) {
//this.localData = lines.join('\n'); data.some((item, index) => {
}*/ if (item[categoryConfig.key] === conflict.local.key) {
replaceText(replacer); if (resultData) {
this.delayTrack(); data[index] = resultData;
} else {
this.conflictIndex--; data.splice(index, 1);
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 true;
} }
});
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], () => []); this.$set(conflict, 'resolved', true);
this.conflictCountingMap[category]--;
} }
},
...mapMutations([
'modifyProjectDetails',
]),
} }
} }
</script> </script>
......
<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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment