From f41534ee4dcdd1eccfe52491307dc3b19a61dede Mon Sep 17 00:00:00 2001 From: James Allen Date: Sun, 22 Jun 2014 12:10:42 +0100 Subject: [PATCH] Add in creating of files to editor --- .../Features/Editor/EditorController.coffee | 2 +- .../Editor/EditorHttpController.coffee | 8 ++ services/web/app/coffee/router.coffee | 2 + services/web/app/views/project/editor.jade | 37 ++++++++- services/web/public/coffee/app/ide.coffee | 30 ++++---- .../app/ide/file-tree/FileTreeManager.coffee | 77 +++++++++++++++++-- .../controllers/FileTreeController.coffee | 33 ++++++++ .../FileTreeEntityController.coffee | 2 +- .../public/coffee/app/ide/services/ide.coffee | 7 ++ .../web/public/stylesheets/app/editor.less | 9 ++- .../Editor/EditorHttpControllerTests.coffee | 24 ++++++ 11 files changed, 203 insertions(+), 28 deletions(-) create mode 100644 services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee create mode 100644 services/web/public/coffee/app/ide/services/ide.coffee diff --git a/services/web/app/coffee/Features/Editor/EditorController.coffee b/services/web/app/coffee/Features/Editor/EditorController.coffee index 19e9d98da5..a5dffc961f 100644 --- a/services/web/app/coffee/Features/Editor/EditorController.coffee +++ b/services/web/app/coffee/Features/Editor/EditorController.coffee @@ -163,7 +163,7 @@ module.exports = EditorController = logger.log sl_req_id:sl_req_id, "sending new doc to project #{project_id}" Metrics.inc "editor.add-doc" ProjectEntityHandler.addDoc project_id, folder_id, docName, docLines, sl_req_id, (err, doc, folder_id)=> - EditorRealTimeController.emitToRoom(project_id, 'reciveNewDoc', folder_id, doc) + EditorRealTimeController.emitToRoom(project_id, 'docCreated', folder_id, doc) callback(err, doc) addFile: (project_id, folder_id, fileName, path, sl_req_id, callback = (error, file)->)-> diff --git a/services/web/app/coffee/Features/Editor/EditorHttpController.coffee b/services/web/app/coffee/Features/Editor/EditorHttpController.coffee index 57b65d7355..94daaf340f 100644 --- a/services/web/app/coffee/Features/Editor/EditorHttpController.coffee +++ b/services/web/app/coffee/Features/Editor/EditorHttpController.coffee @@ -1,6 +1,7 @@ ProjectEntityHandler = require "../Project/ProjectEntityHandler" logger = require "logger-sharelatex" EditorRealTimeController = require "./EditorRealTimeController" +EditorController = require "./EditorController" module.exports = EditorHttpController = restoreDoc: (req, res, next) -> @@ -19,3 +20,10 @@ module.exports = EditorHttpController = doc_id: doc._id } + addDoc: (req, res, next) -> + project_id = req.params.Project_id + name = req.body.name + parent_folder_id = req.body.parent_folder_id + EditorController.addDoc project_id, parent_folder_id, name, [], (error, doc) -> + return next(error) if error? + res.json doc diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 2edb5fb2d2..965ff54fba 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -87,6 +87,8 @@ module.exports = class Router app.get '/Project/:Project_id', SecurityManager.requestCanAccessProject, ProjectController.loadEditor app.get '/Project/:Project_id/file/:File_id', SecurityManager.requestCanAccessProject, FileStoreController.getFile + app.post '/project/:Project_id/doc', SecurityManager.requestCanModifyProject, EditorHttpController.addDoc + app.post '/project/:Project_id/compile', SecurityManager.requestCanAccessProject, CompileController.compile app.get '/Project/:Project_id/output/output.pdf', SecurityManager.requestCanAccessProject, CompileController.downloadPdf app.get /^\/project\/([^\/]*)\/output\/(.*)$/, diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.jade index 2522c30f1a..e2603473b0 100644 --- a/services/web/app/views/project/editor.jade +++ b/services/web/app/views/project/editor.jade @@ -40,9 +40,14 @@ block content i.fa.fa-comment #editor-content(ng-cloak, layout="main", ng-hide="state.loading") - aside#file-tree.ui-layout-west + aside#file-tree.ui-layout-west(ng-controller="FileTreeController") .toolbar.toolbar-small - a(href, tooltip-html-unsafe="New
File", tooltip-placement="bottom") + a( + href, + ng-click="openNewDocModal()", + tooltip-html-unsafe="New
File", + tooltip-placement="bottom" + ) i.fa.fa-file a(href, tooltip="New Folder", tooltip-placement="bottom") i.fa.fa-folder @@ -58,7 +63,7 @@ block content ul.list-unstyled.file-tree-list file-entity( entity="entity", - ng-repeat="entity in rootFolder.children" + ng-repeat="entity in rootFolder.children | orderBy:'name'" ) .ui-layout-center @@ -97,8 +102,32 @@ block content span(ng-click="select()") {{ entity.name }} ul.list-unstyled(ng-show="expanded") - file-entity(entity="child", ng-repeat="child in entity.children") + file-entity(entity="child", ng-repeat="child in entity.children | orderBy:'name'") + script(type='text/ng-template', id='newDocModalTemplate') + .modal-header + h3 New File + .modal-body + form(novalidate, name="newDocForm") + input.form-control( + type="text", + placeholder="File Name", + required, + ng-model="inputs.name", + ng-enter="create()", + focus-on="open" + ) + .modal-footer + button.btn.btn-default( + ng-disabled="state.inflight" + ng-click="cancel()" + ) Cancel + button.btn.btn-primary( + ng-disabled="newDocForm.$invalid || state.inflight" + ng-click="create()" + ) + span(ng-hide="state.inflight") Create + span(ng-show="state.inflight") Creating... //- #loadingScreen //- h3 Loading... diff --git a/services/web/public/coffee/app/ide.coffee b/services/web/public/coffee/app/ide.coffee index 3c06039117..5e3b398c43 100644 --- a/services/web/public/coffee/app/ide.coffee +++ b/services/web/public/coffee/app/ide.coffee @@ -2,35 +2,35 @@ define [ "base" "ide/file-tree/FileTreeManager" "ide/directives/layout" + "ide/services/ide" + "directives/focusOn" ], ( App FileTreeManager ) -> - App.controller "IdeController", ["$scope", "$timeout", ($scope, $timeout) -> + App.controller "IdeController", ["$scope", "$timeout", "ide", ($scope, $timeout, ide) -> $scope.state = { loading: true load_progress: 40 } - window.ide = ide = { - '$scope': $scope - } + window._ide = ide + + ide.project_id = window.project_id + ide.$scope = $scope + ide.socket = io.connect null, + reconnect: false + "force new connection": true + ide.fileTreeManager = new FileTreeManager(ide, $scope) - $scope.project_id = window.project_id - - ioOptions = - reconnect: false - "force new connection": true - $scope.socket = io.connect null, ioOptions - - $scope.socket.on "connect", () -> + ide.socket.on "connect", () -> $scope.$apply () -> $scope.state.load_progress = 80 joinProject = () => - $scope.socket.emit 'joinProject', { - project_id: $scope.project_id + ide.socket.emit 'joinProject', { + project_id: ide.project_id }, (err, project, permissionsLevel, protocolVersion) => if $scope.protocolVersion? and $scope.protocolVersion != protocolVersion location.reload(true) @@ -43,8 +43,6 @@ define [ $scope.$emit "project:joined" - console.log "Project", $scope.project, $scope.rootFolder - setTimeout(joinProject, 100) ] angular.bootstrap(document.body, ["SharelatexApp"]) \ No newline at end of file diff --git a/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee b/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee index fa412cddf0..4958fc1665 100644 --- a/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee +++ b/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee @@ -1,30 +1,61 @@ define [ "ide/file-tree/directives/fileEntity" + "ide/file-tree/controllers/FileTreeController" "ide/file-tree/controllers/FileTreeFolderController" "ide/file-tree/controllers/FileTreeEntityController" ], () -> class FileTreeManager constructor: (@ide, @$scope) -> @$scope.$on "project:joined", => - console.log "Joined" @loadRootFolder() + @_bindToSocketEvents() + + _bindToSocketEvents: () -> + @ide.socket.on "docCreated", (parent_folder_id, doc) => + console.log "Doc created", parent_folder_id, doc + parent_folder = @findEntityById(parent_folder_id) or @$scope.rootFolder + @$scope.$apply () -> + parent_folder.children.push { + name: doc.name + id: doc._id + type: "doc" + } + + findEntityById: (id) -> + @_findEntityByIdInFolder @$scope.rootFolder, id + + _findEntityByIdInFolder: (folder, id) -> + for entity in folder.children or [] + if entity.id == id + return entity + else if entity.children? + result = @_findEntityByIdInFolder(entity, id) + return result if result? + + return null + forEachEntity: (callback) -> @_forEachEntityInFolder(@$scope.rootFolder, callback) _forEachEntityInFolder: (folder, callback) -> - for entity in folder.children + for entity in folder.children or [] callback(entity) if entity.children? @_forEachEntityInFolder(entity, callback) + # forEachFolder: (callback) -> + # @forEachEntity (entity) -> + # if entity.type == "folder" + # callback(entity) + loadRootFolder: () -> @$scope.rootFolder = @_parseFolder(@$scope.project.rootFolder[0]) _parseFolder: (rawFolder) -> folder = { name: rawFolder.name - id: rawFolder.id + id: rawFolder._id type: "folder" children: [] } @@ -32,18 +63,54 @@ define [ for doc in rawFolder.docs or [] folder.children.push { name: doc.name - type: "doc" id: doc._id + type: "doc" } for file in rawFolder.fileRefs or [] folder.children.push { name: file.name - type: "file" id: file._id + type: "file" } for childFolder in rawFolder.folders or [] folder.children.push @_parseFolder(childFolder) return folder + + getCurrentFolder: (startFolder = @$scope.rootFolder) -> + for entity in startFolder.children or [] + # The 'current' folder is either the one selected, or + # the one containing the selected doc/file + if entity.selected + if entity.type == "folder" + return entity + else + return startFolder + + if entity.type == "folder" + result = @getCurrentFolder(entity) + return result if result? + + return null + + createDocInCurrentFolder: (name, callback = (error, doc) ->) -> + # We'll wait for the socket.io 'createDoc' notification to actually + # add the doc for us. + parent_folder = @getCurrentFolder() + $.ajax { + url: "/project/#{@ide.project_id}/doc" + type: "POST" + contentType: "application/json; charset=utf-8" + data: JSON.stringify { + name: name, + parent_folder_id: parent_folder?.id + _csrf: window.csrfToken + } + dataType: "json" + success: (doc) -> + console.log "Got doc from POST", doc + callback(null, doc) + failure: (error) -> callback(error) + } diff --git a/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee b/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee new file mode 100644 index 0000000000..efdb769a77 --- /dev/null +++ b/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeController.coffee @@ -0,0 +1,33 @@ +define [ + "base" +], (App) -> + App.controller "FileTreeController", ["$scope", "$modal", ($scope, $modal) -> + $scope.openNewDocModal = () -> + $modal.open( + templateUrl: "newDocModalTemplate" + controller: "NewDocModalController" + ) + ] + + App.controller "NewDocModalController", [ + "$scope", "ide", "$modalInstance", "$timeout", + ($scope, ide, $modalInstance, $timeout) -> + $scope.inputs = + name: "name.tex" + $scope.state = + inflight: false + + $modalInstance.opened.then () -> + $timeout () -> + $scope.$broadcast "open" + , 700 + + $scope.create = () -> + $scope.state.inflight = true + ide.fileTreeManager.createDocInCurrentFolder $scope.inputs.name, (error, doc) -> + $scope.state.inflight = false + $modalInstance.close() + + $scope.cancel = () -> + $modalInstance.dismiss('cancel') + ] \ No newline at end of file diff --git a/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee b/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee index 21b5359658..b22a14c47a 100644 --- a/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee +++ b/services/web/public/coffee/app/ide/file-tree/controllers/FileTreeEntityController.coffee @@ -1,7 +1,7 @@ define [ "base" ], (App) -> - App.controller "FileTreeEntityController", ["$scope", ($scope) -> + App.controller "FileTreeEntityController", ["$scope", "ide", ($scope, ide) -> $scope.select = ($event) -> ide.fileTreeManager.forEachEntity (entity) -> entity.selected = false diff --git a/services/web/public/coffee/app/ide/services/ide.coffee b/services/web/public/coffee/app/ide/services/ide.coffee new file mode 100644 index 0000000000..2a3b448370 --- /dev/null +++ b/services/web/public/coffee/app/ide/services/ide.coffee @@ -0,0 +1,7 @@ +define [ + "base" +], (App) -> + # We create and provide this as service so that we can access the global ide + # from within other parts of the angular app. + App.factory "ide", () -> + return {} diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 95d64c6e14..35cff2dc31 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -71,7 +71,14 @@ ul.file-tree-list { font-size: 0.8rem; - margin-top: (@line-height-computed / 4); + margin: 0; + padding: (@line-height-computed / 4) 0; + position: absolute; + top: 32px; + bottom: 0; + left: 0; + right: 0; + overflow-y: scroll; ul { margin-left: (@line-height-computed / 2); diff --git a/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee b/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee index 706476a6c4..3dc2d33a26 100644 --- a/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Editor/EditorHttpControllerTests.coffee @@ -9,8 +9,10 @@ describe "EditorHttpController", -> '../Project/ProjectEntityHandler' : @ProjectEntityHandler = {} "./EditorRealTimeController": @EditorRealTimeController = {} "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + "./EditorController": @EditorController = {} @project_id = "mock-project-id" @doc_id = "mock-doc-id" + @parent_folder_id = "mock-folder-id" @req = {} @res = send: sinon.stub() @@ -44,3 +46,25 @@ describe "EditorHttpController", -> @res.json .calledWith(doc_id: @new_doc_id) .should.equal true + + describe "addDoc", -> + beforeEach -> + @doc = { "mock": "doc" } + @req.params = + Project_id: @project_id + @req.body = + name: @name = "doc-name" + parent_folder_id: @parent_folder_id + @EditorController.addDoc = sinon.stub().callsArgWith(4, null, @doc) + @EditorHttpController.addDoc @req, @res + + it "should call EditorController.addDoc", -> + @EditorController.addDoc + .calledWith(@project_id, @parent_folder_id, @name, []) + .should.equal true + + it "should send the doc back as JSON", -> + @res.json + .calledWith(@doc) + .should.equal true +