Lp#1846552: Port Local Admin Shelving Location Order Editor to Angular
authorZavier Banks <zbanks@catalyte.io>
Fri, 11 Oct 2019 21:17:40 +0000 (21:17 +0000)
committerZavier Banks <zbanks@catalyte.io>
Fri, 11 Oct 2019 21:17:40 +0000 (21:17 +0000)
I ported the admin shelving location from dojo, into Angular, with all the
same bells and whistles. Using the Angular framework, the user can drag and
drop through a list of different org units, and save said list to
a database.

Signed-off-by: Zavier Banks <zbanks@catalyte.io>
Open-ILS/src/eg2/src/app/staff/admin/local/admin-local.module.ts
Open-ILS/src/eg2/src/app/staff/admin/local/copy-location-order.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/copy-location-order.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts
Open-ILS/src/eg2/src/app/staff/admin/local/share/card.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/share/card.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/share/table-list.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/share/table-list.component.ts [new file with mode: 0644]

index 9f70ab7..8beccea 100644 (file)
@@ -7,13 +7,19 @@ import {AdminLocalSplashComponent} from './admin-local-splash.component';
 import {AddressAlertComponent} from './address-alert.component';
 import {AdminCarouselComponent} from './admin-carousel.component';
 import {StandingPenaltyComponent} from './standing-penalty.component';
