Skip to content

Instantly share code, notes, and snippets.

@seanhess
Last active December 22, 2015 22:59
Show Gist options
  • Save seanhess/6544212 to your computer and use it in GitHub Desktop.
Save seanhess/6544212 to your computer and use it in GitHub Desktop.
<div class="book_edit">
<!--<pre>{{files}}</pre>-->
<div>
<!--<label>bookId</label>-->
<!--<div><input disabled ng-model="book.bookId"></div>-->
<div><a href="/books/{{book.bookId}}" target="_blank">Data</a></div>
<br/>
<label>productId</label>
<div>{{book.bookId | toProductId}}</div>
<label>popularity</label>
<div>{{book.popularity}}</div>
<label>featured</label>
<div><input type="checkbox" ng-model="book.featured"/></div>
<label>title</label>
<div><input ng-model="book.title" placeholder="A Tale of Two Cities"/></div>
<label class="cover">cover</label>
<div>
<div class="cover" dragupload="onDropCoverImage">
<img ng-show="book.imageUrl" ng-src="{{book.imageUrl}}">
<button ng-show="book.imageUrl" class="clear" ng-click="clearImage()">clear</button>
</div>
</div>
<label>author</label>
<div><input ng-model="book.author" placeholder="Charles Dickens"/></div>
<label>genre</label>
<div ng-hide="editingNewGenre" class="genre">
<select
ng-model="book.genre"
ng-options="genre.name as genre.name for genre in genres">
</select>
<a ng-click="toggleEditNewGenre()">New Genre</a>
</div>
<div ng-show="editingNewGenre" class="genre">
<input ng-model="book.genre" placeholder="Comedy">
<a ng-click="toggleEditNewGenre()">Cancel</a>
</div>
<label>description</label>
<div><textarea ng-model="book.description" placeholder="This book is awesome"></textarea></div>
<label>{{book.textFiles}} text files</label>
<label>{{book.audioFiles}} audio files</label>
<div class="files" dragupload="onDrop">
<div class="instructions">Drop files here (.mp3 or .html)</div>
<div class="file" ng-class="{loading: isLoading(file), active: isFileUploadActive(file)}" ng-repeat="file in book.files | orderBy:'name'">
<div ng-hide="isEditing(file)">
<small><button class="remove" ng-click="removeFile(file)">x</button> </small>
<small><button class="rename" ng-click="editFile(file)">rename</button></small>
<a href="{{file.url}}" target="_blank">{{file.name}}.{{file.ext}}</a>
<!--<a class="external_link" href="{{file.url}}"></a>-->
</div>
<form ng-show="isEditing(file)">
<input ng-model="editing.name">
<button ng-click="updateFile(file)">Save</button>
<a ng-click="cancelEdit()">Cancel</a>
</form>
</div>
</div>
<p>
<input type="submit" ng-disabled="!isBookValid()" ng-click="save()" value="Save"></input>
<button ng-disabled="!book" ng-click="remove()">Remove</button>
</p>
</div>
</div>
///<reference path="../def/angular.d.ts"/>
///<reference path="../def/underscore.d.ts"/>
///<reference path="../types.ts"/>
///<reference path="../services/Books"/>
///<reference path="../services/Files.ts"/>
interface BookParams extends ng.IRouteParamsService {
bookId: string;
}
function BookCtrl($scope, Books:IBookService, Files:IFileService, $routeParams: BookParams, $location:ng.ILocationService, $http:ng.IHttpService) {
var bookId = $scope.bookId = $routeParams.bookId
$scope.book = Books.get({bookId:bookId})
$scope.fileStatus = {}
$http.get("/genres/").success(function(genres) {
$scope.genres = genres
})
function calculateNumFiles() {
$scope.book.audioFiles = Files.audioFiles($scope.book.files).length
$scope.book.textFiles = Files.textFiles($scope.book.files).length
}
function back() {
$location.path("/admin")
}
function bookValid(book:IBook) {
if (!book) return false
if (!book.files) return false
var validFiles = book.files.filter(fileValid)
if (validFiles.length < book.files.length) return false
return true
}
function fileValid(file:IFile) {
if (!file.url) return false
if (!file.fileId) return false
if (!file.name) return false
if (!file.ext) return false
return true
}
$scope.isBookValid = function() {
return bookValid($scope.book)
}
$scope.toggleEditNewGenre = function() {
$scope.editingNewGenre = !$scope.editingNewGenre
}
$scope.save = function(book) {
Books.update($scope.book).then(back)
}
$scope.remove = function() {
Books.remove({bookId: bookId}, back)
}
$scope.removeFile = function(file:IFile) {
$scope.book.files = _.without($scope.book.files, file)
calculateNumFiles()
}
$scope.isEditing = function(file) {
return $scope.editing && $scope.editing.fileId == file.fileId
}
$scope.editFile = function(file:IFile) {
$scope.editing = _.clone(file)
}
$scope.cancelEdit = function() {
delete $scope.editing
}
$scope.updateFile = function(file) {
file.name = $scope.editing.name
$scope.cancelEdit()
$http.put('/files/' + file.fileId, file).success(function() {
//loadFiles()
})
}
$scope.onDrop = function(files:IHTMLFile[]) {
files.forEach(addFile)
}
$scope.onDropCoverImage = function(files:IHTMLFile[]) {
Files.upload(files[0])
.then(function(file:IFile) {
$scope.book.imageUrl = file.url
})
}
$scope.clearImage = function() {
delete $scope.book.imageUrl
}
$scope.isLoading = function(file:IFile) {
return (file.fileId === undefined || file.fileId === null)
}
$scope.isFileUploadActive = function(file:IFile) {
var op = $scope.fileStatus[file.name]
if (!op) return false
return op.active
}
function filesOfType(files:IFile[], ext:string):IFile[] {
return files.filter(function(file:IFile) {
return (file.ext == ext)
})
}
function toPendingFile(htmlFile:IHTMLFile):IFile {
var ext = htmlFile.name.match(/\.(\w+)$/)[1]
var name = htmlFile.name.replace("." + ext, "")
// you can tell it's pending because it doesn't have url, fileId, bookId
return {
name:name,
ext:ext,
url:undefined,
fileId:undefined,
bookId:undefined,
}
}
function addFile(file:IHTMLFile) {
var pendingFile = toPendingFile(file)
$scope.book.files.push(pendingFile)
var op = Files.queueUpload(file, function(file:IFile) {
_.extend(pendingFile, file)
delete $scope.fileStatus[file.name]
calculateNumFiles()
})
$scope.fileStatus[pendingFile.name] = op
}
}
///<reference path="../def/angular.d.ts"/>
///<reference path="../def/jquery.d.ts"/>
// prevents drops from changing the page
function dragIgnore($parse:ng.IParseService) {
return function(scope:ng.IScope, element:JQuery, attrs) {
var target = document
target.addEventListener("dragover", function(e) {
e.preventDefault()
return false
})
target.addEventListener("drop", function(e) {
e.preventDefault()
return false
})
}
}
// dragupload="onDropFiles" will call scope.onDropFiles(files)
function dragUpload($parse:ng.IParseService) {
return function(scope:ng.IScope, element:JQuery, attrs) {
var target = element.get(0)
//var onDrop = $parse(attrs.dragupload)
var onDrop = scope[attrs.dragupload]
//var onDrop = $parse(attrs.dragupload)
//function shouldAccept(e) {
//return (e.dataTransfer.files && e.dataTransfer.files.length && e.dataTransfer.files[0].type.match("text/plain"))
//}
target.addEventListener("dragenter", function(e) {
element.addClass("drag")
})
target.addEventListener("dragleave", function(e) {
element.removeClass("drag")
})
target.addEventListener("dragover", function(e) {
e.stopPropagation()
e.preventDefault()
var ok = e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.indexOf('Files') >= 0
return false
})
target.addEventListener("drop", function(e) {
// dropped! Check out e.dataTransfer
e.stopPropagation()
e.preventDefault()
element.removeClass("drag")
//if (!shouldAccept(e)) return
// convert files to array
var files = Array.prototype.slice.call(e.dataTransfer.files, 0)
scope.$apply(function() {
onDrop(files)
})
})
}
}
/// <reference path="../types.ts"/>
/// <reference path="../def/angular.d.ts" />
/// <reference path="../controls/Book.ts" />
interface IFileCb {
(file:IFile);
}
interface IFileService extends ng.resource.IResourceClass {
upload(file:IHTMLFile):ng.IPromise<IFile>;
queueUpload(file:IHTMLFile, cb:IFileCb):FileServiceUploadOperation;
audioFiles(files:IFile[]):IFile[];
textFiles(files:IFile[]):IFile[];
}
interface FileServiceUploadOperation {
file:IHTMLFile;
active:boolean;
cb(file:IFile);
}
// TODO: turn this into a queue
function Files($http: ng.IHttpService):IFileService {
function not(f:Function) {
return function(...args:any[]) {
return !f.apply(null, args)
}
}
function isAudio(file:IFile) {
return file.ext == "mp3"
}
var queue:FileServiceUploadOperation[] = []
var currentOp:FileServiceUploadOperation;
function nextUpload() {
currentOp = queue.pop()
currentOp.active = true
Service.upload(currentOp.file)
.then((file:IFile) => currentOp.cb(file))
.then(function() {
currentOp = null
if (queue.length) nextUpload()
})
}
// this can return a promise object thing
// should be thenable, for when it finishes
// but also have a progress
var Service:IFileService = <any> {}
Service.queueUpload = function(file:IHTMLFile, cb:IFileCb) {
var op:FileServiceUploadOperation = {file:file, cb:cb, active:false}
queue.push(op)
// if not started, start now
if (!currentOp) nextUpload()
return op
}
Service.upload = function(file:IHTMLFile) {
console.log("UPLOAD", file.name, file.size)
// must be a file from the web browser
var formData = new FormData()
formData.append('file', file)
return $http({
method: 'POST',
url: '/files',
data: formData,
// no idea why you need both of these, but you do
transformRequest: angular.identity,
headers: {'Content-Type': undefined},
})
.then((rs) => rs.data)
}
Service.audioFiles = function(files:IFile[]) {
return files.filter(isAudio)
}
Service.textFiles = function(files:IFile[]) {
return files.filter(not(isAudio))
}
// simple queueing method for uploading?
return Service
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment