<commit_before>function getCookie(name) {
  var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
  return r ? r[1] : undefined;
};

describe('Filebrowser', function() {
  var scope;
  var controller;
  var $compile;
  var modal;

  var mockModal = {
    result: {
      then: function(confirmCallback, cancelCallback) {
        this.confirmCallback = confirmCallback;
        this.cancelCallback = cancelCallback;
      }
    },
    close: function() {
      this.result.confirmCallback();
    },
    dismiss: function() {
      this.result.cancelCallback();
    }
  };

  beforeEach(module('oide.filebrowser'));
  beforeEach(module('oide.filesystemservice'));
  beforeEach(module('oide.templates'));
  beforeEach(module('oide.filetreedirective'));
  beforeEach(module('ui.bootstrap'));
  beforeEach(module('angularFileUpload'));
  // beforeEach(module('FileService'));

  describe('FilebrowserController Test', function() {
    var httpBackend;
    var http;
    var files;
    var groups = ['saurabh', 'sudo', 'adm'];
    var volume_info = {
      'percent': '10',
      'used': '10',
      'size': '100'
    };
    beforeEach(inject(function($controller, $rootScope, $httpBackend, $http, FilesystemService, FileService, _$compile_, _$modal_){
      mockFileService = FileService;
      $compile = _$compile_;
      modal = _$modal_;
      // The injector unwraps the underscores (_) from around the parameter names when matching
      httpBackend = $httpBackend;
      httpBackend.whenGET(/\/filebrowser\/filetree\/a\/dir\?dirpath=.*/).respond(function(){
        return [200, files];
      });
      httpBackend.whenGET('/filebrowser/a/fileutil?operation=GET_GROUPS').respond(function(){
        return [200, groups];
      });
      httpBackend.whenPOST(/\/filebrowser\/a\/fileutil\?newpath=.*&operation=COPY&origpath=.*/).respond(function(){
        return [200, {'status': 'copied file'}];
      });
      httpBackend.whenGET(/\/filebrowser\/a\/fileutil\?filepath=.*&operation=GET_NEXT_DUPLICATE/).respond(function(){
        return [200, []];
      });
      httpBackend.whenGET(/\/filebrowser\/a\/fileutil\?dirpath=.*&operation=GET_NEXT_UNTITLED_FILE/).respond(function(){
        return [200, []];
      });
      httpBackend.whenGET(/\/filebrowser\/a\/fileutil\?dirpath=.*&operation=GET_NEXT_UNTITLED_DIR/).respond(function(){
        return [200, []];
      });
      scope = $rootScope.$new();
      controller = $controller;
      http = $http;

      // Mock FileService
      var currentDirectory = ['/', 'home', 'saurabh'];
      var currentFile = '/home/saurabh/testfile';
      var rootDirectory = ['/', 'home', 'saurabh'];
      files = [{
        "filepath": "/home/saurabh/file1",
        "filename": "file1",
        "group": "saurabh",
        "is_accessible": true,
        "perm": "-rw-rw-r--",
        "perm_string": "664",
        "size": "0.0 KiB",
        "type": "file"
      }, {
        "filepath": "/home/saurabh/file2",
        "filename": "file2",
        "group": "root",
        "is_accessible": false,
        "perm": "-rw-r--r--",
        "perm_string": "644",
        "size": "0.0 KiB",
        "type": "file"
      },
      {
        "filepath": "/home/saurabh/file3",
        "filename": "file3",
        "group": "saurabh",
        "is_accessible": true,
        "perm": "-rw-rw-r--",
        "perm_string": "664",
        "size": "0.0 KiB",
        "type": "file"
      }];
      var fileData;

      mockFilesystemService = FilesystemService;
      // Mock FiletreeService
      mockFiletreeService = {
        updateFiletree: function() {
          console.log('Filetree Refreshed');
        }
      };
      controller = $controller(
        'FilebrowserController as ctrl', {
          $scope: scope,
          FileService: mockFileService,
          FilesystemService: mockFilesystemService,
          FBFiletreeService: mockFiletreeService,
          $modal: modal
        });
        scope.ctrl = controller;
        scope.$apply();
    }));

    it('checks if current directory is set', function() {
      mockFileService.setCurrentDirectory("/home/saurabh/");
      scope.$apply();
      var currentDirectory = mockFileService.getCurrentDirectory();
      var ref = ['/', 'home', 'saurabh'];
      var are_directories_same = is_same(currentDirectory, ref);
      expect(are_directories_same).toBeTruthy();
    });

    it('checks if volume info is set', function(){
      mockFileService.setVolumeInfo(volume_info);
      scope.$apply();
      expect(scope.ctrl.volumeUsed).toBe('10');
    });

    it('checks if root directory is set', function(){
      mockFileService.setRootDirectory('/home/saurabh/');
      scope.$apply();
      var ref = ['/', 'home', 'saurabh'];
      var are_directories_same = is_same(scope.ctrl.rootDirectory, ref);
      expect(are_directories_same).toBeTruthy();
    });

    function is_same(arr1, arr2) {
      return (arr1.length == arr2.length) && arr1.every(function(element, index){
        return element === arr2[index];
      });
    }

    it('should form a correct current dir path', function(){
      mockFileService.setCurrentDirectory('/home/saurabh/');
      scope.$apply();
      var currentDirectory = scope.ctrl.currentDirectory;
      var ref = ['/', 'home', 'saurabh'];
      var are_directories_same = is_same(currentDirectory, ref);
      expect(are_directories_same).toBeTruthy();
      expect(scope.ctrl.formDirPath()).toBe('/home/saurabh/');
    });

    it('should fetch files for a particular directory', function(){
      mockFilesystemService.getFiles({
        filepath: '/home/saurabh'
      }, function(data){
        // scope.ctrl.fileData = data;
        mockFileService.setFileData(data);
      });
      httpBackend.flush();
      expect(scope.ctrl.fileData).toBeDefined();
      // Length of filedata should be 3
      expect(scope.ctrl.fileData.length).toBe(3);
    });

    it('should show details of an accessible file', function(){
      spyOn(scope.ctrl, 'populatePermissions');
      scope.ctrl.ShowDetails(files[0]);
      // show_details should be set to true
      expect(scope.ctrl.show_details).toBeTruthy();
      // populatePermissions to have been called
      expect(scope.ctrl.populatePermissions).toHaveBeenCalled();
    });

    it('should not show details of an inaccessible file', function(){
      spyOn(scope.ctrl, 'populatePermissions');
      scope.ctrl.ShowDetails(files[1]);
      // show_details should be set to false
      expect(scope.ctrl.show_details).not.toBeTruthy();
      // populatePermissions should not have been called
      expect(scope.ctrl.populatePermissions).not.toHaveBeenCalled();
    });

    it('should populate permissions for the selected file', function(){
      // Set a file as the selected file on the scope
      scope.ctrl.selectedFile = files[0];
      scope.ctrl.populatePermissions();
      // Expect that self.currentFilePermissions would be defined
      expect(scope.ctrl.currentFilePermissions).toBeDefined();
      // Permission should be as expected
      expect(scope.ctrl.currentFilePermissions['user']['r']).toBeTruthy();
      expect(scope.ctrl.currentFilePermissions['user']['w']).toBeTruthy();
      expect(scope.ctrl.currentFilePermissions['user']['x']).not.toBeTruthy();
      expect(scope.ctrl.currentFilePermissions['group']['r']).toBeTruthy();
      expect(scope.ctrl.currentFilePermissions['group']['w']).toBeTruthy();
      expect(scope.ctrl.currentFilePermissions['group']['x']).not.toBeTruthy();
      expect(scope.ctrl.currentFilePermissions['others']['r']).toBeTruthy();
      expect(scope.ctrl.currentFilePermissions['others']['w']).not.toBeTruthy();
      expect(scope.ctrl.currentFilePermissions['others']['x']).not.toBeTruthy();
    });

    it('should refresh the directory', function(){
      // set current directory
      scope.ctrl.currentDirectory = ['/', 'home', 'saurabh'];
      spyOn(mockFilesystemService, 'getFiles');
      scope.ctrl.refreshDirectory();
      httpBackend.flush();
      // Expect the FilesystemService.getFiles method to be called
      expect(mockFilesystemService.getFiles).toHaveBeenCalled();
      // Expect self.fileData to be defined
      // expect(scope.ctrl.fileData).toBeDefined();
    });

    it('should be able to change the directory', function(){
      // Set the current directory
      scope.ctrl.currentDirectory = ['/', 'home', 'saurabh', 'workspace', 'oide'];
      // Navigate to /home/saurabh
      scope.ctrl.changeDir(2);
      httpBackend.flush();
      // Expect the current directory to be /home/saurabh
      expect(scope.ctrl.formDirPath()).toBe("/home/saurabh/");
    });

    it('should not be able to navigate below the root directory', function(){
      // Set the current directory
      scope.ctrl.currentDirectory = ['/', 'home', 'saurabh', 'workspace', 'oide'];
      // navigate to /home/
      scope.ctrl.changeDir(1);
      // Expect the current directory to be /home/saurabh/workspace/oide
      expect(scope.ctrl.formDirPath()).toBe('/home/saurabh/workspace/oide/');
    });

    it('should be able to copy a file', function(){
      scope.ctrl.selectedFile = files[0];
      scope.ctrl.copyFile();
      //expect isCopied to be true
      expect(scope.ctrl.isCopied).toBeTruthy();
      //expect copied file to be same as selected file
      expect(scope.ctrl.copiedFile).toBe(scope.ctrl.selectedFile);
    });

    it('should be able to paste a file', function(){
      scope.ctrl.currentDirectory = ['/', 'home', 'saurabh'];
      scope.ctrl.selectedFile = files[0];
      scope.ctrl.copyFile();
      // Paste file
      scope.ctrl.pasteFile();
      httpBackend.flush();
      expect(scope.ctrl.isCopied).not.toBeTruthy();
    });

    it('should be able to duplicate a file', function(){
      // Set current Directory
      scope.ctrl.setCurrentDirectory = ['/', 'home', 'saurabh'];
      scope.ctrl.refreshDirectory();
      httpBackend.flush();
      scope.ctrl.selectedFile = files[0];
      // Get length of current array
      var initial_length = scope.ctrl.fileData.length;
      // Duplicate file

      httpBackend.expectPOST(/\/filebrowser\/a\/fileutil\?operation=COPY&origpath=.*/).respond(function(){
        // Duplicate first element of files
        var element = files[0];
        files.push(element);
        return [200, {'status': 'copied file'}];
      });

      scope.ctrl.duplicateFile();
      httpBackend.flush();
      var new_length = scope.ctrl.fileData.length;
      expect(initial_length).toBe(new_length - 1);
    });

    it('should be able to create a new file', function(){
      // Set current Directory
      scope.ctrl.setCurrentDirectory = ['/', 'home', 'saurabh'];
      scope.ctrl.refreshDirectory();
      httpBackend.flush();
      scope.ctrl.selectedFile = files[0];
      // Get length of current array
      var initial_length = scope.ctrl.fileData.length;
      // Create New file

      httpBackend.expectPOST(/\/filebrowser\/localfiles.*/).respond(function(){
        // Duplicate first element of files
        var element = {filepath: '/home/saurabh', filename: 'newfile'};
        files.push(element);
        return [200, {'status': 'created new file'}];
      });

      scope.ctrl.createNewFile();
      httpBackend.flush();
      var new_length = scope.ctrl.fileData.length;
      expect(initial_length).toBe(new_length - 1);
    });

    it('should be able to create a new directory', function(){
      // Set current Directory
      scope.ctrl.setCurrentDirectory = ['/', 'home', 'saurabh'];
      scope.ctrl.refreshDirectory();
      httpBackend.flush();
      scope.ctrl.selectedFile = files[0];
      // Get length of current array
      var initial_length = scope.ctrl.fileData.length;
      // Create New file

      httpBackend.expectPOST(/\/filebrowser\/localfiles.*\?isDir=true/).respond(function(){
        // Duplicate first element of files
        var element = {filepath: '/home/saurabh', filename: 'newfolder'};
        files.push(element);
        return [200, {'status': 'created new file'}];
      });

      scope.ctrl.createNewDirectory();
      httpBackend.flush();
      var new_length = scope.ctrl.fileData.length;
      expect(initial_length).toBe(new_length - 1);
    });

    it('should delete a file', function(){
  		spyOn(modal, "open").and.callFake(function() {
  			return mockModal;
  		});

      spyOn(scope.ctrl, "refreshDirectory");
      scope.ctrl.selectedFile = files[0];
      scope.ctrl.deleteFile();
      scope.ctrl.modalInstance.close();
      expect(scope.ctrl.refreshDirectory).toHaveBeenCalled();
      expect(scope.ctrl.selectedFile).toBe("");
      expect(scope.ctrl.show_details).not.toBeTruthy();
    });

    it('should handle uploads', function(){
      spyOn(modal, "open").and.callFake(function(){
        return mockModal;
      });
      spyOn(scope.ctrl, "refreshDirectory");
      scope.ctrl.currentDirectory = ['/', 'home', 'saurabh', 'workspace'];
      scope.ctrl.openUploadModal();
      scope.ctrl.modalInstance.close();
      expect(scope.ctrl.refreshDirectory).toHaveBeenCalled();
    });

  });
});
<commit_msg>add test for delete modal controller for codeeditor<commit_after>describe('filetree', function(){
  var controller;
  var $filetreeService;
  var filetreeCtrl;
  var httpBackend;
  var $compile;
  var rootScope;
  var filesystemService;
  var editorService;
  var modal;
  var dirs = [{
        "filepath": "/home/saurabh/dir1",
        "filename": "dir1",
        "group": "saurabh",
        "is_accessible": true,
        "perm": "-rw-rw-r--",
        "perm_string": "664",
        "size": "4.0 KiB",
        "type": "dir"
      }, {
        "filepath": "/home/saurabh/dir2",
        "filename": "dir2",
        "group": "root",
        "is_accessible": false,
        "perm": "-rw-r--r--",
        "perm_string": "644",
        "size": "4.0 KiB",
        "type": "dir"
      },
      {
        "filepath": "/home/saurabh/dir3",
        "filename": "dir3",
        "group": "saurabh",
        "is_accessible": true,
        "perm": "-rw-rw-r--",
        "perm_string": "664",
        "size": "4.0 KiB",
        "type": "dir"
      }];

    var files = [{
          "filepath": "/home/saurabh/file1",
          "filename": "file1",
          "group": "saurabh",
          "is_accessible": true,
          "perm": "-rw-rw-r--",
          "perm_string": "664",
          "size": "4.0 KiB",
          "type": "dir"
        }, {
          "filepath": "/home/saurabh/file2",
          "filename": "file2",
          "group": "root",
          "is_accessible": false,
          "perm": "-rw-r--r--",
          "perm_string": "644",
          "size": "4.0 KiB",
          "type": "dir"
        }];

  var mockModal = {
    result: {
      then: function(confirmCallback, cancelCallback) {
        this.confirmCallback = confirmCallback;
        this.cancelCallback = cancelCallback;
      }
    },
    close: function() {
      this.result.confirmCallback();
    },
    dismiss: function() {
      this.result.cancelCallback();
    }
  };

  beforeEach(module('oide'));
  beforeEach(module('oide.editor'));
  beforeEach(module('oide.filesystemservice'));
  beforeEach(module('oide.templates'));
  beforeEach(module('oide.filetreedirective'));
  beforeEach(module('ui.bootstrap'));

  beforeEach(inject(function($controller, $rootScope, $log, $document, $httpBackend, _$compile_, FilesystemService, EditorService, _$modal_){
    scope = $rootScope.$new();
    rootScope = $rootScope;
    $compile = _$compile_;
    httpBackend = $httpBackend;
    filesystemService = FilesystemService;
    editorService = EditorService;
    modal = _$modal_;
    httpBackend.whenGET('/filebrowser/filetree/a/dir').respond(function(){
      return [200, dirs];
    });
    controller = $controller;
    controller = $controller('FiletreeCtrl', {
      $scope: scope,
      $document: $document,
      $log: $log,
    });
    scope.ctrl = controller;
    scope.$apply();
    var element = angular.element('<div oide-filetree tree-data="ctrl.treeData" leaf-level="file" selection-desc="ctrl.sd"></div>');
    el = $compile(element)(scope);
    scope.$digest();
    httpBackend.flush();
  }));

  describe('Whether the filetree is working as expected or not', function(){
    //Expect Filetree service to be defined
    it('Controller initialization should be proper', function(){
      expect(scope.ctrl).toBeDefined();
      expect(scope.ctrl.treeData).toBeDefined();
      expect(scope.ctrl.treeData.filetreeContents).toBeDefined();
      expect(scope.ctrl.treeData.filetreeContents.length).toBe(3);
    });
    it('should have valid default treeOptions loaded', function(){
      expect(scope.ctrl.sd).toBeDefined();
      expect(scope.ctrl.sd.multipleSelections).not.toBeTruthy();
    });
    it('should copy files to clipboard', function(){
      scope.ctrl.treeData.selectedNodes = [files[0], files[1]];
      scope.ctrl.copyFiles();
      // Expect clipboard length to be 2
      expect(scope.ctrl.clipboard.length).toBe(2);
    });
    it('should paste the selected files', function(){
      scope.ctrl.treeData.selectedNodes = [files[0], files[1]];
      scope.ctrl.copyFiles();
      scope.ctrl.treeData.selectedNodes = [dirs[0]]
      spyOn(filesystemService, 'pasteFile');
      scope.ctrl.pasteFiles();
      // Expect pasteFile method of FilesystemService to have been called
      expect(filesystemService.pasteFile).toHaveBeenCalled();
      // Expect clipboard length to be zero
      expect(scope.ctrl.clipboard.length).toBe(0);
    });
    it('should open files in editor', function(){
      spyOn(editorService, 'openDocument');
      scope.ctrl.treeData.selectedNodes = [files[0]];
      scope.ctrl.openFilesInEditor();
      // As files are selected, the method should be invoked
      expect(editorService.openDocument).toHaveBeenCalled();
    })
    it('if no files are selected, openDocument should not do anything', function(){
      spyOn(editorService, 'openDocument');
      scope.ctrl.treeData.selectedNodes = [];
      scope.ctrl.openFilesInEditor();
      // As nothing is selected, the method should not be called
      expect(editorService.openDocument).not.toHaveBeenCalled();
    });
    it('should delete a file', function(){
      spyOn(modal, "open").and.callFake(function(){
        return mockModal;
      });
      spyOn(filesystemService, 'deleteFile');
      scope.ctrl.treeData.selectedNodes = [files[0]];
      scope.ctrl.deleteFiles();
      scope.ctrl.deleteModalInstance.close();
      expect(filesystemService.deleteFile).toHaveBeenCalled();
    });

  });

});
<|endoftext|>


