|
83 | 83 | $selected= file; |
84 | 84 | }, |
85 | 85 |
|
86 | | -add:async (stubs)=> { |
87 | | -constillegal_create=stubs.some( |
| 86 | +add:async (name,type)=> { |
| 87 | +constnew_stubs=add_stub(name, type, current_stubs); |
| 88 | +
|
| 89 | +constillegal_create=new_stubs.some( |
88 | 90 | (s)=>!editing_constraints.create.some((c)=>s.name=== c) |
89 | 91 | ); |
90 | | -
|
91 | 92 | if (illegal_create) { |
92 | 93 | modal_text= |
93 | 94 | 'Only the following files and folders are allowed to be created in this tutorial chapter:\n'+ |
94 | 95 | editing_constraints.create.join('\n'); |
95 | 96 | return; |
96 | 97 | } |
97 | 98 |
|
98 | | -current_stubs= [...current_stubs,...stubs]; |
99 | | -
|
| 99 | +current_stubs= [...current_stubs,...new_stubs]; |
100 | 100 | awaitload_files(current_stubs); |
101 | 101 |
|
102 | | -if (stubs[0].type==='file') { |
103 | | -select(stubs[0]); |
| 102 | +if (new_stubs[0].type==='file') { |
| 103 | +select(new_stubs[0]); |
104 | 104 | } |
105 | 105 | }, |
106 | 106 |
|
107 | 107 | edit:async (to_rename,new_name)=> { |
108 | | -/**@type{Array<[import('$lib/types').Stub, import('$lib/types').Stub]>}*/ |
109 | | -constchanged= []; |
110 | | -constupdated_stubs=current_stubs.map((s)=> { |
111 | | -if (!s.name.startsWith(to_rename.name)) { |
112 | | -return s; |
113 | | -} |
114 | | -
|
| 108 | +// treat edit as a remove followed by an add |
| 109 | +constout=current_stubs.filter((s)=>s.name.startsWith(to_rename.name)); |
| 110 | +constupdated_stubs=current_stubs.filter((s)=>!out.includes(s)); |
| 111 | +/**@type{Map<string, import('$lib/types').Stub>}*/ |
| 112 | +constnew_stubs=newMap(); |
| 113 | +for (constsof out) { |
115 | 114 | constname= |
116 | 115 | s.name.slice(0,to_rename.name.length-to_rename.basename.length)+ |
117 | 116 | new_name+ |
118 | 117 | s.name.slice(to_rename.name.length); |
119 | | -constbasename= s=== to_rename? new_name:s.basename; |
120 | | -constnew_stub= {...s, name, basename }; |
121 | | -
|
122 | | -changed.push([s, new_stub]); |
123 | | -return new_stub; |
124 | | -}); |
| 118 | +// deduplicate |
| 119 | +for (constto_addofadd_stub(name,s.type, updated_stubs)) { |
| 120 | +new_stubs.set(to_add.name, to_add); |
| 121 | +} |
| 122 | +} |
125 | 123 |
|
126 | 124 | constillegal_rename= |
127 | 125 | !editing_constraints.remove.some((r)=>to_rename.name=== r)|| |
128 | | -changed.some(([,s])=>!editing_constraints.create.some((c)=>s.name=== c)); |
| 126 | +[...new_stubs.keys()].some((name)=>!editing_constraints.create.some((c)=> name=== c)); |
129 | 127 | if (illegal_rename) { |
130 | 128 | modal_text= |
131 | 129 | 'Only the following files and folders are allowed to be renamed in this tutorial chapter:\n'+ |
|
135 | 133 | return; |
136 | 134 | } |
137 | 135 |
|
138 | | -current_stubs= updated_stubs; |
| 136 | +current_stubs=updated_stubs.concat(...new_stubs.values()); |
139 | 137 | awaitload_files(current_stubs); |
140 | 138 |
|
141 | | -if (to_rename.type==='file') { |
142 | | -select(/**@type{any}*/ (changed.find(([old_s])=>old_s===to_rename))[1]); |
| 139 | +if (to_rename.type==='file'&& $selected?.name===to_rename.name) { |
| 140 | +select(/**@type{any}*/ ([...new_stubs.values()].find((s)=>s.type==='file'))); |
143 | 141 | } |
144 | 142 | }, |
145 | 143 |
|
|
165 | 163 | selected |
166 | 164 | }); |
167 | 165 |
|
| 166 | +/** |
| 167 | + *@param{string}name |
| 168 | + *@param{'file' | 'directory'}type |
| 169 | + *@param{import('$lib/types').Stub[]}current |
| 170 | +*/ |
| 171 | +functionadd_stub(name,type,current) { |
| 172 | +// find directory which contains the new file |
| 173 | +/**@type{import('$lib/types').DirectoryStub}*/ |
| 174 | +let dir=/**@type{any} we know it will be assigned after the loop*/ (null); |
| 175 | +for (conststubof current) { |
| 176 | +if ( |
| 177 | +stub.type==='directory'&& |
| 178 | +name.startsWith(stub.name)&& |
| 179 | +(!dir||dir.name.length<stub.name.length) |
| 180 | +) { |
| 181 | +dir= stub; |
| 182 | +} |
| 183 | +} |
| 184 | +
|
| 185 | +constnew_name=name.slice(dir.name.length+1); |
| 186 | +constprefix=dir.name+'/'; |
| 187 | +constdepth=prefix.split('/').length-2; |
| 188 | +constparts=new_name.split('/'); |
| 189 | +/**@type{import('$lib/types').Stub[]}*/ |
| 190 | +conststubs= []; |
| 191 | +
|
| 192 | +for (let i=1; i<=parts.length; i++) { |
| 193 | +constpart=parts.slice(0, i).join('/'); |
| 194 | +constbasename=/**@type{string}*/ (part.split('/').pop()); |
| 195 | +constname= prefix+ part; |
| 196 | +if (!current.some((s)=>s.name=== name)) { |
| 197 | +if (i<parts.length|| type==='directory') { |
| 198 | +stubs.push({ type:'directory', name, depth: depth+ i, basename }); |
| 199 | +}elseif (i===parts.length&& type==='file') { |
| 200 | +stubs.push({ |
| 201 | +type:'file', |
| 202 | +name, |
| 203 | +depth: depth+ i, |
| 204 | +basename, |
| 205 | +text:true, |
| 206 | +contents:'' |
| 207 | +}); |
| 208 | +} |
| 209 | +} |
| 210 | +} |
| 211 | +
|
| 212 | +return stubs; |
| 213 | +} |
| 214 | +
|
168 | 215 | /**@type{import('$lib/types').Adapter | undefined}*/ |
169 | 216 | let adapter; |
170 | 217 |
|
|