1export default { 2 template: '#modules', 3 props: ['version','profile'], // version required for integrity checks, maybe kill? 4 data() { 5 return { 6 span: 21, 7 view: 'create', 8 mode: 'CREATE', 9 module: '', 10 proj: { 11 name: '', 12 start: '', 13 proj_type: '', 14 state: '', 15 field_office: '', 16 county: '', 17 description: '' 18 }, 19 errors: [false,false,false,false,false,false], 20 projs: [], 21 qry_state: '', 22 qry_field: '', 23 filter: '', 24 visCols: [ 'desc', 'proj_type', 'field_office', 'created', 'status', 'actions' ], 25 columns: [ 26 { name: 'desc', label: 'Name', field: row => row.name, align: 'left' }, 27 { name: 'proj_type', label: 'Project Type', field: 'proj_type', align: 'left', sortable: true }, 28 { name: 'state', label: 'State', field: 'state', align: 'left', sortable: true }, 29 { name: 'county', label: 'County', field: 'county', align: 'left' }, 30 { name: 'field_office', label: 'Field Office', field: 'field_office', align: 'left' }, 31 { name: 'created', label: 'Create Date', field: 'created', align: 'left', sortable: true }, 32 { name: 'last_access', label: 'Last Access Date', field: 'last_access', align: 'left', sortable: true }, 33 { name: 'status', label: 'Status', field: 'status', align: 'center', sortable: true }, 34 { name: 'actions', label: 'Action Options', align: 'right' } 35 ], 36 page: { rowsPerPage: 12 }, 37 admin: false, 38 selected: {}, 39 share_email: '', 40 access_level: null, 41 status: null, 42 } 43 }, 44 created() { 45 //this.profile = emit.getProp('profile'); 46 this.isLoaded = emit.getProp('loaded'); 47 this.modules = emit.qry({ typ: 'find', coll: 'modules', q: { name: { '$ne' : null }} }); // Open all, but only enable or check for user mods 48 this.myModules = this.modules.filter(mod => { 49 // if (this.profile.modules.indexOf(mod.name) != -1) { return mod } 50 if (mod.name == 'fluids') { return mod } 51 }); 52 this.module = this.myModules[0]; 53 this.proj_types = []; 54 this.states = (this.profile.acct_type == 'internal' && this.profile.title != 'Air Resource Specialist') ? [this.profile.state] : 55 ['Alaska','Arizona','California','Colorado','Eastern States','Idaho','Montana / Dakotas','Oregon / Washington','Nevada','New Mexico','Utah','Wyoming']; 56 this.fields = []; 57 this.counties = []; 58 59 this.dateOpts = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; 60 this.statusOpts = [{label: 'Active', value: 'active'},{label: 'Reject', value: 'rejected'},{label: 'Accept', value: 'accepted'}]; 61 this.shared = false; 62 }, 63 computed: {
64 isGuest() { return firebase.auth().currentUser.email == '[email protected]' }, 65 internal() { return this.profile.acct_type == 'internal' }, 66 isARS() { return this.profile.acct_type == 'internal' && this.profile.title == 'Air Resource Specialist'}, 67 owner() { return firebase.auth().currentUser.uid == this.selected.owner }, 68 canShare() { return (this.owner || this.selected.shared[firebase.auth().currentUser.uid] && this.selected.shared[firebase.auth().currentUser.uid].access_level == 'Full-Write') ? true : false }, 69 defaultProj() { return this.selected.owner == 'default' }, 70 versionMatch() { return this.version == this.selected.model_version } 71 }, 72 mounted() { 73 74 }, 75 methods: { 76 getProjs() { return emit.getProp('projects') }, 77 createProj() { 78 if (this.isGuest) { 79 this.$q.notify({ message: `No Guest access to ${this.mode.toUpperCase()} function.`, color: 'red', icon: 'error', textColor: 'grey-1', 80 actions: [ { label: 'Dismiss', color: 'white', handler: () => { /* ... */ } } ] 81 }); return; 82 } 83 84 if (this.module.name != 'fluids') { // PLACEHOLDER SCRIPT FOR FLUIDS ONLY SUPPORT 85 this.$q.notify({ color: 'red', icon: 'error', textColor: 'grey-1', message: 'TEMP ERROR: Only FLUIDS module available'}); 86 return; 87 } 88 89 let flds = ['name','start','proj_type','state','field_office','county']; 90 for (let indx = 0; indx < flds.length; indx++) { 91 if (this.proj[flds[indx]] == '' || this.proj[flds[indx]] == null) { 92 this.errors.splice(indx, 1, true); 93 } else { this.errors.splice(indx, 1, false) } 94 } 95 96 if (this.errors.indexOf(true) == -1) { 97 let proj = Object.assign({}, this.proj); 98 proj['module'] = this.module.name; 99 proj['agency_review'] = false; 100 proj['created'] = new Date(); 101 proj['last_access'] = new Date(); 102 proj['last_user'] = ''; 103 proj['access_cnt'] = 0; 104 proj['model_version'] = (this.mode == 'CREATE') ? this.version : ''; 105 proj['owner'] = firebase.auth().currentUser.uid; 106 proj['status'] = 'Active'; 107 proj['shared'] = []; 108 109 this.$eventHub.$emit('loading', true); 110 if (this.mode == 'CREATE') { 111 emit.fb({ op: 'create_proj', proj: proj, callback: this.callback }); 112 } else { // COPY 113 emit.fb({ op: 'copy_proj', proj: proj, proj_id: this.selected._id, callback: this.callback }); 114 } 115 } 116 }, 117 cType(status) { 118 switch (status) { 119 case 'Active': return 'green'; break; 120 case 'Submitted': return 'purple'; break; 121 case 'Rejected': return 'red'; break; 122 case 'Accepted': return 'light-blue'; break; 123 case 'Final': return 'blue-grey'; break; 124 } 125 }, 126 iType(status) { 127 switch (status) { 128 case 'Active': return 'cached'; break; 129 case 'Submitted': return 'info'; break; 130 case 'Rejected': return 'report_problem'; break; 131 case 'Accepted': return 'done'; break; 132 case 'Final': return 'lock'; break; 133 } 134 }, 135 editProj(id) { 136 this.selected = this.projs.filter(proj => proj._id == id)[0]; 137 138 if (this.isLoaded) { 139 this.$q.notify({ message: 'Cannot LOAD project, the module slot is full', color: 'red', 140 actions: [ { label: 'Dismiss', color: 'white', handler: () => { /* ... */ } } ] 141 }); return; 142 } 143 144 let update = (this.canShare || this.internal) ? true : false; 145 this.$q.loading.show(); 146 emit.fb({ op: 'get_proj', proj_id: id, update: update }); 147 }, 148 copyProj(id) { 149 this.selected = this.projs.filter(proj => proj._id == id)[0]; 150 151 if (!this.canShare && !this.defaultProj) { 152 this.$q.notify({ message: 'Cannot COPY project that is not default, owned, or shared in full.',
152color: 'red', 153 actions: [ { label: 'Dismiss', color: 'white', handler: () => { /* ... */ } } ] 154 }); return; 155 } 156 if (this.isLoaded) { 157 this.$q.notify({ message: 'Cannot COPY project, the module slot is full', color: 'red', 158 actions: [ { label: 'Dismiss', color: 'white', handler: () => { /* ... */ } } ] 159 }); return; 160 } 161 162 this.proj.proj_type = this.selected.proj_type; 163 this.proj.state = this.selected.state; 164 this.view = 'create'; 165 this.mode = 'COPY'; 166 }, 167 deleteProj(id) { 168 this.selected = this.projs.filter(proj => proj._id == id)[0]; 169 let msg = ''; 170 171 if (this.isLoaded) { msg = 'Cannot DELETE project while module slot is full' } 172 if (!this.canShare) { msg = 'Cannot DELETE project that is not owned.' } 173 if (this.selected.status == 'Accepted' || this.selected.status == 'Final') { msg = 'Cannot DELETE ineligible project' } 174 175 if (msg) { 176 this.$q.notify({ message: msg, color: 'red', 177 actions: [ { label: 'Dismiss', color: 'white', handler: () => { /* ... */ } } ] 178 }); return; 179 } 180 181 this.$q.dialog({ title: 'Confirm Delete', message: 'This action cannot be undone.', 182 cancel: true, 183 persistent: true 184 }).onOk(() => { 185 this.admin = false; 186 emit.fb({ op: 'delete_proj', proj_id: id, callback: this.callback }); 187 }); 188 }, 189 openAdmin(id) { 190 this.selected = this.projs.filter(proj => proj._id == id)[0]; 191 this.shared = this.selected.shared.hasOwnProperty(firebase.auth().currentUser.uid); 192 this.admin = true; 193 setTimeout(() => { 194 let users = document.querySelectorAll('[data-share-hash]'); 195 for (let n = 0; n < users.length; n++) { 196 while (users[n].firstChild) { users[n].firstChild.remove() } 197 let h = hashicon(users[n].dataset.shareHash, 30); 198 users[n].appendChild(h); 199 } 200 }, 500); 201 }, 202 shareProj() { 203 if (this.share_email == '' || this.access_level == null) { return } 204 let msg = null; 205 206 if (this.internal) { msg = 'Internal project sharing is NOT enabled' } 207 if (!this.canShare) { msg = 'Missing elevated SHARE credentials' } 208 if (this.share_email == '[email protected]' || this.share_email == firebase.auth().currentUser.email) { msg = 'Invalid share address'} 209 210 if (msg) { 211 this.$q.notify({ message: msg, color: 'red', 212 actions: [ { label: 'Dismiss', color: 'white', handler: () => { /* ... */ } } ] 213 }); return; 214 } 215 if (!msg) { 216 emit.fb({ op: 'share_proj', type: 'share', proj_id: this.selected._id, share_email: this.share_email, access_level: this.access_level, callback: this.callback }); 217 } 218 219 this.share_email = ''; 220 this.access_level = null; 221 }, 222 revokeShare(id) { 223 if (!this.canShare) { 224 this.$q.notify({ message: 'Missing credentials to revoke SHARE function', color: 'red', 225 actions: [ { label: 'Dismiss', color: 'white', handler: () => { /* ... */ } } ] 226 }); return; 227 } 228 emit.fb({ op: 'share_proj', type: 'revoke', proj_id: this.selected._id, user: id, callback: this.callback }); 229 }, 230 updateProj(fld) { // CONSIDER INDEPENDANT CALLS, there is really NO code savings here!!! 231 switch (fld) { 232 case 'agency_review': { 233 if (!this.internal) { // should not be necessary, extra precaution 234 this.$q.notify({ message: 'Missing credentials to set Agency Review', color: 'red', 235 actions: [ { label: 'Dismiss', color: 'white', handler: () => { /* ... */ } } ] 236 }); return; 237 } 238 if (this.selected.agency_review == true) { 239 this.$q.dialog({ 240 title: 'Confirm Review', message: 'Make this project visible to External Agency NEPA reviewers?', 241 cancel: true, 242 persistent: true 243 }).onOk(() => { 244 emit.fb({ op: 'update_proj', proj_id: this.selected._id, fld: 'agency_review', val: true, callback: this.callback }); 245 }).onCancel(() => { 246 this.selected.agency_review = false;
247 }); 248 } else { emit.fb({ op: 'update_proj', proj_id: this.selected._id, fld: 'agency_review', val: false, callback: this.callback }) } 249 } break; 250 251 case 'status': 252 if (!this.canShare && !this.internal) { 253 this.$q.notify({ message: 'Missing credentials to set Status', color: 'red', 254 actions: [ { label: 'Dismiss', color: 'white', handler: () => { /* ... */ } } ] 255 }); return; 256 } 257 if (this.selected.status == 'Final') { 258 this.$q.notify({ message: 'Cannot set Status of ineligible project', color: 'red', 259 actions: [ { label: 'Dismiss', color: 'white', handler: () => { /* ... */ } } ] 260 }); return; 261 } 262 263 this.$q.dialog({ 264 title: (this.status.value == 'Submitted') ? 'Confirm Submit' : 'Confirm Final', 265 message: (this.status.value == 'Submitted') ? 'This action will notify agency authority that project data is ready for review. Continue?' : 266 'Setting Status to Final means the project has been completed in the real world to the specifications outlined in project dataset (i.e. As-Built). Final action means that this project will no longer be editable. Continue?', 267 cancel: true, 268 persistent: true 269 }).onOk(() => { 270 emit.fb({ op: 'update_proj', proj_id: this.selected._id, fld: 'status', val: this.status.value, callback: this.callback }); 271 this.status = null; 272 }); 273 274 } 275 }, 276 arsQuery() { 277 this.projs = this.getProjs().filter(proj => proj.module == this.module.name && proj.field_office == this.qry_field); 278 if (this.projs.length == 0) { 279 emit.fb({ op: 'ars_query', fld: this.qry_field, callback: this.callback }); 280 } 281 }, 282 callback(typ, id) { 283 this.projs = this.getProjs().filter(proj => proj.module == this.module.name); 284 285 switch (typ) { 286 case 'create': { 287 for (let n in this.proj) { this.proj[n] = '' } 288 this.mode = 'CREATE'; 289 this.editProj(id); 290 } break; 291 case 'copy': { 292 for (let n in this.proj) { this.proj[n] = '' } 293 this.mode = 'CREATE'; 294 this.editProj(id); 295 } break; 296 case 'admin': { 297 let id = this.selected._id; 298 this.openAdmin(id); 299 } break; 300 } 301 } 302 303 }, 304 watch: { 305 view() { 306 this.admin = false; 307 }, 308 module() { // should function for both create and query 309 this.admin = false; 310 this.qry_field = ''; 311 this.projs = this.getProjs().filter(proj => proj.module == this.module.name); 312 313 for (let n in this.proj) { this.proj[n] = '' } 314 this.proj_types = this.module.proj_types.split(','); 315 this.errors.forEach((el, i) => { this.errors[i] = false }); 316 }, 317 admin() { this.status = null }, 318 319 'proj.state': function() { 320 if (this.proj.state) { 321 if (this.profile.acct_type == 'internal' && this.profile.title != 'Air Resource Specialist') { 322 this.fields = this.profile.flds; 323 } else { 324 this.fields = emit.qry({ typ: 'lookup', q: { '$and': [ 325 { component: 'create' }, 326 { target: 'fields' }, 327 { filter: this.proj.state } 328 ]} }); 329 this.proj.field_office = null; 330 this.proj.county = null; 331 } 332 } 333 }, 334 'proj.field_office': function() { 335 if (this.proj.field_office) {
336 this.counties = emit.qry({ typ: 'lookup', q: { '$and': [ 337 { module: 'all' }, 338 { component: 'create' }, 339 { target: 'counties' }, 340 { filter: this.proj.field_office } 341 ]} }); 342 this.proj.county = null; 343 } 344 }, 345 qry_state() { 346 this.fields = emit.qry({ typ: 'lookup', q: { '$and': [ 347 { component: 'create' }, 348 { target: 'fields' }, 349 { filter: this.qry_state } 350 ]} }); 351 this.qry_field = null; 352 }, 353 } 354}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.