Commit 40ea8633 authored by Phil Hughes's avatar Phil Hughes

moved temp file & folder creation into a web worker

it wasn't previously affecting the performance in any way, however this keeps all the logic of creating folders & files in the worker which we can just post to whenever we need to

[ci skip]
parent 45b9d60b
...@@ -33,6 +33,7 @@ export default { ...@@ -33,6 +33,7 @@ export default {
v-for="file in tree.tree" v-for="file in tree.tree"
:key="file.key" :key="file.key"
:file="file" :file="file"
:level="0"
/> />
</div> </div>
</template> </template>
<script> <script>
/* global monaco */ /* global monaco */
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import flash from '~/flash'; import flash from '~/flash';
import monacoLoader from '../monaco_loader'; import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor'; import Editor from '../lib/editor';
......
<script> <script>
import { mapActions } from 'vuex';
import timeAgoMixin from '~/vue_shared/mixins/timeago'; import timeAgoMixin from '~/vue_shared/mixins/timeago';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import fileIcon from '~/vue_shared/components/file_icon.vue'; import fileIcon from '~/vue_shared/components/file_icon.vue';
...@@ -23,6 +24,10 @@ ...@@ -23,6 +24,10 @@
type: Object, type: Object,
required: true, required: true,
}, },
level: {
type: Number,
required: true,
},
}, },
computed: { computed: {
isTree() { isTree() {
...@@ -33,14 +38,14 @@ ...@@ -33,14 +38,14 @@
}, },
levelIndentation() { levelIndentation() {
return { return {
marginLeft: `${this.file.level * 16}px`, marginLeft: `${this.level * 16}px`,
}; };
}, },
fileClass() { fileClass() {
return { return {
'file-open': this.isBlob && this.file.opened, 'file-open': this.isBlob && this.file.opened,
'file-active': this.isBlob && this.file.active, 'file-active': this.isBlob && this.file.active,
'folder': this.isTree, folder: this.isTree,
}; };
}, },
}, },
...@@ -50,15 +55,15 @@ ...@@ -50,15 +55,15 @@
} }
}, },
methods: { methods: {
clickFile(row) { ...mapActions([
'toggleTreeOpen',
]),
clickFile() {
// Manual Action if a tree is selected/opened // Manual Action if a tree is selected/opened
if (this.file.type === 'tree' && this.$router.currentRoute.path === `/project${row.url}`) { if (this.isTree && this.$router.currentRoute.path === `/project${this.file.url}`) {
this.$store.dispatch('toggleTreeOpen', { this.toggleTreeOpen(this.file.path);
endpoint: this.file.url,
tree: this.file,
});
} }
this.$router.push(`/project${row.url}`); this.$router.push(`/project${this.file.url}`);
}, },
}, },
}; };
...@@ -72,7 +77,7 @@ ...@@ -72,7 +77,7 @@
> >
<div <div
class="file-name" class="file-name"
@click="clickFile(file)" @click="clickFile"
role="button" role="button"
> >
<span <span
...@@ -110,6 +115,7 @@ ...@@ -110,6 +115,7 @@
v-for="childFile in file.tree" v-for="childFile in file.tree"
:key="childFile.key" :key="childFile.key"
:file="childFile" :file="childFile"
:level="level + 1"
/> />
</template> </template>
</div> </div>
......
...@@ -44,7 +44,6 @@ export const createTempEntry = ( ...@@ -44,7 +44,6 @@ export const createTempEntry = (
dispatch('createTempTree', { dispatch('createTempTree', {
projectId, projectId,
branchId, branchId,
parent: selectedParent,
name, name,
}); });
} else if (type === 'blob') { } else if (type === 'blob') {
......
...@@ -4,10 +4,9 @@ import service from '../../services'; ...@@ -4,10 +4,9 @@ import service from '../../services';
import * as types from '../mutation_types'; import * as types from '../mutation_types';
import router from '../../ide_router'; import router from '../../ide_router';
import { import {
findEntry,
setPageTitle, setPageTitle,
createTemp,
} from '../utils'; } from '../utils';
import FilesDecoratorWorker from '../workers/files_decorator_worker';
export const closeFile = ({ commit, state, getters, dispatch }, path) => { export const closeFile = ({ commit, state, getters, dispatch }, path) => {
const indexOfClosedFile = state.openFiles.indexOf(path); const indexOfClosedFile = state.openFiles.indexOf(path);
...@@ -106,37 +105,44 @@ export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn ...@@ -106,37 +105,44 @@ export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn
} }
}; };
export const createTempFile = ({ state, commit, dispatch }, { projectId, branchId, parent, name, content = '', base64 = '' }) => { export const createTempFile = ({ state, commit, dispatch }, { projectId, branchId, parent, name, content = '', base64 = '' }) =>
const path = parent.path !== undefined ? parent.path : ''; new Promise((resolve) => {
// We need to do the replacement otherwise the web_url + file.url duplicate const worker = new FilesDecoratorWorker();
const newUrl = `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${name}`; const path = parent.path !== undefined ? `${parent.path}/` : '';
const file = createTemp({ const entryPath = path ? `${path}/${name}` : name;
projectId,
branchId,
name: name.replace(`${path}/`, ''),
path,
type: 'blob',
level: parent.level !== undefined ? parent.level + 1 : 0,
changed: true,
content,
base64,
url: newUrl,
});
if (findEntry(parent.tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`, 'alert', document, null, false, true); if (state.entries[entryPath]) {
return flash(`The name "${name}" is already taken in this directory.`, 'alert', document, null, false, true);
}
worker.addEventListener('message', ({ data }) => {
const { file } = data;
worker.terminate();
commit(types.CREATE_TMP_FILE, { commit(types.CREATE_TMP_FILE, {
parent, data,
file, projectId,
branchId,
}); });
commit(types.TOGGLE_FILE_OPEN, file.path); commit(types.TOGGLE_FILE_OPEN, file.path);
commit(types.ADD_FILE_TO_CHANGED, file.path); commit(types.ADD_FILE_TO_CHANGED, file.path);
dispatch('setFileActive', file.path); dispatch('setFileActive', file.path);
router.push(`/project${file.url}`); resolve();
});
return Promise.resolve(file); worker.postMessage({
}; data: [entryPath],
projectId,
branchId,
tempFile: true,
content,
base64,
});
return null;
});
export const discardFileChanges = ({ state, commit }, path) => { export const discardFileChanges = ({ state, commit }, path) => {
const file = state.entries[path]; const file = state.entries[path];
......
...@@ -3,10 +3,8 @@ import { normalizeHeaders } from '~/lib/utils/common_utils'; ...@@ -3,10 +3,8 @@ import { normalizeHeaders } from '~/lib/utils/common_utils';
import flash from '~/flash'; import flash from '~/flash';
import service from '../../services'; import service from '../../services';
import * as types from '../mutation_types'; import * as types from '../mutation_types';
import router from '../../ide_router';
import { import {
findEntry, findEntry,
createTemp,
} from '../utils'; } from '../utils';
import FilesDecoratorWorker from '../workers/files_decorator_worker'; import FilesDecoratorWorker from '../workers/files_decorator_worker';
...@@ -33,41 +31,30 @@ export const handleTreeEntryAction = ({ commit, dispatch }, row) => { ...@@ -33,41 +31,30 @@ export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
export const createTempTree = ( export const createTempTree = (
{ state, commit, dispatch }, { state, commit, dispatch },
{ projectId, branchId, parent, name }, { projectId, branchId, name },
) => { ) => new Promise((resolve) => {
let selectedTree = parent; const dirName = name.replace(new RegExp(`^${state.path}/`), '');
const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/'); const worker = new FilesDecoratorWorker();
dirNames.forEach((dirName) => { worker.addEventListener('message', ({ data }) => {
const foundEntry = findEntry(selectedTree.tree, 'tree', dirName); worker.terminate();
if (!foundEntry) { commit(types.CREATE_TMP_TREE, {
const path = selectedTree.path !== undefined ? selectedTree.path : ''; data,
const tmpEntry = createTemp({
projectId, projectId,
branchId, branchId,
name: dirName,
path,
type: 'tree',
level: selectedTree.level !== undefined ? selectedTree.level + 1 : 0,
tree: [],
url: `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${dirName}`,
}); });
commit(types.CREATE_TMP_TREE, { resolve();
parent: selectedTree,
tmpEntry,
}); });
commit(types.TOGGLE_TREE_OPEN, tmpEntry);
router.push(`/project${tmpEntry.url}`);
selectedTree = tmpEntry; worker.postMessage({
} else { data: [`${dirName}/`],
selectedTree = foundEntry; projectId,
} branchId,
tempFile: true,
}); });
}; });
export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => { export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return; if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return;
......
export const activeFile = state => state.openFiles.find(file => file.active) || null; export const activeFile = state => state.openFiles.find(file => file.active) || null;
export const canEditFile = (state) => {
const currentActiveFile = activeFile(state);
return state.canCommit &&
(currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
};
export const addedFiles = state => state.changedFiles.filter(f => f.tempFile); export const addedFiles = state => state.changedFiles.filter(f => f.tempFile);
export const modifiedFiles = state => state.changedFiles.filter(f => !f.tempFile); export const modifiedFiles = state => state.changedFiles.filter(f => !f.tempFile);
......
...@@ -65,8 +65,33 @@ export default { ...@@ -65,8 +65,33 @@ export default {
changed: false, changed: false,
}); });
}, },
[types.CREATE_TMP_FILE](state, { file, parent }) { [types.CREATE_TMP_FILE](state, { data, projectId, branchId }) {
parent.tree.push(file); Object.keys(data.entries).forEach((key) => {
const entry = data.entries[key];
Object.assign(state.entries, {
[key]: entry,
});
});
Object.assign(state.trees[`${projectId}/${branchId}`], {
tree: state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList),
});
// Object.assign(state.entries, {
// [file.path]: file,
// });
// if (parent.path) {
// // Add it as a child of the parent
// Object.assign(state.entries[parent.path], {
// tree: parent.tree.concat(file),
// });
// } else {
// // Add it the root
// Object.assign(parent, {
// tree: parent.tree.concat(file),
// });
// }
}, },
[types.ADD_FILE_TO_CHANGED](state, path) { [types.ADD_FILE_TO_CHANGED](state, path) {
Object.assign(state, { Object.assign(state, {
......
...@@ -30,8 +30,18 @@ export default { ...@@ -30,8 +30,18 @@ export default {
lastCommitPath: url, lastCommitPath: url,
}); });
}, },
[types.CREATE_TMP_TREE](state, { parent, tmpEntry }) { [types.CREATE_TMP_TREE](state, { data, projectId, branchId }) {
parent.tree.push(tmpEntry); Object.keys(data.entries).forEach((key) => {
const entry = data.entries[key];
Object.assign(state.entries, {
[key]: entry,
});
});
Object.assign(state.trees[`${projectId}/${branchId}`], {
tree: state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList),
});
}, },
[types.REMOVE_ALL_CHANGES_FILES](state) { [types.REMOVE_ALL_CHANGES_FILES](state) {
Object.assign(state, { Object.assign(state, {
......
export default () => ({ export default () => ({
canCommit: false,
currentProjectId: '', currentProjectId: '',
currentBranchId: '', currentBranchId: '',
changedFiles: [], changedFiles: [],
endpoints: {}, endpoints: {},
isInitialRoot: false,
lastCommitMsg: '', lastCommitMsg: '',
lastCommitPath: '', lastCommitPath: '',
loading: false, loading: false,
onTopOfBranch: false,
openFiles: [], openFiles: [],
selectedFile: null,
path: '',
parentTreeUrl: '', parentTreeUrl: '',
trees: {}, trees: {},
projects: {}, projects: {},
......
import _ from 'underscore';
export const dataStructure = () => ({ export const dataStructure = () => ({
id: '', id: '',
key: '', key: '',
...@@ -9,7 +7,6 @@ export const dataStructure = () => ({ ...@@ -9,7 +7,6 @@ export const dataStructure = () => ({
name: '', name: '',
url: '', url: '',
path: '', path: '',
level: 0,
tempFile: false, tempFile: false,
tree: [], tree: [],
loading: false, loading: false,
...@@ -24,7 +21,6 @@ export const dataStructure = () => ({ ...@@ -24,7 +21,6 @@ export const dataStructure = () => ({
updatedAt: '', updatedAt: '',
author: '', author: '',
}, },
tree_url: '',
blamePath: '', blamePath: '',
commitsPath: '', commitsPath: '',
permalink: '', permalink: '',
...@@ -50,7 +46,6 @@ export const decorateData = (entity) => { ...@@ -50,7 +46,6 @@ export const decorateData = (entity) => {
type, type,
url, url,
name, name,
tree_url,
path, path,
renderError, renderError,
content = '', content = '',
...@@ -59,7 +54,6 @@ export const decorateData = (entity) => { ...@@ -59,7 +54,6 @@ export const decorateData = (entity) => {
opened = false, opened = false,
changed = false, changed = false,
parentTreeUrl = '', parentTreeUrl = '',
level = 0,
base64 = false, base64 = false,
file_lock, file_lock,
...@@ -75,9 +69,7 @@ export const decorateData = (entity) => { ...@@ -75,9 +69,7 @@ export const decorateData = (entity) => {
type, type,
name, name,
url, url,
tree_url,
path, path,
level,
tempFile, tempFile,
opened, opened,
active, active,
...@@ -92,32 +84,6 @@ export const decorateData = (entity) => { ...@@ -92,32 +84,6 @@ export const decorateData = (entity) => {
}; };
}; };
/*
Takes the multi-dimensional tree and returns a flattened array.
This allows for the table to recursively render the table rows but keeps the data
structure nested to make it easier to add new files/directories.
*/
export const treeList = (state, treeId) => {
const baseTree = state.trees[treeId];
if (baseTree) {
const mapTree = arr => (!arr.tree || !arr.tree.length ?
[] : _.map(arr.tree, a => [a, mapTree(a)]));
return _.chain(baseTree.tree)
.map(arr => [arr, mapTree(arr)])
.flatten()
.value();
}
return [];
};
export const getTree = state => (namespace, projectId, branch) => state.trees[`${namespace}/${projectId}/${branch}`];
export const getTreeEntry = (store, treeId, path) => {
const fileList = treeList(store.state, treeId);
return fileList ? fileList.find(file => file.path === path) : null;
};
export const findEntry = (tree, type, name, prop = 'name') => tree.find( export const findEntry = (tree, type, name, prop = 'name') => tree.find(
f => f.type === type && f[prop] === name, f => f.type === type && f[prop] === name,
); );
...@@ -151,39 +117,6 @@ export const createTemp = ({ ...@@ -151,39 +117,6 @@ export const createTemp = ({
}); });
}; };
export const createOrMergeEntry = ({ projectId,
branchId,
entry,
type,
parentTreeUrl,
level,
state }) => {
if (state.changedFiles.length) {
const foundChangedFile = findEntry(state.changedFiles, type, entry.path, 'path');
if (foundChangedFile) {
return foundChangedFile;
}
}
if (state.openFiles.length) {
const foundOpenFile = findEntry(state.openFiles, type, entry.path, 'path');
if (foundOpenFile) {
return foundOpenFile;
}
}
return decorateData({
...entry,
projectId,
branchId,
type,
parentTreeUrl,
level,
});
};
export const createCommitPayload = (branch, newBranch, state, rootState) => ({ export const createCommitPayload = (branch, newBranch, state, rootState) => ({
branch, branch,
commit_message: state.commitMessage, commit_message: state.commitMessage,
...@@ -210,11 +143,6 @@ const sortTreesByTypeAndName = (a, b) => { ...@@ -210,11 +143,6 @@ const sortTreesByTypeAndName = (a, b) => {
return 0; return 0;
}; };
export const sortTree = (sortedTree) => { export const sortTree = sortedTree => sortedTree.map(entity => Object.assign(entity, {
sortedTree.forEach((el) => { tree: entity.tree.length ? sortTree(entity.tree) : [],
Object.assign(el, { })).sort(sortTreesByTypeAndName);
tree: el && el.tree ? sortTree(el.tree) : [],
});
});
return sortedTree.sort(sortTreesByTypeAndName);
};
...@@ -4,15 +4,16 @@ import { ...@@ -4,15 +4,16 @@ import {
} from '../utils'; } from '../utils';
self.addEventListener('message', (e) => { self.addEventListener('message', (e) => {
const { data, projectId, branchId } = e.data; const { data, projectId, branchId, tempFile = false } = e.data;
const treeList = []; const treeList = [];
let file;
const entries = data.reduce((acc, path) => { const entries = data.reduce((acc, path) => {
const pathSplit = path.split('/'); const pathSplit = path.split('/');
const blobName = pathSplit.pop(); const blobName = pathSplit.pop().trim();
if (pathSplit.length > 0) { if (pathSplit.length > 0) {
pathSplit.reduce((pathAcc, folderName, folderLevel) => { pathSplit.reduce((pathAcc, folderName) => {
const parentFolder = acc[pathAcc[pathAcc.length - 1]]; const parentFolder = acc[pathAcc[pathAcc.length - 1]];
const folderPath = `${(parentFolder ? `${parentFolder.path}/` : '')}${folderName}`; const folderPath = `${(parentFolder ? `${parentFolder.path}/` : '')}${folderName}`;
const foundEntry = acc[folderPath]; const foundEntry = acc[folderPath];
...@@ -25,9 +26,11 @@ self.addEventListener('message', (e) => { ...@@ -25,9 +26,11 @@ self.addEventListener('message', (e) => {
name: folderName, name: folderName,
path: folderPath, path: folderPath,
url: `/${projectId}/tree/${branchId}/${folderPath}`, url: `/${projectId}/tree/${branchId}/${folderPath}`,
level: parentFolder ? parentFolder.level + 1 : folderLevel,
type: 'tree', type: 'tree',
parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`, parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`,
tempFile,
changed: tempFile,
opened: tempFile,
}); });
Object.assign(acc, { Object.assign(acc, {
...@@ -49,17 +52,19 @@ self.addEventListener('message', (e) => { ...@@ -49,17 +52,19 @@ self.addEventListener('message', (e) => {
}, []); }, []);
} }
if (blobName !== '') {
const fileFolder = acc[pathSplit.join('/')]; const fileFolder = acc[pathSplit.join('/')];
const file = decorateData({ file = decorateData({
projectId, projectId,
branchId, branchId,
id: path, id: path,
name: blobName, name: blobName,
path, path,
url: `/${projectId}/blob/${branchId}/${path}`, url: `/${projectId}/blob/${branchId}/${path}`,
level: fileFolder ? fileFolder.level + 1 : 0,
type: 'blob', type: 'blob',
parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`, parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`,
tempFile,
changed: tempFile,
}); });
Object.assign(acc, { Object.assign(acc, {
...@@ -71,6 +76,7 @@ self.addEventListener('message', (e) => { ...@@ -71,6 +76,7 @@ self.addEventListener('message', (e) => {
} else { } else {
treeList.push(file); treeList.push(file);
} }
}
return acc; return acc;
}, {}); }, {});
...@@ -78,5 +84,6 @@ self.addEventListener('message', (e) => { ...@@ -78,5 +84,6 @@ self.addEventListener('message', (e) => {
self.postMessage({ self.postMessage({
entries, entries,
treeList: sortTree(treeList), treeList: sortTree(treeList),
file,
}); });
}); });
...@@ -26,38 +26,6 @@ describe('Multi-file store getters', () => { ...@@ -26,38 +26,6 @@ describe('Multi-file store getters', () => {
}); });
}); });
describe('canEditFile', () => {
beforeEach(() => {
localState.onTopOfBranch = true;
localState.canCommit = true;
localState.openFiles.push(file());
localState.openFiles[0].active = true;
});
it('returns true if user can commit and has open files', () => {
expect(getters.canEditFile(localState)).toBeTruthy();
});
it('returns false if user can commit and has no open files', () => {
localState.openFiles = [];
expect(getters.canEditFile(localState)).toBeFalsy();
});
it('returns false if user can commit and active file is binary', () => {
localState.openFiles[0].binary = true;
expect(getters.canEditFile(localState)).toBeFalsy();
});
it('returns false if user cant commit', () => {
localState.canCommit = false;
expect(getters.canEditFile(localState)).toBeFalsy();
});
});
describe('modifiedFiles', () => { describe('modifiedFiles', () => {
it('returns a list of modified files', () => { it('returns a list of modified files', () => {
localState.openFiles.push(file()); localState.openFiles.push(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