<commit_before>import {Store} from "flux/utils";

import * as earl                        from "@ignavia/earl";
import {Tolkien1To1Map, Tolkien1ToNMap} from "@ignavia/util";
import * as rdf                         from "@ignavia/rdf";
import {Vec2}                           from "@ignavia/ella";
import {GraphView} from "@ignavia/draph";

import {Stylesheet} from "../utils/stylesheet/index.js";

import dispatcher from "../dispatcher/dispatcher.js";

import rdfStore from "./rdfStore.js";
import selectionStore from "./selectionStore.js";

const rdfToken  = rdfStore.getDispatchToken();
const selectionToken = selectionStore.getDispatchToken();

class GraphStore extends Store {
    constructor(dispatcher) {
        super(dispatcher);
        this.initState();
    }

    initState() {
        const graph      = new earl.Graph();
        const stylesheet = new Stylesheet();
        const draph      = new GraphView(graph, stylesheet.computeAllStyles(
            rdfStore.getGraph(),
            rdfStore.getProfile()
        ));
        const layouter = new earl.RandomLayouter({
            width: draph.getWidth(),
            height: draph.getHeight(),
        });
        const layout = layouter.layout(graph);
        this.state = {
            graph,
            draph,
            layout,
            stylesheet
        };
    }

    getGraph() {
        return this.state.graph;
    }

    getDraph() {
        return this.state.draph;
    }

    convertEarlNodeToRDFNode(earlNode) {
        return rdf.RDFNode.fromNT(earlNode.id);
    }

    convertRDFNodeToEarlNode(rdfNode) {
        return this.state.graph.getNodeById(rdfNode.toNT());
    }

    convertEarlEdgeToRDFTriple(earlEdge) {
        return rdfStore.getGraph().getTripleById(earlEdge.id);
    }

    convertRDFTripleToEarlEdge(rdfTriple) {
        return this.state.graph.getEdgeById(rdfTriple.id);
    }

    getState() {
        return this.state;
    }

    importRDFGraph() {
        this.initState();

        for (let triple of rdfStore.state.graph) {
            this.addTriple(triple);
        }

        this.state.draph = new GraphView(
            this.state.graph,
            this.state.stylesheet.computeAllStyles(
                rdfStore.getGraph(),
                rdfStore.getProfile()
            )
        );
    }

    addNode(id, addToDraph = false) {
        if (!this.state.graph.getNodeById(id)) {
            const node = new earl.Node(id);
            this.state.graph.addNodes(node);
            if (addToDraph) {
                this.addNodeToDraph(node);
            }
        }
    }

    addNodeToDraph(nodeObj) {
        const conf = this.state.stylesheet.computeNodeStyle(
            rdfStore.getGraph(),
            rdfStore.getProfile(),
            nodeObj.id
        );
        this.state.draph.addNode(nodeObj, conf);
        const nodeG = this.state.draph.getNodeDisplayObjectById(nodeObj.id);
        this.state.layout.moveNodeTo(nodeObj, new Vec2(nodeG.x, nodeG.y));
    }

    addTriple({subject, object, id}, addToDraph = false) {
        const subjectHash = subject.toNT();
        this.addNode(subjectHash, addToDraph);

        if (!object.isLiteral()) {
            const objectHash = object.toNT();
            this.addNode(objectHash, addToDraph);

            const edge = new earl.Edge(subjectHash, objectHash, id);
            this.state.graph.addEdges(edge);
            if (addToDraph) {
                this.addEdgeToDraph(edge);
            }
        }
    }