+import {CopyLocationOrderComponent} from './copy-location-order.component';
+import {TableListComponent} from './share/table-list.component';
+import {CardComponent} from './share/card.component';
 
 @NgModule({
   declarations: [
       AdminLocalSplashComponent,
       AddressAlertComponent,
       AdminCarouselComponent,
-      StandingPenaltyComponent
+      StandingPenaltyComponent,
+      CopyLocationOrderComponent,
+      TableListComponent,
+      CardComponent
   ],
   imports: [
     AdminCommonModule,
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/copy-location-order.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/copy-location-order.component.html
new file mode 100644 (file)
index 0000000..47631cf
--- /dev/null
@@ -0,0 +1,23 @@
+<div (mouseup)="disableMouseUp()" (mouseleave)="disableMouseUp()" [style.height.vh]="100">\r
+    <eg-staff-banner bannerText="Shelving Location Order" i18n-bannerText>\r
+    </eg-staff-banner>\r
+    <div>\r
+        <h3>Context Org Unit</h3>\r
+        <div [style.width.%]="30">\r
+            <eg-org-select domId="acpl" [applyDefault]="true" (onChange)="onChange($event)"></eg-org-select>            \r
+        </div>\r
+    </div>    \r
+    <p>To move an item, drag it up or down with the mouse.</p>\r
+    <ng-container *ngIf="locations && locations.length>0">\r
+        <table-list \r
+        [content_list]="locations" \r
+        [is_mouse_down]="detect_mouse_down"\r
+        (saveContent)="applyChanges($event)"\r
+        (toggleDragAndDrop)="toggleDragAndDrop($event)"   \r
+        (nodeIsSelected)="displaySelectedNode($event)" (detectMousePosition)="onMouseDown($event)"></table-list>\r
+    </ng-container>\r
+    <div *ngIf="detect_mouse_down && detect_mouse"\r
+    [ngStyle]="position_styling" \r
+    [style.top.px]="mouse_y_position+25" \r
+    [style.left.px]="mouse_x_position-75" >{{selected_node.name}} ({{selected_node.shortname}})</div>\r
+</div>
\ No newline at end of file
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/copy-location-order.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/copy-location-order.component.ts
new file mode 100644 (file)
index 0000000..756600e
--- /dev/null
@@ -0,0 +1,209 @@
+import {Component, OnInit, HostListener} from '@angular/core';\r
+import {PcrudService} from '@eg/core/pcrud.service';\r
+import {AuthService} from '@eg/core/auth.service';\r
+import {ToastService} from '@eg/share/toast/toast.service';\r
+import { IdlService } from '@eg/core/idl.service';\r
+\r
+\r
+\r
+class Category {\r
+    name:string;\r
+    owning_lib:string;\r
+    shortname:string;\r
+    unhashed_element:any;\r
+}\r
+\r
+@Component({\r
+    templateUrl: './copy-location-order.component.html'\r
+})\r
+\r
+export class CopyLocationOrderComponent implements OnInit {\r
+    orders:any;\r
+    locations:any;\r
+    id_log=[];\r
+    lib_tree=[];\r
+    active_libraries=[];\r
+    mouse_x_position:number;    \r
+    mouse_y_position:number;\r
+    detect_mouse_down=false;\r
+    detect_mouse = false;\r
+    selected_node:any;\r
+\r
+    position_styling={\r
+        backgroundColor:"white",\r
+        boxShadow:"5px 4px 10px 2px #5B5A5A",\r
+        textAlign:"center",\r
+        padding:"3px",\r
+        width:'80px',\r
+        position:'fixed',\r
+    }\r
+\r
+    constructor(\r
+        private pcrud: PcrudService,\r
+        private idl: IdlService,\r
+        private toast: ToastService,        \r
+        private auth: AuthService,\r
+    ) {\r
+    }\r
+\r
+    ngOnInit() {\r
+        //this.createTree();        \r
+        //this.filterGrid(this.auth.user().ws_ou());\r
+    };\r
+\r
+    detectMouseLeave(){\r
+        this.detect_mouse=false;\r
+    }\r
+\r
+    toggleDragAndDrop(toggle){\r
+\r
+        this.detect_mouse=toggle;\r
+    }\r
+\r
+    disableMouseUp(){   \r
+        this.detect_mouse_down=false;\r
+    }\r
+    onMouseDown(is_mouse_down){\r
+        this.detect_mouse_down=is_mouse_down;\r
+    }\r
+    \r
+\r
+    displaySelectedNode(node){\r
+        this.selected_node=node;\r
+    }\r
+\r
+    /**\r
+     * Initializes the search for the library tree and the array of the library locations.\r
+     * @param org The authorized user passed in during initialization.\r
+     */\r
+    filterGrid(org) {\r
+        // fetch the locations and order entries\r
+        if(!this.orders) {\r
+            this.pcrud.search('acplo', {org : org}, {order_by : {acplo : 'position'}})\r
+                .subscribe(elements => console.log("Order",elements));\r
+            this.mappingLocation();\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * Maps the values of the specified category to an array of objects, whose keys hold the necessary values.\r
+     */\r
+    mappingLocation(){       \r
+        this.locations=[];\r
+        this.pcrud.search('acpl',\r
+        {owning_lib:this.id_log, deleted : 'f'}\r
+        ).subscribe(element => {\r
+            var categ = new Category();\r
+            var unhashed_element=element;\r
+            var hashed_element = this.idl.toHash(element, false);\r
+            var found_library =  this.findLibrary(hashed_element.owning_lib)\r
+            categ.name=hashed_element.name;\r
+            categ.owning_lib= found_library.name;\r
+            categ.shortname=found_library.shortname;\r
+            categ.unhashed_element=unhashed_element;\r
+            this.locations.push(categ);\r
+        });\r
+        this.id_log=[];\r
+    }\r
+\r
+    /**\r
+     * Finds the Library name, using the library id.\r
+     * @param lib_id The id that is to be used to find the name.\r
+     */\r
+    findLibrary(lib_id){\r
+        var lib_name;\r
+        this.active_libraries.forEach(library=>{\r
+            if(library.id==lib_id){\r
+                lib_name= {\r
+                    name:library.name,\r
+                    shortname:library.shortname\r
+                };\r
+            }\r
+        })\r
+        return lib_name;\r
+    }\r
+\r
+    /**\r
+     * Starts from parent, logs id, looks for a specific key, checks to see if it has any children. If so\r
+     * rinse repeat.\r
+     * @param parent_node The uppermost parent node. \r
+     */\r
+    nodeTrailing(parent_node){\r
+        this.findParent(parent_node);\r
+    }\r
+\r
+    /**\r
+     * Creates a tree of the different library branches\r
+     * @param node The parent node, of which, the branches 'branch out'\r
+     */\r
+    createBranch(node){\r
+        this.lib_tree.push(node);\r
+        node.children.forEach(child => {\r
+            this.createBranch(child);\r
+        });\r
+    }\r
+\r
+    /**\r
+     * Checks if the node passed in has a parent node. If so, follow that branch, while saving the child nodes,\r
+     *  until the root node is found.\r
+     * @param node The node that is to be checked for a parent.\r
+     */\r
+    findParent(node){\r
+        this.id_log.push(node.id);\r
+        this.active_libraries.push(node);\r
+        var filteredTree= this.lib_tree.filter(branch=> branch.ou_type.depth<=(node.ou_type.depth-1)); //Creates a filtered branch\r
+        filteredTree.forEach(branch =>{\r
+            if\r
+            (\r
+                ( branch.id==node.parent_ou)\r
+                && this.isChild(branch.children, node)\r
+            )\r
+            {\r
+                this.findParent(branch);\r
+            }\r
+        })\r
+    }\r
+\r
+    /**\r
+     * Using the given parameters, checks if the 'node' passed is the parent of the 'children' parameter passed in.\r
+     * @param children The array of children nodes that is to be checked.\r
+     * @param node The assumed parent node.\r
+     */\r
+    isChild(children, node){\r
+        var names_are_equal = false;\r
+        children.forEach(child => {\r
+            if(child.shortname==node.shortname){\r
+                names_are_equal= true;\r
+            }                    \r
+        })\r
+        return names_are_equal;\r
+    }\r
+\r
+    onChange(object){\r
+        if(this.lib_tree.length==0){\r
+            this.createBranch(this.idl.toHash(object, false));\r
+        }\r
+        this.nodeTrailing(this.idl.toHash(object, false))\r
+        this.mappingLocation();\r
+    }\r
+\r
+    applyChanges(new_locations){\r
+        var library_locations=[];\r
+        new_locations.forEach(element => {\r
+            library_locations.push(element.unhashed_element)            \r
+        });\r
+        var library =this.pcrud.update(library_locations);   \r
+        \r
+        console.log(library.subscribe(object=> console.log(object)))     \r
+    }\r
+    \r
+  @HostListener('mousemove', ['$event'])\r
+  mouseMove($event: MouseEvent) {\r
+      this.mouse_y_position=$event.clientY;\r
+      this.mouse_x_position=$event.clientX;\r
+  }\r
+\r
+\r
+}\r
+\r
index 39c6be7..865fd7e 100644 (file)
@@ -5,6 +5,7 @@ import {BasicAdminPageComponent} from '@eg/staff/admin/basic-admin-page.componen
 import {AddressAlertComponent} from './address-alert.component';
 import {AdminCarouselComponent} from './admin-carousel.component';
 import {StandingPenaltyComponent} from './standing-penalty.component';
+import {CopyLocationOrderComponent} from './copy-location-order.component';
 
 const routes: Routes = [{
     path: 'splash',
@@ -23,9 +24,14 @@ const routes: Routes = [{
     path: 'config/standing_penalty',
     component: StandingPenaltyComponent
 }, {
+    path: 'asset/copy_location_order',
+    component: CopyLocationOrderComponent
+}, {
     path: ':schema/:table',
     component: BasicAdminPageComponent
-}];
+}
+
+];
 
 @NgModule({
   imports: [RouterModule.forChild(routes)],
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/share/card.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/share/card.component.html
new file mode 100644 (file)
index 0000000..33eb983
--- /dev/null
@@ -0,0 +1,14 @@
+\r
+\r
+<div \r
+(mouseenter)="onMouseEnter()" \r
+(mouseleave)="onMouseLeave()"\r
+[ngStyle]="{display:'flex', flexDirection:'row'}">\r
+    <span *ngIf="is_mouse_down==true && detect_drag==true"\r
+    [ngStyle]="divider_style" class="material-icons">keyboard_arrow_right</span>\r
+    <button class="btn btn-lg"\r
+    (mousedown)="onMouseDownEvent()" \r
+    (mouseup)="onMouseUpEvent()" \r
+    [style.fontSize.px]="10"\r
+    >{{index+1}}. <ng-content></ng-content></button>\r
+</div>\r
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/share/card.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/share/card.component.ts
new file mode 100644 (file)
index 0000000..7f3310c
--- /dev/null
@@ -0,0 +1,50 @@
+\r
+import {Component, OnInit, Input,Output, EventEmitter, ViewChild} from '@angular/core';\r
+\r
+@Component({\r
+    selector:'card',\r
+    templateUrl: './card.component.html'\r
+})\r
+\r
+export class CardComponent implements OnInit {\r
+    @Input() content:any;\r
+    @Input() index:number;\r
+    @Input() content_length:number;\r
+    @Output() detectMouseUpEvents= new EventEmitter<any>();\r
+    @Output() detectMouseDownEvents= new EventEmitter<any>();\r
+    detect_drag=false;\r
+    @Input() is_mouse_down:boolean;\r
+\r
+    card_style={\r
+        display:"flex",\r
+        flexDirection:"column"\r
+    }\r
+\r
+    divider_style={\r
+        marginTop:"4px",\r
+        width:"2px"\r
+    }\r
+\r
+    constructor() {\r
+    }\r
+    \r
+\r
+    ngOnInit() {};\r
+\r
+    onMouseEnter(){\r
+        this.detect_drag=true;\r
+    }\r
+    onMouseLeave(){\r
+        this.detect_drag=false;\r
+    }\r
+\r
+    onMouseUpEvent(){\r
+        this.is_mouse_down=false;\r
+        this.detectMouseUpEvents.emit(this.index)\r
+    }\r
+    \r
+    onMouseDownEvent(){\r
+        this.is_mouse_down=true;\r
+        this.detectMouseDownEvents.emit(this.index);\r
+    }\r
+}\r
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/share/table-list.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/share/table-list.component.html
new file mode 100644 (file)
index 0000000..440bae9
--- /dev/null
@@ -0,0 +1,17 @@
+\r
+\r
+<div [ngStyle]="table_container">\r
+    <div [ngStyle]="card_container" (mouseleave)="onMouseLeave()" (mouseenter)="onMouseEnter()">        \r
+        <button class="btn btn-lg" (click)="applyChanges()" [ngStyle]="button_style">Apply Changes?</button>\r
+        <div *ngFor="let content of mutable_content_list; let i = index">\r
+            <card         \r
+                [content]="content" [index]="i"\r
+                [content_length]="mutable_content_list.length"\r
+                [is_mouse_down]="is_mouse_down"\r
+                (detectMouseDownEvents)="onMouseDown($event)" \r
+                (detectMouseUpEvents)="onMouseUp($event)" >{{content.name}} ({{content.shortname}})</card>\r
+        </div>\r
+    </div>\r
+    <div>\r
+    </div>\r
+</div>\r
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/share/table-list.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/share/table-list.component.ts
new file mode 100644 (file)
index 0000000..de92637
--- /dev/null
@@ -0,0 +1,174 @@
+\r
+import {Component, OnInit, Input,Output,EventEmitter} from '@angular/core';\r
+\r
+/**\r
+ * Creates a class for the pairs.\r
+ */\r
+class Pair {\r
+    1:number;\r
+    2:number;\r
+}\r
+\r
+@Component({\r
+    selector:'table-list',\r
+    templateUrl: './table-list.component.html'\r
+})\r
+\r
+export class TableListComponent implements OnInit {\r
+    @Input() content_list:any;\r
+    @Output() saveContent= new EventEmitter<any>();\r
+    @Output() detectMousePosition = new EventEmitter<boolean>();\r
+    @Output() nodeIsSelected =  new EventEmitter<any>();\r
+    @Output() toggleDragAndDrop = new EventEmitter<any>();\r
+    @Input() is_mouse_down=false;\r
+    detect_mouse_leave=false;\r
+    mutable_content_list:any;\r
+    mutable_content_dictionary={};\r
+    card_pairs:Pair;\r
+\r
+    /**\r
+     * The Following are style variables to style to tabs and cards in the html file.\r
+     */\r
+\r
+    table_container={\r
+        display:"flex",\r
+        flexDirection:"row",\r
+        justifyContent:"flex-start"\r
+    }\r
+    \r
+    card_container={   \r
+        display:"flex",\r
+        flexDirection:"column",\r
+        justifyContent:"center",\r
+        width:"300px",\r
+        marginRight:"10px"\r
+    }\r
+\r
+    button_style={\r
+        margin:"auto"\r
+    }\r
+\r
+    constructor() {\r
+    };\r
+\r
+    ngOnInit() {\r
+        this.card_pairs= new Pair();   \r
+        this.convertListToDictionary(this.content_list);\r
+        this.mutable_content_list= this.content_list.slice(0, this.content_list.length);//Retains immutability when saving string to another variable.\r
+   \r
+    };\r
+\r
+    ngAfterViewInit(){\r
+    }\r
+    applyChanges(){\r
+        this.saveContent.emit(this.mutable_content_list);\r
+    }\r
+\r
+    onMouseLeave(){\r
+        //this.is_mouse_down=false;    \r
+        this.toggleDragAndDrop.emit(false);\r
+\r
+    }\r
+    onMouseEnter(){\r
+        this.toggleDragAndDrop.emit(true);\r
+    }\r
+\r
+    onMouseDown(index){  \r
+        this.detect_mouse_leave=true;    \r
+        this.is_mouse_down=true;\r
+        this.card_pairs[1]=index;\r
+        this.detectMousePosition.emit(true);\r
+        this.toggleDragAndDrop.emit(true);\r
+        this.nodeIsSelected.emit(this.mutable_content_list[index]);\r
+    }\r
+    onMouseUp(index){\r
+        this.toggleDragAndDrop.emit(false);\r
+        this.detectMousePosition.emit(false);\r
+        if(this.card_pairs[1]!=undefined && this.card_pairs[1]!=null){ //Checks if the tab pair is a number\r
+            this.card_pairs[2]=index;          \r
+            this.is_mouse_down=false;\r
+            if((this.card_pairs[1]!= this.card_pairs[2]) && this.card_pairs[2]!=undefined && this.card_pairs[2]!=null){  //Checks if the tab pair is a number\r
+                this.reorderContentListFromCards(this.card_pairs, this.mutable_content_list);\r
+            }        \r
+        }else{            \r
+            this.is_mouse_down=false;\r
+        }   \r
+    }  \r
+\r
+    /**\r
+     * Converts a list into a dictionary to decrease the time complexity of multiple rearrangements.\r
+     * @param list The list that is to be converted\r
+     */\r
+    convertListToDictionary(list){\r
+        var dictionary ={}\r
+        list.forEach(function (element, index) {\r
+            dictionary[index]=element;\r
+        });\r
+        this.mutable_content_dictionary=dictionary;\r
+    }\r
+\r
+    \r
+    convertDictionaryToList(dictionary){\r
+        var new_list=[];\r
+        for(var x =0; x<Object.keys(dictionary).length;x++){\r
+            new_list.push(dictionary[x]);\r
+        }\r
+        return new_list;\r
+    }\r
+\r
+\r
+    /**\r
+     * Reorders the content list using the pairs provided by the user.\r
+     * @param pairs The pairs provided by the user.\r
+     * @param list The List that is to be reordered.\r
+     */\r
+    reorderContentListFromCards(pairs, list){\r
+        // var new_content_list = list.slice(0,list.length);\r
+        // new_content_list.splice(pairs[1],1);//Rearrange the element into the selected place.\r
+        // new_content_list.splice(pairs[2], 0, list[pairs[1]]); \r
+        // this.mutable_content_list=new_content_list;\r
+        this.mutable_content_list=this.convertDictionaryToList(\r
+                                  this.reorderDictionary(this.mutable_content_dictionary, pairs)\r
+                                   );     \r
+        this.card_pairs=new Pair();//Reset the pairs\r
+    }\r
+\r
+    reorderDictionary(dictionary, paired_values){\r
+        var new_dictionary = new Object();\r
+        new_dictionary=Object.assign({...dictionary, [paired_values[2]]:dictionary[paired_values[1]]}); \r
+        new_dictionary=this.alternateLoopWithHighestValueInPair(dictionary, new_dictionary, paired_values);   \r
+        this.mutable_content_dictionary= Object.assign({...new_dictionary});\r
+        return this.mutable_content_dictionary;\r
+    }\r
+\r
+    alternateLoopWithHighestValueInPair(dictionary, new_dictionary, paired_values){  \r
+        if(paired_values[2]>paired_values[1]){ \r
+                \r
+            for(var y=0; y<paired_values[1]; y++){\r
+                new_dictionary[y]=dictionary[y];\r
+            }   \r
+            for(var z=paired_values[1]; z<paired_values[2]; z++){\r
+                new_dictionary[z]=dictionary[z+1];\r
+            }  \r
+            \r
+            for(var x = Object.keys(dictionary).length-1; x > paired_values[2]; x--){\r
+                new_dictionary[x]=dictionary[x];\r
+            }\r
+        }else{\r
+               \r
+            for(var y=0; y<paired_values[2]; y++){\r
+                new_dictionary[y]=dictionary[y];\r
+            }   \r
+\r
+            for(var z=paired_values[2]; z<paired_values[1]; z++){\r
+                new_dictionary[z+1]=dictionary[z];\r
+            }  \r
+            \r
+            for(var x = Object.keys(dictionary).length-1; x > paired_values[1]; x--){\r
+                new_dictionary[x]=dictionary[x];\r
+            }\r
+        }          \r
+        return Object.assign({...new_dictionary});\r
+    }\r
+}\r
+\r