Skip to content

Commit 52dadc8

Browse files
authored
Merge pull request #255 from php-school/nested-file-restore
2 parents fb975e3 + 6443df8 commit 52dadc8

File tree

3 files changed

+115
-34
lines changed

3 files changed

+115
-34
lines changed

assets/components/AceEditor.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import ace from 'ace-builds';
44
import 'ace-builds/src-noconflict/theme-monokai';
55
import 'ace-builds/src-noconflict/mode-php';
6-
import { markRaw} from "vue";
6+
import {markRaw} from "vue";
77
88
export default {
99
props: {
@@ -38,10 +38,16 @@ export default {
3838
this._editor.session.on('change', this.change);
3939
}
4040
},
41+
watch: {
42+
'file.content'(newValue, oldValue) {
43+
if (newValue !== oldValue) {
44+
this._editor.setValue(newValue, 1);
45+
}
46+
}
47+
},
4148
methods: {
4249
change() {
43-
const content = this._editor.session.getValue();
44-
this.file.content = content;
50+
this.file.content = this._editor.session.getValue();
4551
this.$emit('changeContent', this.file);
4652
}
4753
},

assets/components/ExerciseEditor.vue

Lines changed: 99 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Tour from "./Tour.vue";
2323
import Confirm from "./Confirm.vue";
2424
import HeaderNav from "./HeaderNav.vue";
2525
import StudentDropdown from "./StudentDropdown.vue";
26+
import toFilePath from "./utils/toFilePath";
2627
2728
export default {
2829
components: {
@@ -60,30 +61,13 @@ export default {
6061
links: Object
6162
},
6263
mounted() {
63-
const items = { ...localStorage };
64-
const key = this.currentExercise.workshop.code + '.' + this.currentExercise.exercise.slug;
65-
for (const localStorageKey in items) {
66-
if (localStorageKey.startsWith(key)) {
67-
const fileContent = items[localStorageKey]; // Content of what is in file
68-
const fileName = localStorageKey.substring(key.length + 1);
69-
let foundFile = false;
70-
for (const file of this.studentFiles) {
71-
if (file.name === fileName) {
72-
file.content = fileContent;
73-
foundFile = true;
74-
break;
75-
}
76-
}
64+
const files = this.getSavedFiles();
65+
for (const fileName in files) {
66+
const fileContent = files[fileName];
67+
const folderParts = fileName.split("/");
7768
78-
if (!foundFile) {
79-
const file = {
80-
name: fileName,
81-
parent: null,
82-
content: fileContent,
83-
};
84-
this.studentFiles.push(file);
85-
}
86-
}
69+
this.createFileInFolderStructure(this.studentFiles, folderParts, fileContent);
70+
this.studentFiles = this.toTree(this.studentFiles);
8771
}
8872
},
8973
data() {
@@ -93,7 +77,10 @@ export default {
9377
return a.name === this.entryPoint ? -1 : 0;
9478
});
9579
96-
const studentFiles = this.toTree(this.initialFiles);
80+
const initialFileCopy = this.initialFiles.map(file => {
81+
return {...file}
82+
});
83+
const studentFiles = this.toTree(initialFileCopy);
9784
9885
return {
9986
firstRunLoaded: false,
@@ -129,9 +116,91 @@ export default {
129116
},
130117
},
131118
methods: {
119+
getSavedFiles() {
120+
const items = { ...localStorage };
121+
const key = this.currentExercise.workshop.code + '.' + this.currentExercise.exercise.slug;
122+
123+
const files = {};
124+
for (const localStorageKey in items) {
125+
if (localStorageKey.startsWith(key)) {
126+
files[localStorageKey.substring(key.length + 1)] = items[localStorageKey];
127+
}
128+
}
129+
return files;
130+
},
131+
async resetFiles() {
132+
const confirm = this.$refs.confirm;
133+
134+
const ok = await confirm.show({
135+
title: "Resetting...",
136+
message: "File tree will be completely reset. All of your code will be deleted. Are you sure you want to continue?",
137+
okMessage: "Confirm",
138+
});
139+
140+
if (!ok) {
141+
return;
142+
}
143+
144+
const key = this.currentExercise.workshop.code + '.' + this.currentExercise.exercise.slug;
145+
const files = this.getSavedFiles();
146+
for (const fileName in files) {
147+
localStorage.removeItem(key + '.' + fileName);
148+
}
149+
150+
const initialFileCopy = this.initialFiles.map(file => {
151+
return {...file}
152+
});
153+
154+
this.studentFiles = this.toTree(initialFileCopy);
155+
this.openFiles = [this.studentFiles[0]];
156+
this.activeTab = 0;
157+
},
158+
createFileInFolderStructure(rootFolder, parts, fileContent) {
159+
if (parts.length === 1) {
160+
const file = rootFolder.find(child => child.name === parts[0]);
161+
if (file) {
162+
file.content = fileContent;
163+
return;
164+
}
165+
166+
rootFolder.push({ name: parts[0], content: fileContent });
167+
return;
168+
}
169+
170+
const directories = parts;
171+
const fileName = directories.pop();
172+
173+
let currentDirectory = rootFolder.find(directory => directory.name === directories[0]);
174+
175+
if (!currentDirectory) {
176+
currentDirectory = { name: directories.shift(), children: [] };
177+
rootFolder.push(currentDirectory);
178+
}
179+
180+
currentDirectory = directories.reduce((parent, name) => {
181+
return this.findOrCreateDirectory(parent, name);
182+
}, currentDirectory);
183+
184+
const file = { name: fileName, content: fileContent };
185+
currentDirectory.children.push(file);
186+
},
187+
findOrCreateDirectory(directory, name) {
188+
let subdirectory = directory.children.find(child => child.name === name);
189+
190+
if (!subdirectory) {
191+
subdirectory = { name, children: [] };
192+
directory.children.push(subdirectory);
193+
}
194+
195+
return subdirectory;
196+
},
132197
saveSolution(file) {
133-
localStorage.setItem(this.currentExercise.workshop.code + '.' + this.currentExercise.exercise.slug + '.' + file.name, file.content);
134-
console.log(file);
198+
const filePath = toFilePath(file);
199+
200+
localStorage.setItem(
201+
this.currentExercise.workshop.code + '.' + this.currentExercise.exercise.slug + '.' + filePath,
202+
file.content
203+
);
135204
},
136205
resetState() {
137206
const currentExercise = this.currentExercise;
@@ -186,7 +255,7 @@ export default {
186255
this.openPassNotification = false;
187256
},
188257
deleteFileOrFolder(file) {
189-
const confirm = this.$refs.deleteFileConfirm;
258+
const confirm = this.$refs.confirm;
190259
const openFiles = this.openFiles;
191260
const findAndActivateNearestTab = this.findAndActivateNearestTab;
192261
const entryPoint = this.entryPoint;
@@ -348,7 +417,7 @@ export default {
348417
:first-run-loaded="firstRunLoaded"
349418
:first-verify-loaded="firstVerifyLoaded" :problem-modal-open="openProblemModal"></tour>
350419

351-
<confirm ref="deleteFileConfirm"></confirm>
420+
<confirm ref="confirm"></confirm>
352421

353422
<pass-notification
354423
v-if="openPassNotification"
@@ -365,7 +434,8 @@ export default {
365434
:file-select-function="studentSelectFile"
366435
:initial-selected-item="studentFiles[0]"
367436
:delete-function="deleteFileOrFolder"
368-
show-controls/>
437+
show-controls
438+
@reset="resetFiles"/>
369439
</div>
370440
<div class="flex border-l border-solid border-gray-600 p-4 h-full"
371441
:class="[openResults ? 'w-3/5' : 'w-4/5']">

assets/components/FileTree.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
33
import { computed } from 'vue'
44
import TreeItem from "./TreeItem.vue";
5-
import { FolderPlusIcon, PlusIcon } from '@heroicons/vue/24/outline'
5+
import { FolderPlusIcon, PlusIcon, XMarkIcon } from '@heroicons/vue/24/outline'
66
import uniqueName from "./utils/uniqueName.js";
77
88
export default {
99
components: {
1010
TreeItem,
1111
FolderPlusIcon,
12-
PlusIcon
12+
PlusIcon,
13+
XMarkIcon
1314
},
1415
props: {
1516
deleteFunction: Function,
@@ -37,6 +38,9 @@ export default {
3738
}
3839
},
3940
methods: {
41+
reset() {
42+
this.$emit('reset');
43+
},
4044
addFile() {
4145
if (this.files.filter(file => 'new' in file).length) {
4246
return;
@@ -71,6 +75,7 @@ export default {
7175
<div class="border-b border-solid border-gray-600 p-3 flex justify-between">
7276
<span class="text-white text-base font-mono">Files</span>
7377
<div v-if="showControls" class="flex text-white">
78+
<XMarkIcon @click="reset" class="mr-2 h-5 w-5 cursor-pointer hover:text-pink-500" style="fill: none !important;"/>
7479
<FolderPlusIcon @click="addFolder" class="mr-2 h-5 w-5 cursor-pointer hover:text-pink-500" style="fill: none !important;"/>
7580
<PlusIcon @click="addFile" class="mr-2 h-5 w-5 cursor-pointer hover:text-pink-500" style="fill: none !important;"/>
7681
</div>

0 commit comments

Comments
 (0)