    addEdgeToDraph(edgeObj) {
        const conf = this.state.stylesheet.computeEdgeStyle(
            rdfStore.getGraph(),
            rdfStore.getProfile(),
            edgeObj.id
        );
        this.state.draph.addEdge(edgeObj, conf);
    }

    removeNode(rdfNode) {
        const node = this.convertRDFNodeToEarlNode(rdfNode);
        if (node && node.getNumberOfIncidentEdges() === 0) {
            this.state.graph.removeNodes(node);
            this.state.draph.removeNode(node.id);
        }
    }

    removeTriple(rdfTriple) {
        const edge = this.convertRDFTripleToEarlEdge(rdfTriple);
        if (edge) {
            this.state.graph.removeEdges(edge);
            this.removeNode(rdfTriple.subject);
            this.removeNode(rdfTriple.object);
            this.state.draph.removeEdge(edge.id);
        }
    }

    removeTriples(triples) {
        dispatcher.waitFor([rdfToken]);
        for (let triple of triples) {
            this.removeTriple(triple);
        }
        this.__emitChange();
    }

    openStyle(stylesheet) {
        dispatcher.waitFor([rdfToken]);
        this.state.stylesheet = stylesheet;
        this.__emitChange();
    }

    loadedStyle() {
        dispatcher.waitFor([rdfToken]);
        this.state.draph.stopRenderLoop();
        this.state.draph = new GraphView(
            this.state.graph,
            this.state.stylesheet.computeAllStyles(
                rdfStore.getGraph(),
                rdfStore.getProfile()
            )
        );
        this.__emitChange();
    }

    selectNodes() {
        const selectedNodes = selectionStore.getSelectedNodes();
        const result = new Set();
        for (let node of selectedNodes) {
            if (node.interfaceName === "BlankNode" || node.interfaceName === "NamedNode") {
                result.add(node.toNT());
            }
        }
        this.state.draph.selectNodes(result);
    }

    selectTriples() {
        const selectedTriples = selectionStore.getSelectedTriples();
        const result = new Set();
        for (let triple of selectedTriples) {
            result.add(triple.id);
        }
        this.state.draph.selectEdges(result);
    }

    randomLayout(conf) {
        const random = new earl.RandomLayouter(conf);
        this.state.layout = random.layout(this.state.graph);
        this.state.draph.setLayout(this.state.layout);
    }

    eadesLayout(conf) {
        const eades = new earl.EadesLayouter(conf);
        this.state.layout = eades.layout(this.state.graph, this.state.layout);
        this.state.draph.setLayout(this.state.layout);
    }

    fruchtermanLayout(conf) {
        const fruchterman = new earl.FruchtermanLayouter(conf);
        this.state.layout = fruchterman.layout(this.state.graph, this.state.layout);
        this.state.draph.setLayout(this.state.layout);
    }

    /**
     * Handles the SCALE action.
     *
     * @param {number} factorX
     * The scalar to multiply the x-component of the vector by.
     *
     * @param {number} factorY
     * The scalar to multiply the y-component of the vector by.
     *
     * @param {Vec2} center
     * Where to start the scaling.
     *
     * @private
     */
    scaleLayout(factorX, factorY, center) {
        this.state.layout.scaleAll(factorX, factorY, center);
        this.state.draph.setLayout(this.state.layout);
    }

    /**
     * Handles the TRANSLATE action.
     *
     * @param {Vec2} vec
     * The vector to add to the current positions.
     *
     * @private
     */
    translateLayout(vec) {
        this.state.layout.moveAllBy(vec);
        this.state.draph.setLayout(this.state.layout);
    }

    /**
     * Handles the ROTATE action.
     *
     * @param {number} angle
     * How far to rotate.
     *
     * @param {Vec2} center
     * The point to rotate around.
     *
     * @private
     */
    rotateLayout(angle, center) {
        this.state.layout.rotateAll(angle, center);
        this.state.draph.setLayout(this.state.layout);
    }

    center() {
        this.state.draph.center();
    }

    filterTriples() {
        dispatcher.waitFor([selectionToken]);
        const nodesToKeep = new Set();
        const edgesToKeep = new Set();
        for (let triple of selectionStore.getFilteredGraph()) {
            nodesToKeep.add(triple.subject.toNT());
            nodesToKeep.add(triple.object.toNT());
            edgesToKeep.add(triple.id);
        }
        this.state.draph.filterGraph(nodesToKeep, edgesToKeep);
        this.__emitChange();
    }

    __onDispatch(action) {
        switch (action.type) {
            case "ADD_TRIPLE":
                dispatcher.waitFor([rdfToken]);
                this.addTriple(action.triple, true);
                this.__emitChange();
                break;
            case "REMOVE_TRIPLES":
                return this.removeTriples(action.triples);
            case "CLOSE":
                this.initState();
                this.__emitChange();
                break;
            case "OPEN_TURTLE":
                dispatcher.waitFor([rdfToken]);
                this.importRDFGraph();
                this.__emitChange();
                break;
            case "OPEN_STYLE":
                return this.openStyle(action.stylesheet);
            case "LOADED_STYLE":
                return this.loadedStyle();
            case "CLEAR_SELECTION":
                dispatcher.waitFor([selectionToken]);
                this.selectNodes();
                this.selectTriples();
                this.__emitChange();
                break;
            case "CLEAR_NODE_SELECTION":
            case "SELECT_NODES":
            case "DESELECT_NODES":
            case "TOGGLE_NODE_SELECTION":
                dispatcher.waitFor([selectionToken]);
                this.selectNodes();
                this.__emitChange();
                break;
            case "CLEAR_TRIPLE_SELECTION":
            case "SELECT_ALL_MATCHING_TRIPLES":
            case "SELECT_TRIPLES":
            case "DESELECT_TRIPLES":
            case "TOGGLE_TRIPLE_SELECTION":
                dispatcher.waitFor([selectionToken]);
                this.selectTriples();
                this.__emitChange();
                break;
            case "MOVE_NODE_TO_TOP":
                return this.state.draph.moveNodeToTop(action.nodeId);
            case "MOVE_EDGE_TO_TOP":
                return this.state.draph.moveEdgeToTop(action.edgeId);
            case "RANDOM_LAYOUT":
                return this.randomLayout(action.conf);
            case "EADES_LAYOUT":
                return this.eadesLayout(action.conf);
            case "FRUCHTERMAN_LAYOUT":
                return this.fruchtermanLayout(action.conf);
            case "SCALE":
                return this.scaleLayout(action.factorX, action.factorY, action.center);
            case "ROTATE":
                return this.rotateLayout(action.angle, action.center);
            case "TRANSLATE":
                return this.translateLayout(action.vec);
            case "CENTER":
                return this.center();
            case "FILTER_TRIPLES":
            case "CLEAR_TRIPLE_FILTER":
                return this.filterTriples();
        }
    }
}

export default new GraphStore(dispatcher);<commit_msg>Persisting layout<commit_after>import {Store} from "flux/utils";

import * as earl                        from "@ignavia/earl";
import {Tolkien1To1Map, Tolkien1ToNMap} from "@ignavia/util";
import * as rdf                         from "@ignavia/rdf";
import {Vec2}                           from "@ignavia/ella";
import {GraphView} from "@ignavia/draph";

import {Stylesheet} from "../utils/stylesheet/index.js";

import dispatcher from "../dispatcher/dispatcher.js";

import rdfStore from "./rdfStore.js";
import selectionStore from "./selectionStore.js";

const rdfToken  = rdfStore.getDispatchToken();
const selectionToken = selectionStore.getDispatchToken();

class GraphStore extends Store {
    constructor(dispatcher) {
        super(dispatcher);
        this.initialLayouter = new earl.RandomLayouter({
            width:  screen.width,
            height: screen.height
        });
        this.initState();
    }

    initState() {
        const graph      = new earl.Graph();
        const stylesheet = new Stylesheet();
        const layout     = this.initialLayouter.layout(graph);
        const conf       = stylesheet.computeAllStyles(
            rdfStore.getGraph(),
            rdfStore.getProfile()
        );
        conf.layout = layout;
        const draph = new GraphView(graph, conf);
        this.state = {
            graph,
            draph,
            layout,
            stylesheet
        };
    }

    getGraph() {
        return this.state.graph;
    }

    getDraph() {
        return this.state.draph;
    }

    convertEarlNodeToRDFNode(earlNode) {
        return rdf.RDFNode.fromNT(earlNode.id);
    }

    convertRDFNodeToEarlNode(rdfNode) {
        return this.state.graph.getNodeById(rdfNode.toNT());
    }

    convertEarlEdgeToRDFTriple(earlEdge) {
        return rdfStore.getGraph().getTripleById(earlEdge.id);
    }

    convertRDFTripleToEarlEdge(rdfTriple) {
        return this.state.graph.getEdgeById(rdfTriple.id);
    }

    getState() {
        return this.state;
    }

    importRDFGraph() {
        this.initState();

        for (let triple of rdfStore.state.graph) {
            this.addTriple(triple);
        }

        this.state.layout = this.initialLayouter.layout(this.state.graph);
        const conf = this.state.stylesheet.computeAllStyles(
            rdfStore.getGraph(),
            rdfStore.getProfile()
        );
        conf.layout = this.state.layout;
        console.log(conf)
        this.state.draph  = new GraphView(this.state.graph, conf);
    }

    addNode(id, addToDraph = false) {
        if (!this.state.graph.getNodeById(id)) {
            const node = new earl.Node(id);
            this.state.graph.addNodes(node);
            if (addToDraph) {
                this.addNodeToDraph(node);
            }
        }
    }

    addNodeToDraph(nodeObj) {
        const conf = this.state.stylesheet.computeNodeStyle(
            rdfStore.getGraph(),
            rdfStore.getProfile(),
            nodeObj.id
        );
        this.state.draph.addNode(nodeObj, conf);
        const nodeG = this.state.draph.getNodeDisplayObjectById(nodeObj.id);
        this.state.layout.moveNodeTo(nodeObj, new Vec2(nodeG.x, nodeG.y));
    }

    addTriple({subject, object, id}, addToDraph = false) {
        const subjectHash = subject.toNT();
        this.addNode(subjectHash, addToDraph);

        if (!object.isLiteral()) {
            const objectHash = object.toNT();
            this.addNode(objectHash, addToDraph);

            const edge = new earl.Edge(subjectHash, objectHash, id);
            this.state.graph.addEdges(edge);
            if (addToDraph) {
                this.addEdgeToDraph(edge);
            }
        }
    }

    addEdgeToDraph(edgeObj) {
        const conf = this.state.stylesheet.computeEdgeStyle(
            rdfStore.getGraph(),
            rdfStore.getProfile(),
            edgeObj.id
        );
        this.state.draph.addEdge(edgeObj, conf);
    }

    removeNode(rdfNode) {
        const node = this.convertRDFNodeToEarlNode(rdfNode);
        if (node && node.getNumberOfIncidentEdges() === 0) {
            this.state.graph.removeNodes(node);
            this.state.draph.removeNode(node.id);
        }
    }

    removeTriple(rdfTriple) {
        const edge = this.convertRDFTripleToEarlEdge(rdfTriple);
        if (edge) {
            this.state.graph.removeEdges(edge);
            this.removeNode(rdfTriple.subject);
            this.removeNode(rdfTriple.object);
            this.state.draph.removeEdge(edge.id);
        }
    }

    removeTriples(triples) {
        dispatcher.waitFor([rdfToken]);
        for (let triple of triples) {
            this.removeTriple(triple);
        }
        this.__emitChange();
    }

    openStyle(stylesheet) {
        dispatcher.waitFor([rdfToken]);
        this.state.stylesheet = stylesheet;
        this.__emitChange();
    }

    loadedStyle() {
        dispatcher.waitFor([rdfToken]);
        this.state.draph.stopRenderLoop();
        const conf = this.state.stylesheet.computeAllStyles(
            rdfStore.getGraph(),
            rdfStore.getProfile()
        );
        conf.layout = this.state.layout;
        this.state.draph = new GraphView(this.state.graph, conf);
        this.__emitChange();
    }

    selectNodes() {
        const selectedNodes = selectionStore.getSelectedNodes();
        const result = new Set();
        for (let node of selectedNodes) {
            if (node.interfaceName === "BlankNode" || node.interfaceName === "NamedNode") {
                result.add(node.toNT());
            }
        }
        this.state.draph.selectNodes(result);
    }

    selectTriples() {
        const selectedTriples = selectionStore.getSelectedTriples();
        const result = new Set();
        for (let triple of selectedTriples) {
            result.add(triple.id);
        }
        this.state.draph.selectEdges(result);
    }

    randomLayout(conf) {
        const random = new earl.RandomLayouter(conf);
        this.state.layout = random.layout(this.state.graph);
        this.state.draph.setLayout(this.state.layout);
    }

    eadesLayout(conf) {
        const eades = new earl.EadesLayouter(conf);
        this.state.layout = eades.layout(this.state.graph, this.state.layout);
        this.state.draph.setLayout(this.state.layout);
    }

    fruchtermanLayout(conf) {
        const fruchterman = new earl.FruchtermanLayouter(conf);
        this.state.layout = fruchterman.layout(this.state.graph, this.state.layout);
        this.state.draph.setLayout(this.state.layout);
    }

    /**
     * Handles the SCALE action.
     *
     * @param {number} factorX
     * The scalar to multiply the x-component of the vector by.
     *
     * @param {number} factorY
     * The scalar to multiply the y-component of the vector by.
     *
     * @param {Vec2} center
     * Where to start the scaling.
     *
     * @private
     */
    scaleLayout(factorX, factorY, center) {
        this.state.layout.scaleAll(factorX, factorY, center);
        this.state.draph.setLayout(this.state.layout);
    }

    /**
     * Handles the TRANSLATE action.
     *
     * @param {Vec2} vec
     * The vector to add to the current positions.
     *
     * @private
     */
    translateLayout(vec) {
        this.state.layout.moveAllBy(vec);
        this.state.draph.setLayout(this.state.layout);
    }

    /**
     * Handles the ROTATE action.
     *
     * @param {number} angle
     * How far to rotate.
     *
     * @param {Vec2} center
     * The point to rotate around.
     *
     * @private
     */
    rotateLayout(angle, center) {
        this.state.layout.rotateAll(angle, center);
        this.state.draph.setLayout(this.state.layout);
    }

    center() {
        this.state.draph.center();
    }

    filterTriples() {
        dispatcher.waitFor([selectionToken]);
        const nodesToKeep = new Set();
        const edgesToKeep = new Set();
        for (let triple of selectionStore.getFilteredGraph()) {
            nodesToKeep.add(triple.subject.toNT());
            nodesToKeep.add(triple.object.toNT());
            edgesToKeep.add(triple.id);
        }
        this.state.draph.filterGraph(nodesToKeep, edgesToKeep);
        this.__emitChange();
    }

    __onDispatch(action) {
        switch (action.type) {
            case "ADD_TRIPLE":
                dispatcher.waitFor([rdfToken]);
                this.addTriple(action.triple, true);
                this.__emitChange();
                break;
            case "REMOVE_TRIPLES":
                return this.removeTriples(action.triples);
            case "CLOSE":
                this.initState();
                this.__emitChange();
                break;
            case "OPEN_TURTLE":
                dispatcher.waitFor([rdfToken]);
                this.importRDFGraph();
                this.__emitChange();
                break;
            case "OPEN_STYLE":
                return this.openStyle(action.stylesheet);
            case "LOADED_STYLE":
                return this.loadedStyle();
            case "CLEAR_SELECTION":
                dispatcher.waitFor([selectionToken]);
                this.selectNodes();
                this.selectTriples();
                this.__emitChange();
                break;
            case "CLEAR_NODE_SELECTION":
            case "SELECT_NODES":
            case "DESELECT_NODES":
            case "TOGGLE_NODE_SELECTION":
                dispatcher.waitFor([selectionToken]);
                this.selectNodes();
                this.__emitChange();
                break;
            case "CLEAR_TRIPLE_SELECTION":
            case "SELECT_ALL_MATCHING_TRIPLES":
            case "SELECT_TRIPLES":
            case "DESELECT_TRIPLES":
            case "TOGGLE_TRIPLE_SELECTION":
                dispatcher.waitFor([selectionToken]);
                this.selectTriples();
                this.__emitChange();
                break;
            case "MOVE_NODE_TO_TOP":
                return this.state.draph.moveNodeToTop(action.nodeId);
            case "MOVE_EDGE_TO_TOP":
                return this.state.draph.moveEdgeToTop(action.edgeId);
            case "RANDOM_LAYOUT":
                return this.randomLayout(action.conf);
            case "EADES_LAYOUT":
                return this.eadesLayout(action.conf);
            case "FRUCHTERMAN_LAYOUT":
                return this.fruchtermanLayout(action.conf);
            case "SCALE":
                return this.scaleLayout(action.factorX, action.factorY, action.center);
            case "ROTATE":
                return this.rotateLayout(action.angle, action.center);
            case "TRANSLATE":
                return this.translateLayout(action.vec);
            case "CENTER":
                return this.center();
            case "FILTER_TRIPLES":
            case "CLEAR_TRIPLE_FILTER":
                return this.filterTriples();
        }
    }
}

export default new GraphStore(dispatcher);<|endoftext|>


<commit_before>/*
 * This file is to be included by beaver contest tasks, it defines a basic
 * implementation of the main functions of the task object, as well as a grader.
 *
 * Task can overwrite these definitions.
 *
 */

/************************************************************************
 * Untouched parts from beaver-task-1.0.
 ************************************************************************/

var task = {};

task.showViews = function(views, callback) {
   if (views.forum || views.hint || views.editor) {
      //console.error("this task does not have forum, hint nor editor specific view, showing task view instead.");
      views.task = true;
   }
   $.each(['task', 'solution'], function(i, view) {
      if (views[view]) {
         $('#'+view).show();
      } else {
         $('#'+view).hide();
      }
   });
   if (typeof task.hackShowViews === 'function') {task.hackShowViews(views);}
   callback();
};

task.getViews = function(callback) {
    // all beaver tasks have the same views
    var views = {
        task: {},
        solution: {},
        hints: {requires: "task"},
        forum: {requires: "task"},
        editor: {requires: "task"},
        submission: {requires: "task"}
    };
    callback(views);
};

task.updateToken = function(token, callback) {
   callback();
};

task.getHeight = function(callback) {
   callback(parseInt($("html").outerHeight(true)));
};

task.getMetaData = function(callback) {
   if (typeof json !== 'undefined') {
      callback(json);
   } else {
      callback({nbHints:0});
   }
};

/************************************************************************
 * Updated parts in beaver-task-2.0.
 ************************************************************************/

// TODO We update the grader below, if the task has levels. Is this line necessary?
var grader = grader ? grader : {};

function initWrapper(initSubTask, levels, defaultLevel, reloadWithCallbacks) {
   // Create a subTask instance, possibly operating on an existing object.
   function createTask(displayFlag) {
      var subTask = {};
      subTask.delayFactory = new DelayFactory();
      subTask.raphaelFactory = new RaphaelFactory();

      // Simulation factory needs a specific corresponding delay factory.
      // TODO should it expect something else? subTask? A list of factories?
      subTask.simulationFactory = new SimulationFactory(subTask.delayFactory);

      subTask.display = displayFlag;
      initSubTask(subTask);
      return subTask;
   }

   // Destroy a subTask instance.
   function destroyTask(subTask, callback) {
      var doUnload = function() {
         // Order is important.
         subTask.raphaelFactory.destroyAll();
         subTask.simulationFactory.destroyAll();
         subTask.delayFactory.destroyAll();
         if(callback && typeof callback === "function") {
            callback();
         }
      };
      if(levels) {
         subTask.unloadLevel(doUnload);
      }
      else {
         // TODO Can we assume non-level tasks will imeplement 'unload'?
         subTask.unload(doUnload);
      }
   }

   // Invoke a function for each level, and wait for callback.
   // When done, invoke finalCallback (optional).
   function callbackLoop(array, itemCallback, finalCallback) {
      var index = 0;
      function innerLoop() {
         if(index >= array.length) {
            if(finalCallback) {
               finalCallback();
            }
            return;
         }
         itemCallback(array[index], function() {
            index++;
            innerLoop();
         });
      }
      innerLoop();
   }

   // Main subTask instance, for user display.
   var mainTask;
   var mainTaskParams;

   // The state of the task, including current level and levelState for each level.
   var state = null;

   // Instances of subTask intended for grading.
   var gradingTasks = {};

   task.load = function(views, callback) {
      platform.getTaskParams(null, null, function(taskParams) {
         mainTask = createTask(true);
         mainTask.taskParams = taskParams;
         mainTaskParams = taskParams;
         task.displayedSubTask = mainTask;
         if(levels) {
            // TODO okay to assume default level is the first level, if not supplied?
            if(defaultLevel === null || defaultLevel === undefined) {
               defaultLevel = levels[0];
            }

            // The objects levelAnswers and levelStates are indexed by level names.
            state = {
               levelAnswers: {},
               levelStates: {},
               level: defaultLevel
            };
            mainTask.loadLevel(state.level, null, views);
            displayHelper.setupLevels(null, reloadWithCallbacks);
            callback();
         }
         else {
            // TODO what's the desired behavior when no levels?
            mainTask.load(views, callback);
         }
      });
   };

   task.getState = function(callback) {
      if(levels) {
         // Update state to reflect latest user interaction.
         state.levelStates[state.level] = mainTask.getStateObject();
         state.levelAnswers[state.level] = mainTask.getAnswerObject();
         callback(JSON.stringify(state));
      }
      else {
         // TODO is this the desired behavior? It is from beaver-task-1.0.
         var currentState = {};
         mainTask.getAnswer(function(displayedAnswer) {
            currentState.displayedAnswer = displayedAnswer;
            callback(JSON.stringify(currentState));
         });
      }
   };

   task.getStateObject = function() {
      state.levelStates[state.level] = mainTask.getStateObject();
      state.levelAnswers[state.level] = mainTask.getAnswerObject();
      return state;
   };

   task.reloadAnswer = function(strAnswer, callback) {
      var newAnswer;
      if(strAnswer && strAnswer !== '') {
         newAnswer = JSON.parse(strAnswer);
      }
      else {
         newAnswer = null;
      }
      task.reloadAnswerObject(newAnswer, callback);
   };

   task.reloadAnswerObject = function(newAnswers, callback) {
      if(levels) {
         if (!newAnswers) {
            state.levelAnswers = {};
         }
         else {
            state.levelAnswers = newAnswers;
         }

         // Recreate the main task and load the relevant answer.
         var level = state.level;
         var levelAnswer = state.levelAnswers[level];
         if(levelAnswer === undefined || levelAnswer === null) {
            levelAnswer = mainTask.getDefaultAnswerObject();
            state.levelAnswers[level] = levelAnswer;
         }

         state.levelStates[state.level] = mainTask.getStateObject();
         var levelState = state.levelStates[level];
         if(mainTask.getDefaultStateObject && (levelState === undefined || levelState === null)) {
            levelState = mainTask.getDefaultStateObject();
            state.levelStates[level] = levelState;
         }

         destroyTask(mainTask, function() {
            mainTask = createTask(true);
            mainTask.taskParams = mainTaskParams;
            task.displayedSubTask = mainTask;
            mainTask.loadLevel(level, levelState);
            mainTask.reloadAnswerObject(levelAnswer);
            if(mainTask.resetDisplay) {
               mainTask.resetDisplay();
            }
            if(callback && typeof callback === "function") {
               callback();
            }
         });
      }
      else {
         // TODO is this the desired behavior? Taken from beaver-task-1.0.
         if (!newAnswers) {
            mainTask.reloadAnswerObject(mainTask.getDefaultAnswerObject());
         } else {
            mainTask.reloadAnswerObject(newAnswers);
         }
         if(callback && typeof callback === "function") {
            callback();
         }
      }
   };

   task.reloadState = function(newStateStr, callback) {
      if (newStateStr) {
         task.reloadStateObject(JSON.parse(newStateStr), callback);
      } else {
         task.reloadStateObject(task.getDefaultStateObject(), callback);
      }
   };

   task.reloadStateObject = function(newState, callback) {
      if(levels) {
         // Recreate the task to reflect the new state.
         state = newState;
         var level = state.level;
         var levelState = state.levelStates[level];
         destroyTask(mainTask, function() {
            mainTask = createTask(true);
            mainTask.taskParams = mainTaskParams;
            task.displayedSubTask = mainTask;
            mainTask.loadLevel(level, levelState);
            mainTask.reloadAnswerObject(state.levelAnswers[level]);
            if(callback && typeof callback === "function") {
               callback();
            }
         });
      }
      else {
         // TODO is this the desired behavior? Taken from beaver-task-1.0.
         if (typeof newState.displayedAnswer !== 'undefined') {
            mainTask.reloadAnswer(newState.displayedAnswer, callback);
         }
         else {
            if(callback && typeof callback === "function") {
               callback();
            }
         }
      }
   };

   task.getDefaultStateObject = function() {
      return {
         level: defaultLevel,
         levelStates: {},
         levelAnswers: {}
      };
   };

   task.getDefaultAnswerObject = function() {
      return {};
   };

   task.getAnswer = function(callback) {
      if(levels) {
         // Update answer to reflect latest user interaction.
         state.levelAnswers[state.level] = mainTask.getAnswerObject();
         callback(JSON.stringify(state.levelAnswers));
      }
      else {
         // TODO is this desired behavior? Taken from beaver-task-1.
         var answerObj = mainTask.getAnswerObject();
         callback(JSON.stringify(answerObj));
      }
   };

   task.getAnswerObject = function() {
      if(levels) {
         state.levelAnswers[state.level] = mainTask.getAnswerObject();
         return state.levelAnswers;
      }
      else {
         return mainTask.getAnswerObject();
      }
   };

   task.unload = function(callback) {
      var instances = [];
      if (levels) {
         for(var iLevel in gradingTasks) {
            for(var iSeed in gradingTasks[iLevel]) {
               instances.push(gradingTasks[iLevel][iSeed]);
            }
         }
      } else {
         for(var iSeed in gradingTasks) {
            instances.push(gradingTasks[iSeed]);
         }
      }
      instances.push(mainTask);
      callbackLoop(instances, function(subTask, loopCallback) {
         destroyTask(subTask, loopCallback);
      }, function() {
         task.displayedSubTask = null;
         callback();
      });

      if (Blockly != undefined) { // TEMPORARY, to replace with a global unload function provided by the task
         removeBlockly();
      }
   };

   function gradeAnswerInner(gradingTask, answer, maxScore, callback) {
      gradingTask.isGrading = true;
      if(answer === undefined || answer === null) {
         answer = gradingTask.getDefaultAnswerObject();
      }
      gradingTask.reloadAnswerObject(answer);
      gradingTask.getGrade(function(result) {
         gradingTask.isGrading = false;
         callback({
            score: Math.round(result.successRate * maxScore),
            message: result.message
         });
      });
   }

   function gradeAnswerByLevel(taskParams, level, seed, levelAnswer, maxScore, callback) {
      var gradingTask;

      if(!gradingTasks[level]) {
         gradingTasks[level] = {};
      }
      // Create new instance to be kept in gradingTasks array, and use it for this grading.
      if(!gradingTasks[level][seed]) {
         gradingTask = createTask(false);
         gradingTask.taskParams = taskParams;
         gradingTask.isGrading = false;
         gradingTask.loadLevel(level);
         gradingTasks[level][seed] = gradingTask;
         gradeAnswerInner(gradingTask, levelAnswer, maxScore, callback);
      }
      // Current gradingTasks[level][seed] instance is busy, so create a temporary instance,
      // to be destroyed immediately after grading.
      else if(gradingTasks[level][seed].isGrading) {
         gradingTask = createTask(false);
         gradingTask.taskParams = taskParams;
         gradingTask.isGrading = false;
         gradingTask.loadLevel(level);
         gradeAnswerInner(gradingTask, answer, maxScore, function(result) {
            destroyTask(gradingTask, function() {
               callback(result);
            });
         });
      }
      // Current gradingTasks[level][seed] instance is not busy, use it.
      else {
         gradingTasks[level][seed].taskParams = taskParams;
         gradeAnswerInner(gradingTasks[level][seed], levelAnswer, maxScore, callback);
      }
   }

   function gradeAnswerNoLevels(taskParams, seed, answer, maxScore, callback) {
      var gradingTask;

      // Grade a task instance.
      var doGrading = function() {
         gradeAnswerInner(gradingTask, answer, maxScore, callback);
      };

      // Grade a task instance and destroy it. Invoke callback after destruction.
      var doGradingAndDestroy = function() {
         gradeAnswerInner(gradingTask, answer, maxScore, function(result) {
            destroyTask(gradingTask, function() {
               callback(result);
            });
         });
      };

      // Create new instance to be kept in gradingTasks array, and use it for this grading.
      if(!gradingTasks[seed]) {
         gradingTask = createTask(false);
         gradingTask.taskParams = taskParams;
         gradingTasks[seed] = gradingTask;
         gradingTask.load(null, doGrading);
      }
      // Current gradingTasks[seed] instance is busy, so create a temporary instance,
      // to be destroyed immediately after grading.
      else if(gradingTasks[seed].isGrading) {
         gradingTask = createTask(false);
         gradingTask.taskParams = taskParams;
         gradingTask.load(null, doGradingAndDestroy);
      }
      // Current gradingTasks[seed] instance is not busy, use it.
      else {
         gradingTask = gradingTasks[seed];
         gradingTask.taskParams = taskParams;
         doGrading();
      }
   }

   task.gradeAnswer = function(strAnswer, answerToken, callback) {
      task.getLevelGrade(strAnswer, answerToken, callback, null);
   };

   // TODO: case where gradeAnswer is called again before it calls its callback
   task.getLevelGrade = function(strAnswer, answerToken, callback, gradedLevel) {
      // TODO Can we fetch task params just once instead of every time?
      // If we can, then why do we need to index by seed in graders[level][seed]?
      platform.getTaskParams(null, null, function(taskParams) {
         if (strAnswer === '') {
            callback(taskParams.minScore, '');
            return;
         }

         var seed = taskParams.randomSeed;
         var parsedAnswer = $.parseJSON(strAnswer);

         if(levels) {
            var maxScores = displayHelper.getLevelsMaxScores();
            var levelAnswers = parsedAnswer;
            var scores = {};
            var messages = {};

            callbackLoop(levels, function(level, loopCallback) {
               if(gradedLevel !== null && gradedLevel !== undefined && level !== gradedLevel) {
                  loopCallback();
                  return;
               }
               gradeAnswerByLevel(taskParams, level, seed, levelAnswers[level], maxScores[level], function(result) {
                  scores[level] = result.score;
                  messages[level] = result.message;
                  loopCallback();
               });
            },
            function() {
               // TODO is this correct? Taken from Arthur's level wrapper.
               if(gradedLevel === null || gradedLevel === undefined) {
                  displayHelper.sendBestScore(callback, scores, messages);
               } else {
                  callback(scores[gradedLevel], messages[gradedLevel]);
               }
            });
         }
         else {
            gradeAnswerNoLevels(taskParams, seed, parsedAnswer, taskParams.maxScore, function(result) {
               callback(result.score, result.message);
            });
         }
      });
   };

   // TODO is this the correct behavior?
   grader.gradeTask = task.gradeAnswer;
   task.gradeTask = grader.gradeTask;
}

$('document').ready(function() {
   platform.initWithTask(window.task);
});
<commit_msg>Added a score range to each level.<commit_after>/*
 * This file is to be included by beaver contest tasks, it defines a basic
 * implementation of the main functions of the task object, as well as a grader.
 *
 * Task can overwrite these definitions.
 *
 */

/************************************************************************
 * Untouched parts from beaver-task-1.0.
 ************************************************************************/

var task = {};

task.showViews = function(views, callback) {
   if (views.forum || views.hint || views.editor) {
      //console.error("this task does not have forum, hint nor editor specific view, showing task view instead.");
      views.task = true;
   }
   $.each(['task', 'solution'], function(i, view) {
      if (views[view]) {
         $('#'+view).show();
      } else {
         $('#'+view).hide();
      }
   });
   if (typeof task.hackShowViews === 'function') {task.hackShowViews(views);}
   callback();
};

task.getViews = function(callback) {
    // all beaver tasks have the same views
    var views = {
        task: {},
        solution: {},
        hints: {requires: "task"},
        forum: {requires: "task"},
        editor: {requires: "task"},
        submission: {requires: "task"}
    };
    callback(views);
};

task.updateToken = function(token, callback) {
   callback();
};

task.getHeight = function(callback) {
   callback(parseInt($("html").outerHeight(true)));
};

task.getMetaData = function(callback) {
   if (typeof json !== 'undefined') {
      callback(json);
   } else {
      callback({nbHints:0});
   }
};

/************************************************************************
 * Updated parts in beaver-task-2.0.
 ************************************************************************/

// TODO We update the grader below, if the task has levels. Is this line necessary?
var grader = grader ? grader : {};

function initWrapper(initSubTask, levels, defaultLevel, reloadWithCallbacks) {
   // Create a subTask instance, possibly operating on an existing object.
   function createTask(displayFlag) {
      var subTask = {};
      subTask.delayFactory = new DelayFactory();
      subTask.raphaelFactory = new RaphaelFactory();

      // Simulation factory needs a specific corresponding delay factory.
      // TODO should it expect something else? subTask? A list of factories?
      subTask.simulationFactory = new SimulationFactory(subTask.delayFactory);

      subTask.display = displayFlag;
      initSubTask(subTask);
      return subTask;
   }

   // Destroy a subTask instance.
   function destroyTask(subTask, callback) {
      var doUnload = function() {
         // Order is important.
         subTask.raphaelFactory.destroyAll();
         subTask.simulationFactory.destroyAll();
         subTask.delayFactory.destroyAll();
         if(callback && typeof callback === "function") {
            callback();
         }
      };
      if(levels) {
         subTask.unloadLevel(doUnload);
      }
      else {
         // TODO Can we assume non-level tasks will imeplement 'unload'?
         subTask.unload(doUnload);
      }
   }

   // Invoke a function for each level, and wait for callback.
   // When done, invoke finalCallback (optional).
   function callbackLoop(array, itemCallback, finalCallback) {
      var index = 0;
      function innerLoop() {
         if(index >= array.length) {
            if(finalCallback) {
               finalCallback();
            }
            return;
         }
         itemCallback(array[index], function() {
            index++;
            innerLoop();
         });
      }
      innerLoop();
   }

   // Main subTask instance, for user display.
   var mainTask;
   var mainTaskParams;

   // The state of the task, including current level and levelState for each level.
   var state = null;

   // Instances of subTask intended for grading.
   var gradingTasks = {};

   task.load = function(views, callback) {
      platform.getTaskParams(null, null, function(taskParams) {
         mainTask = createTask(true);
         mainTask.taskParams = taskParams;
         mainTaskParams = taskParams;
         task.displayedSubTask = mainTask;
         if(levels) {
            // TODO okay to assume default level is the first level, if not supplied?
            if(defaultLevel === null || defaultLevel === undefined) {
               defaultLevel = levels[0];
            }

            // The objects levelAnswers and levelStates are indexed by level names.
            state = {
               levelAnswers: {},
               levelStates: {},
               level: defaultLevel
            };
            mainTask.loadLevel(state.level, null, views);
            displayHelper.setupLevels(null, reloadWithCallbacks);
            callback();
         }
         else {
            // TODO what's the desired behavior when no levels?
            mainTask.load(views, callback);
         }
      });
   };

   task.getState = function(callback) {
      if(levels) {
         // Update state to reflect latest user interaction.
         state.levelStates[state.level] = mainTask.getStateObject();
         state.levelAnswers[state.level] = mainTask.getAnswerObject();
         callback(JSON.stringify(state));
      }
      else {
         // TODO is this the desired behavior? It is from beaver-task-1.0.
         var currentState = {};
         mainTask.getAnswer(function(displayedAnswer) {
            currentState.displayedAnswer = displayedAnswer;
            callback(JSON.stringify(currentState));
         });
      }
   };

   task.getStateObject = function() {
      state.levelStates[state.level] = mainTask.getStateObject();
      state.levelAnswers[state.level] = mainTask.getAnswerObject();
      return state;
   };

   task.reloadAnswer = function(strAnswer, callback) {
      var newAnswer;
      if(strAnswer && strAnswer !== '') {
         newAnswer = JSON.parse(strAnswer);
      }
      else {
         newAnswer = null;
      }
      task.reloadAnswerObject(newAnswer, callback);
   };

   task.reloadAnswerObject = function(newAnswers, callback) {
      if(levels) {
         if (!newAnswers) {
            state.levelAnswers = {};
         }
         else {
            state.levelAnswers = newAnswers;
         }

         // Recreate the main task and load the relevant answer.
         var level = state.level;
         var levelAnswer = state.levelAnswers[level];
         if(levelAnswer === undefined || levelAnswer === null) {
            levelAnswer = mainTask.getDefaultAnswerObject();
            state.levelAnswers[level] = levelAnswer;
         }

         state.levelStates[state.level] = mainTask.getStateObject();
         var levelState = state.levelStates[level];
         if(mainTask.getDefaultStateObject && (levelState === undefined || levelState === null)) {
            levelState = mainTask.getDefaultStateObject();
            state.levelStates[level] = levelState;
         }

         destroyTask(mainTask, function() {
            mainTask = createTask(true);
            mainTask.taskParams = mainTaskParams;
            task.displayedSubTask = mainTask;
            mainTask.loadLevel(level, levelState);
            mainTask.reloadAnswerObject(levelAnswer);
            if(mainTask.resetDisplay) {
               mainTask.resetDisplay();
            }
            if(callback && typeof callback === "function") {
               callback();
            }
         });
      }
      else {
         // TODO is this the desired behavior? Taken from beaver-task-1.0.
         if (!newAnswers) {
            mainTask.reloadAnswerObject(mainTask.getDefaultAnswerObject());
         } else {
            mainTask.reloadAnswerObject(newAnswers);
         }
         if(callback && typeof callback === "function") {
            callback();
         }
      }
   };

   task.reloadState = function(newStateStr, callback) {
      if (newStateStr) {
         task.reloadStateObject(JSON.parse(newStateStr), callback);
      } else {
         task.reloadStateObject(task.getDefaultStateObject(), callback);
      }
   };

   task.reloadStateObject = function(newState, callback) {
      if(levels) {
         // Recreate the task to reflect the new state.
         state = newState;
         var level = state.level;
         var levelState = state.levelStates[level];
         destroyTask(mainTask, function() {
            mainTask = createTask(true);
            mainTask.taskParams = mainTaskParams;
            task.displayedSubTask = mainTask;
            mainTask.loadLevel(level, levelState);
            mainTask.reloadAnswerObject(state.levelAnswers[level]);
            if(callback && typeof callback === "function") {
               callback();
            }
         });
      }
      else {
         // TODO is this the desired behavior? Taken from beaver-task-1.0.
         if (typeof newState.displayedAnswer !== 'undefined') {
            mainTask.reloadAnswer(newState.displayedAnswer, callback);
         }
         else {
            if(callback && typeof callback === "function") {
               callback();
            }
         }
      }
   };

   task.getDefaultStateObject = function() {
      return {
         level: defaultLevel,
         levelStates: {},
         levelAnswers: {}
      };
   };

   task.getDefaultAnswerObject = function() {
      return {};
   };

   task.getAnswer = function(callback) {
      if(levels) {
         // Update answer to reflect latest user interaction.
         state.levelAnswers[state.level] = mainTask.getAnswerObject();
         callback(JSON.stringify(state.levelAnswers));
      }
      else {
         // TODO is this desired behavior? Taken from beaver-task-1.
         var answerObj = mainTask.getAnswerObject();
         callback(JSON.stringify(answerObj));
      }
   };

   task.getAnswerObject = function() {
      if(levels) {
         state.levelAnswers[state.level] = mainTask.getAnswerObject();
         return state.levelAnswers;
      }
      else {
         return mainTask.getAnswerObject();
      }
   };

   task.unload = function(callback) {
      var instances = [];
      if (levels) {
         for(var iLevel in gradingTasks) {
            for(var iSeed in gradingTasks[iLevel]) {
               instances.push(gradingTasks[iLevel][iSeed]);
            }
         }
      } else {
         for(var iSeed in gradingTasks) {
            instances.push(gradingTasks[iSeed]);
         }
      }
      instances.push(mainTask);
      callbackLoop(instances, function(subTask, loopCallback) {
         destroyTask(subTask, loopCallback);
      }, function() {
         task.displayedSubTask = null;
         callback();
      });

      if (Blockly != undefined) { // TEMPORARY, to replace with a global unload function provided by the task
         removeBlockly();
      }
   };

   function gradeAnswerInner(gradingTask, answer, minScore, maxScore, callback) {
      gradingTask.isGrading = true;
      if(answer === undefined || answer === null) {
         answer = gradingTask.getDefaultAnswerObject();
      }
      gradingTask.reloadAnswerObject(answer);
      gradingTask.getGrade(function(result) {
         gradingTask.isGrading = false;
         var score = 0;
         if(result.successRate > 0) {
            score = Math.round(result.successRate * (maxScore - minScore) + minScore);
         }
         callback({
            score: score,
            message: result.message
         });
      });
   }

   function gradeAnswerByLevel(taskParams, level, seed, levelAnswer, minScore, maxScore, callback) {
      var gradingTask;

      if(!gradingTasks[level]) {
         gradingTasks[level] = {};
      }
      // Create new instance to be kept in gradingTasks array, and use it for this grading.
      if(!gradingTasks[level][seed]) {
         gradingTask = createTask(false);
         gradingTask.taskParams = taskParams;
         gradingTask.isGrading = false;
         gradingTask.loadLevel(level);
         gradingTasks[level][seed] = gradingTask;
         gradeAnswerInner(gradingTask, levelAnswer, minScore, maxScore, callback);
      }
      // Current gradingTasks[level][seed] instance is busy, so create a temporary instance,
      // to be destroyed immediately after grading.
      else if(gradingTasks[level][seed].isGrading) {
         gradingTask = createTask(false);
         gradingTask.taskParams = taskParams;
         gradingTask.isGrading = false;
         gradingTask.loadLevel(level);
         gradeAnswerInner(gradingTask, answer, minScore, maxScore, function(result) {
            destroyTask(gradingTask, function() {
               callback(result);
            });
         });
      }
      // Current gradingTasks[level][seed] instance is not busy, use it.
      else {
         gradingTasks[level][seed].taskParams = taskParams;
         gradeAnswerInner(gradingTasks[level][seed], levelAnswer, minScore, maxScore, callback);
      }
   }

   function gradeAnswerNoLevels(taskParams, seed, answer, minScore, maxScore, callback) {
      var gradingTask;

      // Grade a task instance.
      var doGrading = function() {
         gradeAnswerInner(gradingTask, answer, minScore, maxScore, callback);
      };

      // Grade a task instance and destroy it. Invoke callback after destruction.
      var doGradingAndDestroy = function() {
         gradeAnswerInner(gradingTask, answer, minScore, maxScore, function(result) {
            destroyTask(gradingTask, function() {
               callback(result);
            });
         });
      };

      // Create new instance to be kept in gradingTasks array, and use it for this grading.
      if(!gradingTasks[seed]) {
         gradingTask = createTask(false);
         gradingTask.taskParams = taskParams;
         gradingTasks[seed] = gradingTask;
         gradingTask.load(null, doGrading);
      }
      // Current gradingTasks[seed] instance is busy, so create a temporary instance,
      // to be destroyed immediately after grading.
      else if(gradingTasks[seed].isGrading) {
         gradingTask = createTask(false);
         gradingTask.taskParams = taskParams;
         gradingTask.load(null, doGradingAndDestroy);
      }
      // Current gradingTasks[seed] instance is not busy, use it.
      else {
         gradingTask = gradingTasks[seed];
         gradingTask.taskParams = taskParams;
         doGrading();
      }
   }

   task.gradeAnswer = function(strAnswer, answerToken, callback) {
      task.getLevelGrade(strAnswer, answerToken, callback, null);
   };

   // TODO: case where gradeAnswer is called again before it calls its callback
   task.getLevelGrade = function(strAnswer, answerToken, callback, gradedLevel) {
      // TODO Can we fetch task params just once instead of every time?
      // If we can, then why do we need to index by seed in graders[level][seed]?
      platform.getTaskParams(null, null, function(taskParams) {
         if (strAnswer === '') {
            callback(taskParams.minScore, '');
            return;
         }

         var seed = taskParams.randomSeed;
         var parsedAnswer = $.parseJSON(strAnswer);

         if(levels) {
            var maxScores = displayHelper.getLevelsMaxScores();
            var minScores = {
               easy: 0,
               medium: maxScores.easy,
               hard: maxScores.medium
            };
            var levelAnswers = parsedAnswer;
            var scores = {};
            var messages = {};

            callbackLoop(levels, function(level, loopCallback) {
               if(gradedLevel !== null && gradedLevel !== undefined && level !== gradedLevel) {
                  loopCallback();
                  return;
               }
               gradeAnswerByLevel(taskParams, level, seed, levelAnswers[level], minScores[level], maxScores[level], function(result) {
                  scores[level] = result.score;
                  messages[level] = result.message;
                  loopCallback();
               });
            },
            function() {
               // TODO is this correct? Taken from Arthur's level wrapper.
               if(gradedLevel === null || gradedLevel === undefined) {
                  displayHelper.sendBestScore(callback, scores, messages);
               } else {
                  callback(scores[gradedLevel], messages[gradedLevel]);
               }
            });
         }
         else {
            gradeAnswerNoLevels(taskParams, seed, parsedAnswer, taskParam.minScore, taskParams.maxScore, function(result) {
               callback(result.score, result.message);
            });
         }
      });
   };

   // TODO is this the correct behavior?
   grader.gradeTask = task.gradeAnswer;
   task.gradeTask = grader.gradeTask;
}

$('document').ready(function() {
   platform.initWithTask(window.task);
});
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { Alert, Form, FormGroup, FormFeedback, Input } from 'reactstrap'
import { Field, FieldArray, reduxForm } from 'redux-form'
import { Tooltip } from 'reactstrap';
import ButtonConfirmAction from '../ButtonConfirmAction'
import { toSafeInteger, without } from 'lodash'

const MIN_PORT = 1
const MAX_PORT = 65535

const validatePortRange = value => {
  const number = toSafeInteger(value)

  if ( number < MIN_PORT || number > MAX_PORT ) {
    return `Invalid port. Must be between ${MIN_PORT} and less than ${MAX_PORT}`
  }
}

const renderInput = ({input, _meta, ...field}) => {
  let _field = without(field, ["meta"])
  return (
    <Input {..._field} {...input} type={field.type} placeholder={field.placeholder} disabled={field.disabled} />
  )
}

const normalizeInt = (value, _previousValue) => {
  return parseInt(value, 10)
}

const normalizeBool = (value, _previousValue) => {
    if(value === ""){
        return false
    }
    return true
}

const InputWithWarnings = props => {
  const { error, touched } = props.meta

  return (
    <FormGroup color={error ? "danger" : ""}>
      {renderInput(props)}
      {touched && error && <FormFeedback>{error}</FormFeedback>}
    </FormGroup>
  )
}

const renderListeners = ({ fields }) => (

    { (fields.length > 0) && Container ports}
    {fields.map((service, i) =>

        <Field name={'listeners['+i+'].port'} component={InputWithWarnings} validate={validatePortRange} type="number" step="1" min={MIN_PORT} max={MAX_PORT} normalize={normalizeInt}/>

            <Field className="form-check-input" name={'listeners['+i+'].protocol'} component={renderInput} type="radio" value="TCP"/> TCP

            <Field className="form-check-input" name={'listeners['+i+'].protocol'} component={renderInput} type="radio" value="UDP"/> UDP

        <button type="button" className="btn btn-secondary btn-sm float-xs-right btn-service-action-right" onClick={() => fields.remove(i)}>

        </button>

    )}

        <button type="button" className="btn btn-secondary btn-sm float-xs-left btn-service-action" onClick={() => fields.push({ protocol: 'TCP' })}>Add container port </button>

  )

const renderSpecsSelect = field => {
  return (
    <select {...field.input} name={field.name} className="form-control">
      <option disabled value="">Choose service spec</option>
      {field.serviceSpecs.map(function (spec) {
        return <option key={spec._id} value={spec._id}>{spec.name}</option>
      })}
    </select>
  )
}

class ProjectServiceForm extends Component {
  constructor(props) {
    super(props);

    this.toggleContainerPort = this.toggleContainerPort.bind(this);
    this.state = {
      tooltipContainerPortOpen: false
    };
  }

  toggleContainerPort() {
    this.setState({
      tooltipContainerPortOpen: !this.state.tooltipContainerPortOpen
    })
  }

  render() {
    const { edit, onCancel, onDelete, handleSubmit, error } = this.props
    return (
        <Form onSubmit={handleSubmit} noValidate>
          { error &&
            <Alert color="danger">{error}</Alert>
          }
          <FormGroup>

                      Name
                      <Field name="name" className="form-control" component={renderInput} disabled={edit} type="text"/>

                      Spec
                      <Field className="form-control" name="specId" serviceSpecs={this.props.serviceSpecs} component={renderSpecsSelect}/>

                      Count
                      <Field name="count" component={renderInput} type="number" step="1" min="0" max="100" normalize={normalizeInt}/>

                  Command
                  <Field name="command" className="form-control" component={renderInput} type="text"/>

                  <Field className="form-check-input" name="oneShot" component={renderInput} type="checkbox" value="false" normalize={normalizeBool} /> One-shot

                    <Tooltip placement="right" isOpen={this.state.tooltipContainerPortOpen} target="ToolTipContainerPort" toggle={this.toggleContainerPort}>
                      If your application is a webserver then add the port that it listens on.
                    </Tooltip>

                  <FieldArray name="listeners" component={renderListeners}/>

                <button type="button" className="btn btn-secondary btn-sm float-xs-right btn-service-action-right" onClick={() => onCancel()}>
                   Cancel
                </button>
                { edit &&
                <ButtonConfirmAction btnLabel="Delete" btnIconClass="fa fa-trash" onConfirm={onDelete} btnClass="btn btn-danger btn-sm float-xs-right btn-service-action-right">
                  Are you sure?
                </ButtonConfirmAction>}
                <button type="submit" className="btn btn-success btn-sm float-xs-right btn-service-action-right">
                   Save
                </button>

          </FormGroup>
        </Form>
    )
  }
}

export default reduxForm({
  enableReinitialize: true,
  destroyOnUnmount: true,
  form: 'projectService'
})(ProjectServiceForm)
<commit_msg>Fixes #54; Tooltip to describe service spec settings while editing services<commit_after>import React, { Component } from 'react'
import { Alert, Form, FormGroup, FormFeedback, Input } from 'reactstrap'
import { Field, FieldArray, reduxForm } from 'redux-form'
import { Tooltip } from 'reactstrap';
import ButtonConfirmAction from '../ButtonConfirmAction'
import { toSafeInteger, without } from 'lodash'
import { forEach } from 'lodash'

const MIN_PORT = 1
const MAX_PORT = 65535

const validatePortRange = value => {
  const number = toSafeInteger(value)

  if ( number < MIN_PORT || number > MAX_PORT ) {
    return `Invalid port. Must be between ${MIN_PORT} and less than ${MAX_PORT}`
  }
}

const renderInput = ({input, _meta, ...field}) => {
  let _field = without(field, ["meta"])
  return (
    <Input {..._field} {...input} type={field.type} placeholder={field.placeholder} disabled={field.disabled} />
  )
}

const normalizeInt = (value, _previousValue) => {
  return parseInt(value, 10)
}

const normalizeBool = (value, _previousValue) => {
    if(value === ""){
        return false
    }
    return true
}

const InputWithWarnings = props => {
  const { error, touched } = props.meta

  return (
    <FormGroup color={error ? "danger" : ""}>
      {renderInput(props)}
      {touched && error && <FormFeedback>{error}</FormFeedback>}
    </FormGroup>
  )
}

const renderListeners = ({ fields }) => (

    { (fields.length > 0) && Container ports}
    {fields.map((service, i) =>

        <Field name={'listeners['+i+'].port'} component={InputWithWarnings} validate={validatePortRange} type="number" step="1" min={MIN_PORT} max={MAX_PORT} normalize={normalizeInt}/>

            <Field className="form-check-input" name={'listeners['+i+'].protocol'} component={renderInput} type="radio" value="TCP"/> TCP

            <Field className="form-check-input" name={'listeners['+i+'].protocol'} component={renderInput} type="radio" value="UDP"/> UDP

        <button type="button" className="btn btn-secondary btn-sm float-xs-right btn-service-action-right" onClick={() => fields.remove(i)}>

        </button>

    )}

        <button type="button" className="btn btn-secondary btn-sm float-xs-left btn-service-action" onClick={() => fields.push({ protocol: 'TCP' })}>Add container port </button>

  )

const renderSpecsSelect = field => {
  return (
    <select {...field.input} name={field.name} className="form-control">
      <option disabled value="">Choose service spec</option>
      {field.serviceSpecs.map(function (spec) {
        return <option key={spec._id} value={spec._id}>{spec.name}</option>
      })}
    </select>
  )
}

class ProjectServiceForm extends Component {
  constructor(props) {
    super(props);

    this.toggleContainerPort = this.toggleContainerPort.bind(this);
    this.toggleTooltipServiceSettings = this.toggleTooltipServiceSettings.bind(this);
    this.state = {
      tooltipContainerPortOpen: false,
      tooltipServiceSettingsOpen: false
    };
  }

  toggleContainerPort() {
    this.setState({
      tooltipContainerPortOpen: !this.state.tooltipContainerPortOpen
    })
  }

  toggleTooltipServiceSettings() {
    this.setState({
      tooltipServiceSettingsOpen: !this.state.tooltipServiceSettingsOpen
    })
  }

  renderServiceSpecsTip() {
    let { serviceSpecs } = this.props
    let specs_jsx = []

    forEach(serviceSpecs, spec => {
      specs_jsx.push(this.renderQuickSpec(spec))
    })
    return specs_jsx
  }

  renderQuickSpec(spec) {
    return (

{spec.name}

          CPU: {spec.cpuRequest} / {spec.cpuLimit}

          Memory: {spec.memoryRequest} / {spec.memoryLimit}

          Termination: {spec.terminationGracePeriodSeconds}s

    )
  }

  render() {
    const { edit, onCancel, onDelete, handleSubmit, error } = this.props
    return (
        <Form onSubmit={handleSubmit} noValidate>
          { error &&
            <Alert color="danger">{error}</Alert>
          }
          <FormGroup>

                      Name
                      <Field name="name" className="form-control" component={renderInput} disabled={edit} type="text"/>

                        Spec

                        <Field className="form-control" name="specId" serviceSpecs={this.props.serviceSpecs} component={renderSpecsSelect}/>

                      <Tooltip placement="right" isOpen={this.state.tooltipServiceSettingsOpen} target="ToolTipServiceSettings" toggle={this.toggleTooltipServiceSettings}>
                        {this.renderServiceSpecsTip()}
                      </Tooltip>

                      Count
                      <Field name="count" component={renderInput} type="number" step="1" min="0" max="100" normalize={normalizeInt}/>

                  Command
                  <Field name="command" className="form-control" component={renderInput} type="text"/>

                  <Field className="form-check-input" name="oneShot" component={renderInput} type="checkbox" value="false" normalize={normalizeBool} /> One-shot

                    <Tooltip placement="right" isOpen={this.state.tooltipContainerPortOpen} target="ToolTipContainerPort" toggle={this.toggleContainerPort}>
                      If your application is a webserver then add the port that it listens on.
                    </Tooltip>

                  <FieldArray name="listeners" component={renderListeners}/>

                <button type="button" className="btn btn-secondary btn-sm float-xs-right btn-service-action-right" onClick={() => onCancel()}>
                   Cancel
                </button>
                { edit &&
                <ButtonConfirmAction btnLabel="Delete" btnIconClass="fa fa-trash" onConfirm={onDelete} btnClass="btn btn-danger btn-sm float-xs-right btn-service-action-right">
                  Are you sure?
                </ButtonConfirmAction>}
                <button type="submit" className="btn btn-success btn-sm float-xs-right btn-service-action-right">
                   Save
                </button>

          </FormGroup>
        </Form>
    )
  }
}

export default reduxForm({
  enableReinitialize: true,
  destroyOnUnmount: true,
  form: 'projectService'
})(ProjectServiceForm)
<|endoftext|>


<commit_before>import React from 'react';

import SmallHeader from './SmallHeader';
import ActivityRewards from './ActivityRewards';
import ActivityModifiers from './ActivityModifiers';
import FeaturedItems from './FeaturedItems';
import VendorStock from './VendorStock';

import AdvisorsStore from '../stores/AdvisorsStore';
import DetailsStore from '../stores/DetailsStore';
import DetailsActions from '../actions/DetailsActions';

var overlayColor = 'rgba(39, 58, 65, 0.75)';

export default class AdvisorDetails extends React.Component {
    constructor(props) {
        super(props);
        this.state = {};
        this.onAdvisorsChanged = this.onAdvisorsChanged.bind(this);
        this.onDetailsChanged = this.onDetailsChanged.bind(this);
    }

    update() {
        this.onAdvisorsChanged(AdvisorsStore.getState());
        this.onDetailsChanged(DetailsStore.getState());
        if (!this.state.details) {
            DetailsActions.fetchAdvisor(
                this.props.params.category, this.props.params.id);
        }
    }

    componentDidMount() {
        AdvisorsStore.listen(this.onAdvisorsChanged);
        DetailsStore.listen(this.onDetailsChanged);
        this.update();
    }

    componentWillUnmount() {
        AdvisorsStore.unlisten(this.onAdvisorsChanged);
        DetailsStore.unlisten(this.onDetailsChanged);
    }

    componentDidUpdate(prevProps) {
        if (prevProps.params.category !== this.props.params.category
            || prevProps.params.id !== this.props.params.id) {
            this.update();
        }
    }

    getAdvisorSummary(categories, categoryID, advisorID) {
        for (let c = 0; c < categories.length; c++) {
            let category = categories[c];
            if (category.id === categoryID) {
                for (let a = 0; a < category.advisors.length; a++) {
                    let advisor = category.advisors[a];
                    if (advisor.shortID === advisorID) {
                        return advisor;
                    }
                }
            }
        }
        return null;
    }

    onAdvisorsChanged(advisors) {
        if (advisors) {
            if (advisors.categoryMap) {
                let category = advisors.categoryMap[this.props.params.category];
                if (category) {
                    let advisorID = category[this.props.params.id];
                    if (advisorID) {
                        this.setState({
                            advisorID: advisorID
                        });
                    }
                }
            }
            if (advisors.categories) {
                let summary = this.getAdvisorSummary(
                    advisors.categories,
                    this.props.params.category,
                    this.props.params.id);
                if (summary) {
                    this.setState({
                        summary: summary
                    });
                }
            }
        }
    }

    onDetailsChanged(store) {
        if (store) {
            if (store.errorMessage) {
                this.setState({
                    errorMessage: store.errorMessage
                });
            }
            else if (store.details && this.state.advisorID) {
                let details = store.details[this.state.advisorID];
                if (details) {
                    this.setState({
                        details: details
                    });
                }
            }
        }
    }

    renderIcon(summary) {
        if (summary.icon) {
            let iconStyle = {
                backgroundImage: `url('${summary.icon}')`
            };
            return (

            );
        }
        return null;
    }

    renderHeroImage() {
        let summary = this.state.summary || {
            name: null,
            type: null,
            image: null,
            icon: null
        };

        let heroImageStyle = {
            background: summary.image
                ? `linear-gradient(${overlayColor}, ${overlayColor}), url('${summary.image}')`
                : overlayColor
        };
        let icon = this.renderIcon(summary);
        return (

                <SmallHeader />

                        {icon}

{summary.type}

{summary.name}

        );
    }

    renderGroup(name, content, key = undefined) {
        return (

{name}

                {content}

        );
    }

    renderDetails() {
        if (this.state.errorMessage) {
            return
{this.state.errorMessage}
;
        }
        if (!this.state.details) {
            return
Loading...
;
        }

        let modifiers = this.state.details.modifiers ?
            this.renderGroup('Modifiers', <ActivityModifiers modifiers={this.state.details.modifiers} />)
            : null;
        let rewards = this.state.details.rewards ?
            this.renderGroup('Rewards', <ActivityRewards rewards={this.state.details.rewards} />)
            : null;

        // render vendor stock
        let featuredItems = null;
        let otherItems = null;
        if (this.state.details.stock) {
            let stock = this.state.details.stock;
            featuredItems = stock.featured ?
                this.renderGroup(stock.featured.name,
                    <FeaturedItems items={stock.featured.items} />)
                : null;

            if (stock.other) {
                otherItems = stock.other.map((vendor, i) =>
                    this.renderGroup(vendor.name,
                        <VendorStock categories={vendor.categories} />,
                        `otherItems-${i}`));
            }
        }

        return (

                {modifiers}
                {featuredItems}

                    {otherItems}

                {rewards}

        );
    }

    render() {
        let hero = this.renderHeroImage();
        let details = this.renderDetails();

        return (

                {hero}
                {details}

        );
    };
}<commit_msg>Redirect to homepage on invalid advisor ID.<commit_after>import React from 'react';
import { browserHistory } from 'react-router';

import SmallHeader from './SmallHeader';
import ActivityRewards from './ActivityRewards';
import ActivityModifiers from './ActivityModifiers';
import FeaturedItems from './FeaturedItems';
import VendorStock from './VendorStock';

import AdvisorsStore from '../stores/AdvisorsStore';
import DetailsStore from '../stores/DetailsStore';
import DetailsActions from '../actions/DetailsActions';

var overlayColor = 'rgba(39, 58, 65, 0.75)';

export default class AdvisorDetails extends React.Component {
    constructor(props) {
        super(props);
        this.state = {};
        this.onAdvisorsChanged = this.onAdvisorsChanged.bind(this);
        this.onDetailsChanged = this.onDetailsChanged.bind(this);
    }

    update() {
        this.onAdvisorsChanged(AdvisorsStore.getState());
        this.onDetailsChanged(DetailsStore.getState());
        if (!this.state.details) {
            DetailsActions.fetchAdvisor(
                this.props.params.category, this.props.params.id);
        }
    }

    componentDidMount() {
        AdvisorsStore.listen(this.onAdvisorsChanged);
        DetailsStore.listen(this.onDetailsChanged);
        this.update();
    }

    componentWillUnmount() {
        AdvisorsStore.unlisten(this.onAdvisorsChanged);
        DetailsStore.unlisten(this.onDetailsChanged);
    }

    componentDidUpdate(prevProps) {
        if (prevProps.params.category !== this.props.params.category
            || prevProps.params.id !== this.props.params.id) {
            this.update();
        }
    }

    getAdvisorSummary(categories, categoryID, advisorID) {
        for (let c = 0; c < categories.length; c++) {
            let category = categories[c];
            if (category.id === categoryID) {
                for (let a = 0; a < category.advisors.length; a++) {
                    let advisor = category.advisors[a];
                    if (advisor.shortID === advisorID) {
                        return advisor;
                    }
                }
            }
        }
        return null;
    }

    onAdvisorsChanged(advisors) {
        if (advisors) {
            if (advisors.categoryMap) {
                let category = advisors.categoryMap[this.props.params.category];
                if (category) {
                    let advisorID = category[this.props.params.id];
                    if (advisorID) {
                        this.setState({
                            advisorID: advisorID
                        });
                    }
                }
            }
            if (advisors.categories) {
                let summary = this.getAdvisorSummary(
                    advisors.categories,
                    this.props.params.category,
                    this.props.params.id);
                if (summary) {
                    this.setState({
                        summary: summary
                    });
                }
            }
        }
    }

    onDetailsChanged(store) {
        if (store) {
            if (store.errorMessage) {
                console.log(store.errorMessage);
                // send us back to the homepage if there's an error
                browserHistory.push('/');
                // clear the error
                store.errorMessage = null;
            }
            else if (store.details && this.state.advisorID) {
                let details = store.details[this.state.advisorID];
                if (details) {
                    this.setState({
                        details: details
                    });
                }
            }
        }
    }

    renderIcon(summary) {
        if (summary.icon) {
            let iconStyle = {
                backgroundImage: `url('${summary.icon}')`
            };
            return (

            );
        }
        return null;
    }

    renderHeroImage() {
        let summary = this.state.summary || {
            name: null,
            type: null,
            image: null,
            icon: null
        };

        let heroImageStyle = {
            background: summary.image
                ? `linear-gradient(${overlayColor}, ${overlayColor}), url('${summary.image}')`
                : overlayColor
        };
        let icon = this.renderIcon(summary);
        return (

                <SmallHeader />

                        {icon}

{summary.type}

{summary.name}

        );
    }

    renderGroup(name, content, key = undefined) {
        return (

{name}

                {content}

        );
    }

    renderDetails() {
        if (!this.state.details) {
            return
Loading...
;
        }

        let modifiers = this.state.details.modifiers ?
            this.renderGroup('Modifiers', <ActivityModifiers modifiers={this.state.details.modifiers} />)
            : null;
        let rewards = this.state.details.rewards ?
            this.renderGroup('Rewards', <ActivityRewards rewards={this.state.details.rewards} />)
            : null;

        // render vendor stock
        let featuredItems = null;
        let otherItems = null;
        if (this.state.details.stock) {
            let stock = this.state.details.stock;
            featuredItems = stock.featured ?
                this.renderGroup(stock.featured.name,
                    <FeaturedItems items={stock.featured.items} />)
                : null;

            if (stock.other) {
                otherItems = stock.other.map((vendor, i) =>
                    this.renderGroup(vendor.name,
                        <VendorStock categories={vendor.categories} />,
                        `otherItems-${i}`));
            }
        }

        return (

                {modifiers}
                {featuredItems}

                    {otherItems}

                {rewards}

        );
    }

    render() {
        let hero = this.renderHeroImage();
        let details = this.renderDetails();

        return (

                {hero}
                {details}

        );
    };
}<|endoftext|>