├── .gitignore ├── Gruntfile.js ├── README.md ├── app ├── cloud-explorer.html ├── demo.html ├── images │ └── default │ │ ├── breadcrumb.png │ │ ├── btn-close.png │ │ ├── btn-icon-items.png │ │ ├── btn-item-delete.png │ │ ├── btn-item-rename.png │ │ ├── btn-item-select.png │ │ ├── btn-list-items.png │ │ ├── btn-new-folder.png │ │ ├── btn-parent-folder.png │ │ ├── btn-sort.png │ │ ├── ce-header.png │ │ ├── dropzone.png │ │ ├── home-dropbox.png │ │ ├── home-ftp.png │ │ ├── home-github.png │ │ ├── home-open-pages.png │ │ ├── home-webdav.png │ │ ├── home-www.png │ │ ├── icon-file.png │ │ ├── icon-folder.png │ │ ├── icon-image.png │ │ ├── icon-sound.png │ │ ├── icon-video.png │ │ ├── srv-dropbox.png │ │ ├── srv-ftp.png │ │ ├── srv-github.png │ │ ├── srv-open-pages.png │ │ ├── srv-state-connected.png │ │ ├── srv-state-disabled.png │ │ ├── srv-state-disconnected.png │ │ ├── srv-webdav.png │ │ └── srv-www.png ├── index.html ├── oauth-cb.html ├── scripts │ └── .gitignore └── styles │ ├── base.scss │ ├── cloud-explorer.scss │ ├── default │ ├── README.md │ └── theme.scss │ └── platforms.scss ├── build.hxml ├── package.json ├── screenshot01.png ├── screenshot02.png ├── screenshot03.png ├── screenshot04.png ├── screenshot05.png ├── screenshot06.png ├── server └── unifile.js └── src └── ce ├── api └── CloudExplorer.hx ├── core ├── Controller.hx ├── config │ └── Config.hx ├── ctrl │ └── ErrorCtrl.hx ├── model │ ├── CEBlob.hx │ ├── CEError.hx │ ├── DisplayMode.hx │ ├── Location.hx │ ├── Mode.hx │ ├── Service.hx │ ├── SortField.hx │ ├── SortOrder.hx │ ├── State.hx │ ├── api │ │ ├── ExportOptions.hx │ │ ├── PickOptions.hx │ │ ├── ReadOptions.hx │ │ └── WriteOptions.hx │ ├── oauth │ │ └── OAuthResult.hx │ └── unifile │ │ ├── Account.hx │ │ ├── ConnectResult.hx │ │ ├── File.hx │ │ ├── LoginResult.hx │ │ ├── LogoutResult.hx │ │ ├── Service.hx │ │ ├── UnifileError.hx │ │ └── UploadResult.hx ├── parser │ ├── json │ │ └── Json2Primitive.hx │ ├── oauth │ │ └── Str2OAuthResult.hx │ └── unifile │ │ ├── Json2Account.hx │ │ ├── Json2ConnectResult.hx │ │ ├── Json2File.hx │ │ ├── Json2LoginResult.hx │ │ ├── Json2LogoutResult.hx │ │ ├── Json2Service.hx │ │ ├── Json2UnifileError.hx │ │ └── Json2UploadResult.hx ├── service │ └── UnifileSrv.hx └── view │ ├── AlertPopup.hx │ ├── Application.hx │ ├── AuthPopup.hx │ ├── Breadcrumb.hx │ ├── Button.hx │ ├── DropZone.hx │ ├── Export.hx │ ├── FileBrowser.hx │ └── Home.hx └── util ├── FileTools.hx ├── HtmlTools.hx └── OptionTools.hx /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bin/web 3 | bin/native 4 | .tmp 5 | .sass-cache 6 | bin/web/app/bower_components 7 | cloud-explorer.swf 8 | cloud-explorer.js 9 | test.swf 10 | test.js 11 | *.js.map 12 | *.sublime-project 13 | *.sublime-workspace 14 | *.DS_Store 15 | export/ 16 | all-tests.js 17 | all-tests.swf 18 | all-tests.js.map 19 | Thumbs.db -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2013-11-01 using generator-webapp 0.4.3 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | module.exports = function (grunt) { 11 | // show elapsed time at the end 12 | require('time-grunt')(grunt); 13 | // load all grunt tasks 14 | require('load-grunt-tasks')(grunt); 15 | 16 | grunt.initConfig({ 17 | // configurable paths 18 | yeoman: { 19 | app: 'app', 20 | dist: 'bin/web', 21 | native: 'bin/native', 22 | heroku: '../heroku' 23 | }, 24 | watch: { 25 | compass: { 26 | files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 27 | tasks: ['compass:server', 'autoprefixer'] 28 | }, 29 | styles: { 30 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'], 31 | tasks: ['copy:styles', 'autoprefixer'] 32 | }, 33 | livereload: { 34 | options: { 35 | livereload: '<%= connect.options.livereload %>' 36 | }, 37 | files: [ 38 | '<%= yeoman.app %>/*.html', 39 | '<%= yeoman.app %>/*.swf', 40 | '.tmp/styles/{,*/}*.css', 41 | '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', 42 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 43 | ] 44 | } 45 | }, 46 | connect: { 47 | options: { 48 | port: 9000, 49 | livereload : false, 50 | /*livereload: 35729,*/ 51 | // change this to '0.0.0.0' to access the server from outside 52 | hostname: 'localhost' 53 | }, 54 | livereload: { 55 | options: { 56 | livereload : false, 57 | open: true, 58 | base: [ 59 | '.tmp', 60 | '<%= yeoman.app %>' 61 | ] 62 | } 63 | }, 64 | /*test: { 65 | options: { 66 | base: [ 67 | '.tmp', 68 | 'test', 69 | '<%= yeoman.app %>' 70 | ] 71 | } 72 | },*/ 73 | test : { 74 | options: { 75 | open : true, 76 | keepalive:true, 77 | base: ['test/bin'] 78 | } 79 | }, 80 | dist: { 81 | options: { 82 | open: true, 83 | base: '<%= yeoman.dist %>' 84 | } 85 | } 86 | }, 87 | clean: { 88 | heroku: { 89 | options: { force: true }, 90 | files: [{ 91 | dot: true, 92 | src: [ 93 | '.tmp', 94 | '<%= yeoman.heroku %>/app/*', 95 | '!<%= yeoman.heroku %>/app/.git*' 96 | ] 97 | }] 98 | }, 99 | dist: { 100 | files: [{ 101 | dot: true, 102 | src: [ 103 | '.tmp', 104 | '<%= yeoman.dist %>/*', 105 | '!<%= yeoman.dist %>/.git*' 106 | ] 107 | }] 108 | }, 109 | native : '<%= yeoman.native %>', 110 | server: '.tmp' 111 | }, 112 | jshint: { 113 | options: { 114 | jshintrc: '.jshintrc' 115 | }, 116 | all: [ 117 | 'Gruntfile.js', 118 | '<%= yeoman.app %>/scripts/{,*/}*.js', 119 | '!<%= yeoman.app %>/scripts/vendor/*', 120 | 'test/spec/{,*/}*.js' 121 | ] 122 | }, 123 | mocha: { 124 | all: { 125 | options: { 126 | run: true, 127 | urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html'] 128 | } 129 | } 130 | }, 131 | compass: { 132 | options: { 133 | sassDir: '<%= yeoman.app %>/styles', 134 | cssDir: '.tmp/styles', 135 | generatedImagesDir: '.tmp/images/generated', 136 | imagesDir: '<%= yeoman.app %>/images', 137 | javascriptsDir: '<%= yeoman.app %>/scripts', 138 | //importPath: '<%= yeoman.app %>/bower_components', 139 | httpImagesPath: '/images', 140 | httpGeneratedImagesPath: '/images/generated', 141 | relativeAssets: false, 142 | assetCacheBuster: false 143 | }, 144 | dist: { 145 | options: { 146 | generatedImagesDir: '<%= yeoman.dist %>/images/generated' 147 | } 148 | }, 149 | server: { 150 | options: { 151 | debugInfo: false 152 | } 153 | } 154 | }, 155 | autoprefixer: { 156 | options: { 157 | browsers: ['last 1 version'] 158 | }, 159 | dist: { 160 | files: [{ 161 | expand: true, 162 | cwd: '.tmp/styles/', 163 | src: '{,*/}*.css', 164 | dest: '.tmp/styles/' 165 | }] 166 | } 167 | }, 168 | // not used since Uglify task does concat, 169 | // but still available if needed 170 | /*concat: { 171 | dist: { 172 | 173 | } 174 | },*/ 175 | /*'bower-install': { 176 | app: { 177 | html: '<%= yeoman.app %>/cloud-explorer.html', 178 | ignorePath: '<%= yeoman.app %>/' 179 | } 180 | },*/ 181 | // not enabled since usemin task does concat and uglify 182 | // check index.html to edit your build targets 183 | // enable this task if you prefer defining your build targets here 184 | /*uglify: { 185 | dist: {} 186 | },*/ 187 | rev: { 188 | dist: { 189 | files: { 190 | src: [ 191 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 192 | '<%= yeoman.dist %>/styles/{,*/}*.css', 193 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}' 194 | ] 195 | } 196 | } 197 | }, 198 | useminPrepare: { 199 | options: { 200 | dest: '<%= yeoman.dist %>' 201 | }, 202 | html: '<%= yeoman.app %>/cloud-explorer.html' 203 | }, 204 | usemin: { 205 | options: { 206 | dirs: ['<%= yeoman.dist %>'] 207 | }, 208 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 209 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'] 210 | }, 211 | // imagemin: { 212 | // dist: { 213 | // files: [{ 214 | // expand: true, 215 | // cwd: '<%= yeoman.app %>/images', 216 | // src: '{,*/}*.{png,jpg,jpeg}', 217 | // dest: '<%= yeoman.dist %>/images' 218 | // }] 219 | // } 220 | // }, 221 | svgmin: { 222 | dist: { 223 | files: [{ 224 | expand: true, 225 | cwd: '<%= yeoman.app %>/images', 226 | src: '{,*/}*.svg', 227 | dest: '<%= yeoman.dist %>/images' 228 | }] 229 | } 230 | }, 231 | cssmin: { 232 | // This task is pre-configured if you do not wish to use Usemin 233 | // blocks for your CSS. By default, the Usemin block from your 234 | // `index.html` will take care of minification, e.g. 235 | // 236 | // 237 | // 238 | // dist: { 239 | // files: { 240 | // '<%= yeoman.dist %>/styles/main.css': [ 241 | // '.tmp/styles/{,*/}*.css', 242 | // '<%= yeoman.app %>/styles/{,*/}*.css' 243 | // ] 244 | // } 245 | // } 246 | }, 247 | htmlmin: { 248 | dist: { 249 | options: { 250 | /*removeCommentsFromCDATA: true, 251 | // https://github.com/yeoman/grunt-usemin/issues/44 252 | //collapseWhitespace: true, 253 | collapseBooleanAttributes: true, 254 | removeAttributeQuotes: true, 255 | removeRedundantAttributes: true, 256 | useShortDoctype: true, 257 | removeEmptyAttributes: true, 258 | removeOptionalTags: true*/ 259 | removeOptionalTags : false 260 | }, 261 | files: [{ 262 | expand: true, 263 | cwd: '<%= yeoman.app %>', 264 | src: '*.html', 265 | dest: '<%= yeoman.dist %>' 266 | }] 267 | } 268 | }, 269 | // Put files not handled in other tasks here 270 | copy: { 271 | heroku: { 272 | files: [{ 273 | expand: true, 274 | dot: true, 275 | cwd: '<%= yeoman.app %>', 276 | dest: '<%= yeoman.heroku %>/app/', 277 | src: [ 278 | 'scripts/*.js', 279 | 'cloud-explorer.html', 280 | 'oauth-cb.html', 281 | 'demo.html', 282 | '*.{ico,png,txt}', 283 | '.htaccess', 284 | 'images/{,*/}*.{webp,gif}', 285 | 'fonts/{,*/}*.*', 286 | 'cloud-explorer.swf', 287 | 'images/{,*/}*.{png,jpg,jpeg}' 288 | ] 289 | }, 290 | // needed as we don't use cssmin anymore 291 | { 292 | expand: true, 293 | dot: true, 294 | cwd: '.tmp', 295 | dest: '<%= yeoman.heroku %>/app/', 296 | src: [ 297 | 'styles/cloud-explorer.css' 298 | ] 299 | }] 300 | }, 301 | dist: { 302 | files: [{ 303 | expand: true, 304 | dot: true, 305 | cwd: '<%= yeoman.app %>', 306 | dest: '<%= yeoman.dist %>', 307 | src: [ 308 | 'scripts/*.js', 309 | 'cloud-explorer.html', 310 | 'oauth-cb.html', 311 | '*.{ico,png,txt}', 312 | '.htaccess', 313 | 'images/{,*/}*.{webp,gif}', 314 | 'fonts/{,*/}*.*', 315 | 'cloud-explorer.swf', 316 | 'images/{,*/}*.{png,jpg,jpeg}' 317 | ] 318 | }, 319 | // needed as we don't use cssmin anymore 320 | { 321 | expand: true, 322 | dot: true, 323 | cwd: '.tmp', 324 | dest: '<%= yeoman.dist %>', 325 | src: [ 326 | 'styles/cloud-explorer.css' 327 | ] 328 | }] 329 | }, 330 | styles: { 331 | expand: true, 332 | dot: true, 333 | cwd: '<%= yeoman.app %>/styles', 334 | dest: '.tmp/styles/', 335 | src: '{,*/}*.css' 336 | }, 337 | 338 | native : { 339 | expand: true, 340 | dot: true, 341 | cwd: '<%= yeoman.dist %>', 342 | dest: '<%= yeoman.native %>', 343 | src: [ 344 | 'cloud-explorer.html', 345 | 'images/{,*/}*.png', 346 | 'styles/{,*/}*.css' 347 | ] 348 | } 349 | 350 | }, 351 | concurrent: { 352 | server: [ 353 | 'compass', 354 | 'copy:styles' 355 | ], 356 | test: [ 357 | 'copy:styles' 358 | ], 359 | dist: [ 360 | 'compass', 361 | 'copy:styles', 362 | //'imagemin', 363 | 'svgmin'//, 364 | //'htmlmin' // don't htmlmin as it breaks XHTML 365 | ] 366 | }, 367 | haxe : { 368 | build : { 369 | hxml : "build.hxml" 370 | }, 371 | test : { 372 | hxml : "test.hxml" 373 | } 374 | } 375 | }); 376 | 377 | /* 378 | grunt.registerTask('run', 'Start Silex', function () { 379 | var server = require('./dist/server/server.js'); 380 | console.log('Start Silex', server); 381 | }); 382 | */ 383 | // run haxe and build Cloud Explorer 384 | grunt.registerTask('npmHaxeBuild', 'Run haxe to build Cloud Explorer', function () { 385 | var buildCommand = 'node node_modules/haxe/bin/haxe-cli.js build.hxml'; 386 | console.log('Run haxe and build Cloud Explorer'); 387 | console.log('Executing', buildCommand); 388 | var cp = require('child_process') 389 | cp.exec(buildCommand); 390 | 391 | grunt.task.run([ 392 | 'clean:dist', 393 | 'useminPrepare', 394 | 'concurrent:dist', 395 | 'autoprefixer', 396 | 'concat', 397 | 'copy:dist', 398 | 'clean:native', 399 | 'copy:native' 400 | ]); 401 | }); 402 | 403 | grunt.registerTask('server', function (target) { 404 | 405 | var server = require('./server/unifile.js'); 406 | console.log('Start unifile', server); 407 | 408 | if (target === 'dist') { 409 | return grunt.task.run(['build', 'connect:dist:keepalive']); 410 | } 411 | 412 | grunt.task.run([ 413 | 'clean:server', 414 | 'haxe:build', 415 | 'concurrent:server', 416 | 'autoprefixer', 417 | //'connect:livereload', 418 | 'watch' 419 | ]); 420 | }); 421 | 422 | /*grunt.registerTask('test', [ 423 | 'clean:server', 424 | 'concurrent:test', 425 | 'autoprefixer', 426 | 'connect:test', 427 | 'mocha' 428 | ]);*/ 429 | 430 | grunt.registerTask('test', [ 431 | 'haxe:test', 432 | 'connect:test' 433 | ]); 434 | 435 | grunt.registerTask('build', [ 436 | 'clean:dist', 437 | 'haxe:build', 438 | 'useminPrepare', 439 | 'concurrent:dist', 440 | 'autoprefixer', 441 | 'concat', 442 | //'cssmin', 443 | //'uglify', 444 | //'modernizr', 445 | 'copy:dist', 446 | 'clean:native', 447 | 'copy:native' 448 | //'copy:styles', 449 | //'rev', 450 | //'usemin' 451 | ]); 452 | 453 | grunt.registerTask('heroku', [ 454 | 'clean:heroku', 455 | 'haxe:build', 456 | 'useminPrepare', 457 | 'concurrent:dist', 458 | 'autoprefixer', 459 | 'concat', 460 | 'copy:heroku' 461 | ]); 462 | 463 | grunt.registerTask('default', [ 464 | //'jshint', 465 | //'test', 466 | 'build' 467 | ]); 468 | }; 469 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Cloud Explorer, file picker for the cloud 2 | 3 | Cloud Explorer enables your application or website users with picking their files from the cloud. 4 | 5 | Here is [cloud explorer official website](http://cloud-explorer.org/). 6 | 7 | Cloud Explorer is a free and open source project [powered by Silex Labs](http://www.silexlabs.org/). 8 | 9 | Authored by Thomas Fétiveau [@zab0jad](https://twitter.com/zab0jad) and Alex Hoyau [@lexoyo](https://twitter.com/lexoyo). 10 | 11 | ##About Cloud Explorer 12 | 13 | Cloud Explorer aims to provide an open source client library that exposes the same API as the [File Picker API](https://www.filepicker.com/documentation/file-ingestion/javascript-api). 14 | 15 | Cloud Explorer is the front end client of the [unifile](https://github.com/silexlabs/unifile) backend, a nodejs server which provides a unified access to cloud services. This project uses nodejs and those modules: express, dbox, express, googleapis, logger, node-oauth, oauth, path. 16 | 17 | The backend is in node.js and the front end is in Javascript. Cloud Explorer is written with [Haxe](http://www.haxe.org), enabling a modern and elegant syntax as well as a strong typed and more reliable javascript source code. 18 | 19 | The project is not mature yet and doesn't provide half of what is provided by File Picker. It's however under constant development and will provide more and more of the IPF API every week plus some extra features we've found useful for our projects but that were not offered by IPF. 20 | 21 | Cloud Explorer is also skinable with CSS and hostable in house. 22 | 23 | ###Discussions 24 | 25 | * Facebook http://www.facebook.com/silexlabs 26 | * Twitter https://twitter.com/silexlabs 27 | * Google plus https://plus.google.com/communities/107373636457908189681 28 | 29 | ##Setup 30 | 31 | ### Development or Test 32 | 33 | Prerequisite : 34 | 35 | * [node.js](http://nodejs.org/) installed 36 | * [NPM](https://npmjs.org/) installed 37 | * [Haxe compiler](http://haxe.org/download) installed 38 | 39 | Cloud Explorer default development environment uses [grunt](http://gruntjs.com/) (nodejs), [compass](http://compass-style.org/) and few other little tools that aim to make developping CE easier and faster. 40 | 41 | For early testers and contributors, here are the setup steps to follow in order to run this version of CE: 42 | 43 | * git clone this branch on your local file system, 44 | ``` 45 | git clone git@github.com:silexlabs/cloud-explorer.git cloud-explorer 46 | ``` 47 | 48 | * run the following command in a terminal to install the nodejs dependencies: 49 | ``` 50 | npm install 51 | ``` 52 | 53 | * run the following command in a terminal to start the local server: 54 | ``` 55 | grunt server 56 | ``` 57 | 58 | * compile the haxe js sources 59 | ``` 60 | haxe build.hxml 61 | ``` 62 | 63 | * open your favorite HTML5 browser on http://localhost:6805/ to have the test page displayed. From there, click the buttons corresponding to the API functions you want to test. Some of them are not yet implemented. 64 | 65 | ### Production 66 | 67 | To install and use Cloud Explorer in your projects, follow those steps : 68 | 69 | * compile the Cloud Explorer library out of this repository: 70 | ``` 71 | grunt 72 | ``` 73 | You will then find the full Cloud Explorer library under bin/web. Place the entire bin/web directory in a subdirectory of your web app project (you may rename web into cloud-explorer). 74 | 75 | * include the Cloud Explorer javascript file in your web page: 76 | ``` 77 | 78 | 79 | 80 | My project 81 | 82 | 83 | 84 | 85 | (...) 86 | 87 | 88 | 89 | ``` 90 | We assume that you've pasted the Cloud Explorer lib files in the cloud-explorer sub directory. 91 | 92 | * To use it from your project js code, first initialize a Cloud Explorer instance: 93 | ``` 94 | window.document.onload = function(e){ 95 | 96 | window.cloudExplorer = ce.api.CloudExplorer.get(); 97 | } 98 | ``` 99 | 100 | Note that you can also precise the iframe element id that will be used by Cloud Explorer. If not specified, one will be automatically generated. 101 | ``` 102 | (...) 103 | 109 | (...) 110 | 116 | ``` 117 | 118 | * You will then be able to call it like you would call File Picker: 119 | ``` 120 | cloudExplorer.pick(function(b){ 121 | 122 | console.log("my Blob: " + JSON.stringify(b)); 123 | 124 | }, function(e){ console.log("error " + JSON.stringify(e)); }); 125 | ``` 126 | 127 | #### Configuration 128 | 129 | To pass configuration variables to Cloud Explorer, specify the iframe it will use as described earlier: 130 | ``` 131 | 138 | ``` 139 | This will allow you to add the below supported configuration properties: 140 | 141 | * data-ce-unifile-url: the url of the unifile root endpoint your instance of Cloud Explorer will use. 142 | * data-ce-path: path to the folder containing the cloud-explorer files on your server. 143 | 144 | ## Current implementation state and roadmap 145 | 146 | ### Currently supported cloud services 147 | 148 | * hosting server (www), transfer files from and to your own unifile server 149 | * [Dropbox](http://www.dropbox.com) 150 | 151 | More to come soon... 152 | 153 | ### Currently supported features 154 | 155 | The currently implemented part of the IPF API in Cloud Explorer consists of: 156 | 157 | * [CEBlob](https://www.filepicker.com/documentation/file-ingestion/javascript-api/blob) 158 | 159 | Supported fields: url, filename, mimetype, size 160 | 161 | Other fields will return null. 162 | 163 | * [Pick Files](https://www.filepicker.com/documentation/file-ingestion/javascript-api/pick) 164 | ``` 165 | cloudExplorer.pick(function(b){ 166 | 167 | currentBlob = b; 168 | 169 | textarea.val(textarea.val() + "\ncurrentBlob: " + JSON.stringify(currentBlob)); 170 | 171 | }, function(e){ console.log("error " + JSON.stringify(e)); }); 172 | ``` 173 | 174 | No option supported yet. Will just pick a file from your favorite cloud service and give back a CEBlob instance. 175 | 176 | 177 | * [Export](https://www.filepicker.com/documentation/file-export/javascript-api/export) 178 | ``` 179 | cloudExplorer.exportFile(currentBlob, { mimetype: "text/html" }, function(b){ 180 | 181 | currentBlob = b; 182 | 183 | textarea.val(textarea.val() + "\ncurrentBlob is now: " + JSON.stringify(currentBlob)); 184 | 185 | }, function(e){ console.log("error " + JSON.stringify(e)); }); 186 | ``` 187 | 188 | Supported options are: mimetype, extension. 189 | 190 | This function doesn't work exactly like the IFP yet as it will need a call to write() after the call to export() to actually write the file. For now, it will just generate a CEBlob instance corresponding to the new file you want to create/store. 191 | 192 | * [Write back to a file](https://www.filepicker.com/documentation/file-ingestion/javascript-api/write) 193 | ``` 194 | cloudExplorer.write(currentBlob, "write() test succeeded", function(ceb){ 195 | 196 | currentBlob = ceb; 197 | 198 | textarea.val(textarea.val() + "\nwrite operation successful!"); 199 | 200 | }, function(e){ console.log("error " + JSON.stringify(e)); }); 201 | ``` 202 | 203 | No option supported yet. 204 | * [Read Files](https://www.filepicker.com/documentation/file-ingestion/javascript-api/read) 205 | 206 | ``` 207 | cloudExplorer.read(currentBlob, function(d){ 208 | 209 | textarea.val(textarea.val() + "\nread data: " + d); 210 | 211 | }, function(e){ console.log("error " + JSON.stringify(e)); }); 212 | ``` 213 | 214 | No option supported yet. 215 | 216 | ### Roadmap 217 | 218 | Current version is 1.0. It is a complete refactoring of the previous 0.1 version that was dependant on JQuery and AngularJS. Version 1.0 has no client side dependency and is implemented with Haxe javascript, allowing future ports of the basecode to native mobile/desktop, Flash/AIR, ... 219 | 220 | The goals of version 1.1 are simple: implement the full File Picker API (web version). 221 | 222 | Then we will probably make SDKs for Android and iOS apps. 223 | 224 | ## Contributions 225 | 226 | We love contributions and consider all kind of pull requests: 227 | 228 | * new themes or improvments of existing default theme 229 | * new components or functionnalities 230 | * additions to the documentation 231 | * bug reports, fixes 232 | * any idea or suggestion 233 | 234 | You can also contribute to the [unifile library](https://github.com/silexlabs/unifile#developer-guide) in order to add more cloud services to Cloud Explorer 235 | 236 | > See the [instructions to contribute to unifile](https://github.com/silexlabs/unifile#developer-guide) 237 | 238 | ## Contributors 239 | 240 | We are all members of [Silex Labs foundation](http://www.silexlabs.org). 241 | 242 | ![Silex Labs](http://www.silexlabs.org/wp-content/themes/parallelus-salutation/assets/images/logo-silexlabs-grey.png) 243 | 244 | Feel free to contact us through twitter or linkedin 245 | 246 | ![Thomas Fétiveau](https://media.licdn.com/mpr/mpr/shrink_200_200/p/1/005/093/2f0/2330672.jpg) Thomas Fétiveau [@zab0jad](https://twitter.com/zab0jad) [Web & mobile Developer](https://www.linkedin.com/in/thomasfetiveau) 247 | 248 | ![Alex Hoyau, javascript developer](http://m.c.lnkd.licdn.com/mpr/mpr/shrink_200_200/p/2/005/099/3b6/06c77af.jpg) Alex Hoyau [@lexoyo](https://twitter.com/lexoyo) [Front end architect](https://www.linkedin.com/in/webappdev/) 249 | 250 | ![Pol Goasdoué, freelance UI designer](https://media.licdn.com/mpr/mpr/shrink_200_200/p/4/005/04b/36b/164e5af.jpg) Pol Goasdoué [@superwup](https://twitter.com/superwup) [freelance UI designer](http://superwup.me) 251 | 252 | 253 | 254 | ##Licensing 255 | 256 | Cloud Explorer is licensed under the MIT license. 257 | 258 | ##Screenshots 259 | 260 | ![Cloud explorer user interface](https://raw.github.com/silexlabs/cloud-explorer/master/screenshot01.png) 261 | ![Cloud explorer user interface](https://raw.github.com/silexlabs/cloud-explorer/master/screenshot02.png) 262 | ![Cloud explorer user interface](https://raw.github.com/silexlabs/cloud-explorer/master/screenshot03.png) 263 | ![Cloud explorer user interface](https://raw.github.com/silexlabs/cloud-explorer/master/screenshot04.png) 264 | ![Cloud explorer user interface](https://raw.github.com/silexlabs/cloud-explorer/master/screenshot05.png) 265 | ![Cloud explorer user interface](https://raw.github.com/silexlabs/cloud-explorer/master/screenshot06.png) 266 | 267 | 268 | -------------------------------------------------------------------------------- /app/cloud-explorer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Cloud Explorer 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 |
22 | 26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 |
41 | 42 |
loading...
43 | 44 |
45 | 46 |
47 |
48 | CLICK HERE to authorize Cloud Explorer to use your {srvName} account. 49 |
50 |
51 | 52 |
53 | 54 | Click on a service in the list below to connect it. 55 | 56 | 57 | 58 |
59 | 60 |
61 | 62 |
63 | 64 | 65 | 66 |
    • Log out from {srvName}
67 | 68 |
69 | 70 |
71 | 72 | 75 | 76 |
77 |
78 | Name 79 | Last update 80 | Type 81 |
82 |
83 |
    84 |
  • 85 |
  • 86 | 87 | 88 | 89 | 90 | folder 91 |
  • 92 |
  • 93 | 94 | 95 | 96 | 97 | 98 |
  • 99 |
100 |
101 |
102 | 103 |
104 | 105 |

Drop your file(s) here

106 | or 107 |
108 | 109 |
110 | 111 |
112 | 113 |
114 | 115 | 125 | 126 |
127 | 128 | -------------------------------------------------------------------------------- /app/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cloud Explorer 5 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 88 | 89 |
90 |

Welcome to the Cloud Explorer demo

91 |

Thank you for testing Cloud Explorer!

92 |

Please click the following button to start the demo:

93 |
94 | 95 |
96 |

Open a file from the cloud

97 |

Pick a file, image or text content, from a cloud storage:

98 |

Great ! Here is your file's content:

99 |

Click here to continue:

100 |
101 | 102 |
103 |

Create some content and save it in the cloud

104 |

Write some text in the below input:

105 | 106 |

And save it in a new file :

107 |

File successfully saved with content:

108 |

Click here to continue:

109 |
110 | 111 |
112 |

Edit your file content and save the changes

113 |

Now, edit your previously created file's content in the below input:

114 | 115 |

And save those changes to the cloud :

116 |

File successfully saved with new content:

117 |

Click here to continue:

118 |
119 | 120 |
121 |

Thank you for having tested Cloud Explorer

122 |

For suggestion, bug report, contribution or anything about the project, please use the Github issue tracker.

123 |

To restart the demo, click here:

124 |
125 | 126 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /app/images/default/breadcrumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/breadcrumb.png -------------------------------------------------------------------------------- /app/images/default/btn-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/btn-close.png -------------------------------------------------------------------------------- /app/images/default/btn-icon-items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/btn-icon-items.png -------------------------------------------------------------------------------- /app/images/default/btn-item-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/btn-item-delete.png -------------------------------------------------------------------------------- /app/images/default/btn-item-rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/btn-item-rename.png -------------------------------------------------------------------------------- /app/images/default/btn-item-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/btn-item-select.png -------------------------------------------------------------------------------- /app/images/default/btn-list-items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/btn-list-items.png -------------------------------------------------------------------------------- /app/images/default/btn-new-folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/btn-new-folder.png -------------------------------------------------------------------------------- /app/images/default/btn-parent-folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/btn-parent-folder.png -------------------------------------------------------------------------------- /app/images/default/btn-sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/btn-sort.png -------------------------------------------------------------------------------- /app/images/default/ce-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/ce-header.png -------------------------------------------------------------------------------- /app/images/default/dropzone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/dropzone.png -------------------------------------------------------------------------------- /app/images/default/home-dropbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/home-dropbox.png -------------------------------------------------------------------------------- /app/images/default/home-ftp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/home-ftp.png -------------------------------------------------------------------------------- /app/images/default/home-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/home-github.png -------------------------------------------------------------------------------- /app/images/default/home-open-pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/home-open-pages.png -------------------------------------------------------------------------------- /app/images/default/home-webdav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/home-webdav.png -------------------------------------------------------------------------------- /app/images/default/home-www.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/home-www.png -------------------------------------------------------------------------------- /app/images/default/icon-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/icon-file.png -------------------------------------------------------------------------------- /app/images/default/icon-folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/icon-folder.png -------------------------------------------------------------------------------- /app/images/default/icon-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/icon-image.png -------------------------------------------------------------------------------- /app/images/default/icon-sound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/icon-sound.png -------------------------------------------------------------------------------- /app/images/default/icon-video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/icon-video.png -------------------------------------------------------------------------------- /app/images/default/srv-dropbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/srv-dropbox.png -------------------------------------------------------------------------------- /app/images/default/srv-ftp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/srv-ftp.png -------------------------------------------------------------------------------- /app/images/default/srv-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/srv-github.png -------------------------------------------------------------------------------- /app/images/default/srv-open-pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/srv-open-pages.png -------------------------------------------------------------------------------- /app/images/default/srv-state-connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/srv-state-connected.png -------------------------------------------------------------------------------- /app/images/default/srv-state-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/srv-state-disabled.png -------------------------------------------------------------------------------- /app/images/default/srv-state-disconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/srv-state-disconnected.png -------------------------------------------------------------------------------- /app/images/default/srv-webdav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/srv-webdav.png -------------------------------------------------------------------------------- /app/images/default/srv-www.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/app/images/default/srv-www.png -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cloud Explorer 5 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /app/oauth-cb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cloud Explorer - authentication 5 | 6 | 7 | 14 | 15 | -------------------------------------------------------------------------------- /app/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /app/styles/base.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | 3 | font-family: sans-serif; 4 | margin: 0; 5 | padding: 0; 6 | color: #000; 7 | height: 100%; width: 100%; 8 | } 9 | 10 | @import "platforms.scss"; 11 | 12 | .placeholder { 13 | 14 | display: none; 15 | } 16 | 17 | .explorer { 18 | 19 | width: 100%; height: 100%; 20 | } 21 | 22 | .explorer { 23 | 24 | .loader, 25 | .closeBtn, 26 | .logoutBtn, 27 | .breadcrumb, 28 | .export, 29 | .home, 30 | .fileBrowser, 31 | .alertPopup, 32 | .authPopup, 33 | .footer, 34 | .parentFolderBtn, 35 | .newFolderBtn, 36 | .listItemsBtn, 37 | .iconItemsBtn, 38 | .deleteBtn, 39 | .fileBrowser .files ul .folder.new 40 | { 41 | display: none; 42 | } 43 | 44 | 45 | /** 46 | 47 | Application states 48 | 49 | **/ 50 | 51 | &.loading { 52 | 53 | .loader { 54 | 55 | display: block; 56 | } 57 | .fileBrowser { 58 | 59 | .dropzone { 60 | 61 | display: none; 62 | } 63 | } 64 | } 65 | 66 | &.starting { 67 | 68 | .home { 69 | 70 | display: block; 71 | } 72 | .closeBtn { 73 | 74 | display: inline-block; 75 | } 76 | } 77 | 78 | &.browsing { 79 | 80 | .fileBrowser { 81 | 82 | display: block; 83 | } 84 | .parentFolderBtn, 85 | .newFolderBtn, 86 | .listItemsBtn, 87 | .iconItemsBtn, 88 | .closeBtn { 89 | 90 | display: inline-block; 91 | } 92 | .breadcrumb { 93 | 94 | display: block; 95 | } 96 | .export { 97 | 98 | button { 99 | 100 | display: none; 101 | } 102 | } 103 | } 104 | 105 | &.browsing.single-file-sel-mode { 106 | 107 | 108 | } 109 | 110 | &.browsing.single-file-exp-mode { 111 | 112 | .footer { 113 | 114 | display: block; 115 | } 116 | .export { 117 | 118 | display: inline-block; 119 | 120 | .saveBtn { 121 | 122 | display: inline-block; 123 | } 124 | .overwriteBtn { 125 | 126 | display: none; 127 | } 128 | } 129 | 130 | &.export-overwriting { 131 | 132 | .export { 133 | 134 | .saveBtn { 135 | 136 | display: none; 137 | } 138 | .overwriteBtn { 139 | 140 | display: inline-block; 141 | } 142 | } 143 | } 144 | } 145 | 146 | &.browsing.making-new-folder { 147 | 148 | .fileBrowser .files ul .folder.new { 149 | 150 | display: block; 151 | } 152 | } 153 | 154 | &.authorizing { 155 | 156 | .authPopup { 157 | 158 | display: block; 159 | } 160 | } 161 | 162 | &.alerting { 163 | 164 | .alertPopup { 165 | 166 | display: block; 167 | } 168 | } 169 | 170 | &.loggedin { 171 | 172 | .logoutBtn { 173 | 174 | display: block; 175 | } 176 | } 177 | 178 | 179 | /** 180 | 181 | Components states 182 | 183 | **/ 184 | 185 | .fileBrowser { 186 | 187 | .files { 188 | 189 | .list { 190 | 191 | ul { 192 | 193 | li { 194 | 195 | input[type="text"] { 196 | 197 | display: none; 198 | } 199 | 200 | &.renaming { 201 | 202 | .fileName { 203 | 204 | display: none; 205 | } 206 | input[type="text"] { 207 | 208 | display: inline-block; 209 | } 210 | } 211 | } 212 | li.new { 213 | 214 | input[type="text"] { 215 | 216 | display: inline-block; 217 | } 218 | } 219 | } 220 | } 221 | } 222 | } 223 | 224 | &.selecting { 225 | 226 | .footer { 227 | 228 | display: block; 229 | } 230 | button.deleteBtn { 231 | 232 | display: inline-block; 233 | } 234 | } 235 | } -------------------------------------------------------------------------------- /app/styles/cloud-explorer.scss: -------------------------------------------------------------------------------- 1 | @import "base.scss"; 2 | 3 | @import "default/theme.scss"; -------------------------------------------------------------------------------- /app/styles/default/README.md: -------------------------------------------------------------------------------- 1 | default theme for cloud explorer -------------------------------------------------------------------------------- /app/styles/default/theme.scss: -------------------------------------------------------------------------------- 1 | ul { margin: 0; padding: 0; list-style: none; } 2 | 3 | *, *:before, *:after { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } 4 | 5 | body, input[type="text"] { 6 | font-family: 'Roboto', arial, sans-serif; font-size: 14px; color: #727271; 7 | } 8 | 9 | button:focus { outline: 0; } /* avoid default blue outline on chrome */ 10 | 11 | .explorer-bg { 12 | 13 | background-color: black; opacity: .5; 14 | position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; 15 | } 16 | 17 | .explorer { 18 | 19 | position: relative; top: 50%; margin: auto; margin-top: -330px; 20 | height: 660px; width: 80%; min-width: 640px; 21 | background-color: white; opacity: 1; border-radius: 5px; 22 | overflow: hidden; 23 | 24 | .loader, .alertPopup { 25 | 26 | position: absolute; top: 0; left: 0; z-index: 99; 27 | width: 100%; height: 100%; vertical-align: middle; 28 | 29 | div.bg { 30 | position: absolute; top: 0; left: 0; z-index: -1; 31 | width: 100%; height: 100%; 32 | background-color: black; opacity: .5; 33 | } 34 | div.txt { 35 | position: absolute; 36 | top: 50%; 37 | left: 50%; width: 450px; margin-left: -225px; 38 | padding: 20px; 39 | background-color: white; 40 | opacity: 1; border-radius: 10px; 41 | text-align: center; 42 | font: { 43 | size: 20px; 44 | } 45 | } 46 | } 47 | .loader { 48 | div.txt { 49 | height: 80px; margin-top: -40px; 50 | } 51 | } 52 | .alertPopup { 53 | 54 | div.txt { 55 | 56 | height: 160px; margin-top: -80px; 57 | 58 | .choice { 59 | padding: 6px; margin: 2px; 60 | cursor: pointer; 61 | user-select: none; 62 | border-radius: 5px; 63 | } 64 | .choice:nth-of-type(1) { 65 | border: 2px solid red; 66 | } 67 | .choice:nth-of-type(2) { 68 | border: 2px solid green; 69 | } 70 | .choice:nth-of-type(1):hover { 71 | background: red; color: #FFF; 72 | } 73 | .choice:nth-of-type(2):hover { 74 | background: green; color: #FFF; 75 | } 76 | } 77 | } 78 | .alertPopup.warning { 79 | 80 | div.txt { 81 | 82 | background-color: orange; 83 | } 84 | } 85 | .alertPopup.error { 86 | 87 | div.txt { 88 | 89 | background-color: red; 90 | } 91 | } 92 | 93 | .home { 94 | 95 | text-align: center; 96 | 97 | span { 98 | display: inline-block; 99 | font-size: 20px; margin-top: 180px; 100 | } 101 | ul { 102 | display: block; width: 400px; 103 | margin: auto; margin-top: 15px; 104 | li { 105 | display: inline-block; 106 | width: 110px; height: 110px; 107 | vertical-align: top; 108 | margin: 10px; 109 | cursor: pointer; 110 | user-select: none; 111 | padding-top: 90px; 112 | background: { 113 | repeat: no-repeat; 114 | size: auto 110px; 115 | position: 0px -8px; 116 | } 117 | border: 1px solid 34b7ea; 118 | font: { 119 | size: 16; 120 | } 121 | } 122 | li:hover { 123 | background-position: -110px -8px; 124 | border: 1px solid 288cb3; 125 | } 126 | li:active { 127 | background-position: -220px -8px; 128 | border: 1px solid 195a73; 129 | } 130 | li.dropbox { 131 | background-image: url('../images/default/home-dropbox.png'); 132 | } 133 | li.www { 134 | background-image: url('../images/default/home-www.png'); 135 | } 136 | li.ftp { 137 | background-image: url('../images/default/home-ftp.png'); 138 | } 139 | li.webdav { 140 | background-image: url('../images/default/home-webdav.png'); 141 | } 142 | li.github { 143 | background-image: url('../images/default/home-github.png'); 144 | } 145 | li.open-pages{ 146 | background-image: url('../images/default/home-open-pages.png'); 147 | } 148 | } 149 | } 150 | 151 | .header { 152 | 153 | position: relative; z-index: 2; 154 | height: 104px; 155 | background: { 156 | color: #282828; 157 | image: url('../images/default/ce-header.png'); 158 | repeat: no-repeat; 159 | size: auto 70%; 160 | position: 20px 50%; 161 | } 162 | .controls { 163 | 164 | position: absolute; top: 26px; right: 10px; 165 | width: 310px; height: 52px; 166 | 167 | button { 168 | 169 | float:left; 170 | width: 52px; height: 52px; 171 | padding: 0; border: 0; margin: 0; 172 | background-repeat: no-repeat; 173 | background-position: 0px 0px; 174 | background-color: transparent; 175 | cursor: pointer; 176 | } 177 | button:hover { 178 | 179 | background-position: -52px 0px; 180 | } 181 | button:active { 182 | 183 | background-position: -104px 0px; 184 | } 185 | button:disabled { 186 | 187 | background-position: -208px 0px; 188 | cursor: not-allowed; 189 | } 190 | button.parentFolderBtn { 191 | 192 | background-image: url('../images/default/btn-parent-folder.png'); 193 | margin-right: 10px; 194 | } 195 | button.newFolderBtn { 196 | 197 | background-image: url('../images/default/btn-new-folder.png'); 198 | margin-right: 20px; 199 | } 200 | button.listItemsBtn { 201 | 202 | background-image: url('../images/default/btn-list-items.png'); 203 | margin-right: 10px; 204 | } 205 | button.iconItemsBtn { 206 | 207 | background-image: url('../images/default/btn-icon-items.png'); 208 | margin-right: 10px; 209 | } 210 | button.closeBtn { 211 | 212 | background-image: url('../images/default/btn-close.png'); 213 | } 214 | } 215 | } 216 | 217 | .authPopup { 218 | 219 | position: absolute; z-index: 110; 220 | top: 0; left: 0; 221 | width: 100%; height: 100%; 222 | background-color: rgba(0,0,0,.4); 223 | 224 | div { 225 | position: absolute; 226 | top: 50%; height: 80px; margin-top: -40px; 227 | left: 50%; width: 450px; margin-left: -225px; 228 | padding: 20px; 229 | background-color: #FFFFFF; 230 | border-radius: 10px; 231 | color: #727271; 232 | text-align: center; 233 | font: { 234 | weight: bold; 235 | size: 20px; 236 | } 237 | } 238 | } 239 | 240 | .breadcrumb { 241 | 242 | padding: 6px; padding-left: 50px; 243 | font-size: 14px; 244 | background: url("../images/default/breadcrumb.png") no-repeat 10px 50% / auto 65%; 245 | 246 | .pathIt { 247 | font-style: italic; 248 | cursor: pointer; 249 | color: #727271; 250 | } 251 | .pathIt:hover { 252 | text-decoration: underline; 253 | } 254 | } 255 | 256 | .fileBrowser { 257 | 258 | position: relative; width: 100%; height: 100%; 259 | margin-top: -104px; 260 | padding-top: 104px; padding-bottom: 0px; 261 | 262 | .services, .files { 263 | 264 | float: left; 265 | height: 100%; 266 | /* overflow: hidden; */ 267 | } 268 | .services { 269 | 270 | position: relative; width: 53px; z-index: 10; 271 | border-right: 1px solid #282828; 272 | 273 | .logoutBtn { 274 | height: 52px; margin: 0px; padding: 0px; 275 | cursor: pointer; 276 | } 277 | 278 | > ul { 279 | height: 100%; width: 100%; padding-bottom: 52px; 280 | 281 | > li { 282 | display: block; float: left; 283 | height: 52px; width: 52px; 284 | border-bottom: 1px solid #666666; 285 | cursor: pointer; 286 | background-repeat: no-repeat, no-repeat; 287 | background-position: 32px 9px, 0px 0px; 288 | background-size: 11px 11px, auto 52px; 289 | } 290 | > li:hover { 291 | background-position: 32px 9px, -52px 0px; 292 | 293 | ul.contextMenu { 294 | /* display: block; WIP */ 295 | } 296 | } 297 | > li:active { 298 | background-position: 32px 9px, -104px 0px; 299 | } 300 | > li.dropbox { 301 | background-image: url("../images/default/srv-state-disconnected.png"), url("../images/default/srv-dropbox.png"); 302 | } 303 | > li.dropbox.connected { 304 | background-image: url("../images/default/srv-state-connected.png"), url("../images/default/srv-dropbox.png"); 305 | } 306 | > li.www { 307 | background-image: url("../images/default/srv-state-disconnected.png"), url('../images/default/srv-www.png'); 308 | } 309 | > li.www.connected { 310 | background-image: url("../images/default/srv-state-connected.png"), url('../images/default/srv-www.png'); 311 | } 312 | > li.ftp { 313 | background-image: url("../images/default/srv-state-disconnected.png"), url('../images/default/srv-ftp.png'); 314 | } 315 | > li.ftp.connected { 316 | background-image: url("../images/default/srv-state-connected.png"), url('../images/default/srv-ftp.png'); 317 | } 318 | > li.github { 319 | background-image: url("../images/default/srv-state-disconnected.png"), url('../images/default/srv-github.png'); 320 | } 321 | > li.github.connected { 322 | background-image: url("../images/default/srv-state-connected.png"), url('../images/default/srv-github.png'); 323 | } 324 | > li.webdav { 325 | background-image: url("../images/default/srv-state-disconnected.png"), url('../images/default/srv-webdav.png'); 326 | } 327 | > li.webdav.connected { 328 | background-image: url("../images/default/srv-state-connected.png"), url('../images/default/srv-webdav.png'); 329 | } 330 | > li.open-pages { 331 | background-image: url("../images/default/srv-state-disconnected.png"), url('../images/default/srv-open-pages.png'); 332 | } 333 | > li.open-pages.connected { 334 | background-image: url("../images/default/srv-state-connected.png"), url('../images/default/srv-open-pages.png'); 335 | } 336 | 337 | ul.contextMenu { 338 | 339 | position: absolute; z-index: 20; 340 | width: 170px; left: 50px; margin-top: 4px; 341 | display: none; 342 | padding: 0px; 343 | background: { 344 | color: #fcf6d6; 345 | } 346 | border: { 347 | left: 1px solid black; 348 | top: 1px solid black; 349 | right: 1px solid black; 350 | } 351 | 352 | li { 353 | height: 27px; line-height: 26px; 354 | margin: 0px; padding: 0px; padding-left: 6px; 355 | border-bottom: 1px solid black; 356 | } 357 | li:hover { 358 | text-decoration: underline; 359 | } 360 | li.logout { 361 | display: none; 362 | } 363 | } 364 | ul.contextMenu::before { 365 | 366 | content: ""; 367 | position: absolute; 368 | width: 0; height: 0; 369 | margin-left: -12px; margin-top: 8px; 370 | border: { 371 | width: 6px; 372 | style: solid; 373 | color: transparent black transparent transparent; 374 | } 375 | } 376 | li.connected { 377 | li.login { 378 | display: none; 379 | } 380 | li.logout { 381 | display: block; 382 | } 383 | } 384 | } 385 | } 386 | .files { 387 | 388 | position: absolute; width: 100%; top: 104px; left: 0; 389 | padding-left: 52px; 390 | 391 | .list { 392 | 393 | padding-left: 10px; padding-right: 10px; 394 | display: block; height: 100%; padding-bottom: 224px; 395 | 396 | .titles { 397 | 398 | color: #34B8EB; font-size: 14px; padding-top: 6px; padding-bottom: 6px; 399 | border-top: 1px solid #34B8EB; border-bottom: 1px solid #34B8EB; 400 | 401 | span { 402 | display: inline-block; 403 | cursor: pointer; user-select: none; 404 | } 405 | span.sortArrow { 406 | width: 6px; height: 6px; margin-right: 6px; 407 | background: { 408 | image: url("../images/default/btn-sort.png"); 409 | size: auto 6px; 410 | position: 0px 0px; 411 | repeat: no-repeat; 412 | } 413 | } 414 | span:hover { 415 | text-decoration: underline; 416 | .sortArrow { 417 | background-position: -6px 0px; 418 | } 419 | } 420 | span:active { 421 | .sortArrow { 422 | background-position: -12px 0px; 423 | } 424 | } 425 | .lastUpdate { 426 | width: 150px; 427 | } 428 | .fileName { 429 | margin-left: 20px; padding-left: 35px; 430 | width: 210px; 431 | } 432 | .fileType{ 433 | width: 60px; 434 | } 435 | .lastUpdate, .fileType { 436 | float: right; text-align: center; 437 | } 438 | } 439 | div.items { 440 | height: 100%; padding-bottom: 30px; 441 | ul { 442 | height: 100%; overflow-y: auto; 443 | user-select: none; 444 | 445 | li .fileName { 446 | cursor: pointer; 447 | } 448 | li .fileType { 449 | width: 60px; 450 | } 451 | li .lastUpdate { 452 | width: 150px; 453 | } 454 | li .fileType, li .lastUpdate { 455 | float: right; text-align: center; 456 | } 457 | li span { 458 | display: inline-block; overflow: hidden; 459 | white-space: nowrap; 460 | } 461 | li { 462 | clear: both; height: 35px; line-height: 35px; 463 | border-bottom: 1px solid rgb(229, 229, 229); 464 | -ms-text-overflow: ellipsis; -o-text-overflow: ellipsis; text-overflow: ellipsis; 465 | 466 | span.icon { 467 | width: 34px; height: 34px; 468 | background: { 469 | repeat: no-repeat; 470 | size: auto 34px; 471 | position: 0px 0px; 472 | } 473 | } 474 | input, button, span { 475 | vertical-align: middle; 476 | } 477 | span.fileName, input[type="text"] { 478 | cursor: pointer; 479 | } 480 | input[type="text"] { 481 | border: 0; margin: 0; 482 | } 483 | button { 484 | display: none; 485 | width: 34px; height: 34px; 486 | border: 0; padding: 0; margin-left: 10px; 487 | cursor: pointer; 488 | background: { 489 | color: transparent; 490 | size: auto 34px; 491 | position: 0px 0px; 492 | repeat: no-repeat; 493 | } 494 | } 495 | button:hover { 496 | background-position: -34px 0px; 497 | } 498 | button:active { 499 | background-position: -68px 0px; 500 | } 501 | button.select { 502 | background-image: url("../images/default/btn-item-select.png"); 503 | } 504 | button.rename { 505 | background-image: url("../images/default/btn-item-rename.png"); 506 | } 507 | button.delete { 508 | background-image: url("../images/default/btn-item-delete.png"); 509 | } 510 | } 511 | li.nosel { 512 | input[type="checkbox"] { 513 | display: none; 514 | } 515 | .fileName { 516 | margin-left: 25px; 517 | } 518 | } 519 | li:hover { 520 | span.icon { 521 | background-position: -34px 0px; 522 | } 523 | button.rename, button.delete { 524 | display: inline-block; 525 | } 526 | } 527 | li:active { 528 | span.icon { 529 | background-position: -68px 0px; 530 | } 531 | } 532 | li.folder { 533 | span.icon { 534 | background-image: url('../images/default/icon-folder.png'); 535 | } 536 | &.new { 537 | input[type="text"] { 538 | margin-left: 20px; 539 | } 540 | } 541 | } 542 | li.file { 543 | span.icon { 544 | background-image: url('../images/default/icon-file.png'); 545 | } 546 | } 547 | li.file.image { 548 | span.icon { 549 | background-image: url('../images/default/icon-image.png'); 550 | } 551 | } 552 | li.file.sound { 553 | span.icon { 554 | background-image: url('../images/default/icon-sound.png'); 555 | } 556 | } 557 | li.file.video { 558 | span.icon { 559 | background-image: url('../images/default/icon-video.png'); 560 | } 561 | } 562 | li.filteredOut { 563 | /* display: none; */ 564 | color: grey; 565 | font-style: italic; 566 | cursor: not-allowed; 567 | span.fileName { 568 | cursor: not-allowed; 569 | } 570 | } 571 | li.filteredOut:hover { 572 | span.icon { 573 | background-position: 0px 0px; 574 | } 575 | button.rename, button.delete { 576 | display: none; 577 | } 578 | } 579 | } 580 | } 581 | } 582 | } 583 | &.selectFolders { 584 | .files { 585 | .list { 586 | ul { 587 | li:hover { 588 | button.select { 589 | display: inline-block; 590 | } 591 | } 592 | } 593 | } 594 | } 595 | } 596 | } 597 | 598 | .dropzone { 599 | 600 | position: relative; height: 92px; z-index: 10; margin-top: -224px; 601 | border-top: 1px dashed #34B8EB; 602 | background: url("../images/default/dropzone.png") no-repeat center / auto 50%; 603 | text-align: center; 604 | color: #34B7EA; 605 | 606 | h2 { 607 | margin-top: 4px; margin-bottom: 11px; 608 | position: relative; 609 | font: { 610 | weight: 300; 611 | size: 14pt; 612 | } 613 | } 614 | div, div button { 615 | position: absolute; top: 0; left: 0; 616 | height: 100%; width: 100%; padding: 0; margin: 0; border: 0; 617 | } 618 | div button { 619 | background: transparent; 620 | color: #34B7EA; 621 | line-height : 150px; 622 | font: { 623 | weight: 300; 624 | size: 11pt; 625 | } 626 | cursor: pointer; 627 | } 628 | div button:hover { 629 | text-decoration: underline; 630 | } 631 | div input { 632 | opacity: 0; 633 | } 634 | } 635 | 636 | .footer { 637 | 638 | position: relative; height: 52px; 639 | margin-top: -52px; 640 | background: #34B8EB; 641 | border-top: 1px solid #282828; 642 | text-align: center; 643 | 644 | button.deleteBtn, div.export .saveBtn, div.export .overwriteBtn { 645 | 646 | background: #fff; height: 40px; padding: 8px; 647 | border: 0; 648 | font: { 649 | weight: 300; 650 | size: 18px; 651 | } 652 | color: #444; text-transform: lowercase; 653 | cursor: pointer; 654 | } 655 | button.deleteBtn, div.export { 656 | margin-top: 6px; 657 | } 658 | button.deleteBtn { 659 | 660 | float: right; margin-right: 10px; 661 | } 662 | div.export { 663 | 664 | .saveBtn, .overwriteBtn { 665 | color: #FFF; 666 | } 667 | .overwriteBtn { 668 | background: orange; 669 | } 670 | .saveBtn { 671 | background: green; 672 | } 673 | } 674 | } 675 | 676 | 677 | /* 678 | STYLING BY STATE 679 | */ 680 | 681 | &.starting { 682 | 683 | .header .controls { 684 | 685 | width: 52px; 686 | } 687 | } 688 | 689 | &.selecting, &.browsing.single-file-exp-mode { /* footer <+ ?> */ 690 | 691 | .fileBrowser { 692 | 693 | padding-bottom: 52px; /* for footer */ 694 | } 695 | } 696 | &.selecting.browsing.single-file-sel-mode { /* dropzone + footer */ 697 | 698 | .fileBrowser { 699 | 700 | .files { 701 | 702 | .list { 703 | 704 | padding-bottom: 276px; /* for footer + dropzone + titles + header */ 705 | } 706 | } 707 | .dropzone { 708 | 709 | margin-top: -276px; 710 | } 711 | } 712 | } 713 | &.browsing.single-file-exp-mode { /* footer (no dropzone) */ 714 | 715 | .fileBrowser { 716 | 717 | .files { 718 | 719 | .list { 720 | 721 | padding-bottom: 184px; 722 | } 723 | } 724 | .dropzone { 725 | 726 | display: none; 727 | } 728 | } 729 | } 730 | &.items-list { 731 | 732 | .header { 733 | 734 | .listItemsBtn, .listItemsBtn:hover { 735 | background-position: -156px 0px; 736 | } 737 | } 738 | } 739 | &.items-icons { 740 | 741 | .header { 742 | 743 | .iconItemsBtn, .iconItemsBtn:hover { 744 | background-position: -156px 0px; 745 | } 746 | } 747 | } 748 | &.browsing.making-new-folder { 749 | 750 | .header { 751 | 752 | .newFolderBtn, .newFolderBtn:hover { 753 | background-position: -156px 0px; 754 | } 755 | } 756 | } 757 | &.sortedby-name.asc { 758 | .files { 759 | .list { 760 | .titles { 761 | .fileName { 762 | span.sortArrow { 763 | background-position: -18px 0px; 764 | } 765 | } 766 | } 767 | } 768 | } 769 | } 770 | &.sortedby-name.desc { 771 | .files { 772 | .list { 773 | .titles { 774 | .fileName { 775 | span.sortArrow { 776 | background-position: -18px 0px; 777 | transform: rotate(0.5turn) 778 | } 779 | } 780 | } 781 | } 782 | } 783 | } 784 | &.sortedby-type.asc { 785 | .files { 786 | .list { 787 | .titles { 788 | .fileType { 789 | span.sortArrow { 790 | background-position: -18px 0px; 791 | } 792 | } 793 | } 794 | } 795 | } 796 | } 797 | &.sortedby-type.desc { 798 | .files { 799 | .list { 800 | .titles { 801 | .fileType { 802 | span.sortArrow { 803 | background-position: -18px 0px; 804 | transform: rotate(0.5turn) 805 | } 806 | } 807 | } 808 | } 809 | } 810 | } 811 | &.sortedby-lastUpdate.asc { 812 | .files { 813 | .list { 814 | .titles { 815 | .lastUpdate { 816 | span.sortArrow { 817 | background-position: -18px 0px; 818 | } 819 | } 820 | } 821 | } 822 | } 823 | } 824 | &.sortedby-lastUpdate.desc { 825 | .files { 826 | .list { 827 | .titles { 828 | .lastUpdate { 829 | span.sortArrow { 830 | background-position: -18px 0px; 831 | transform: rotate(0.5turn) 832 | } 833 | } 834 | } 835 | } 836 | } 837 | } 838 | &.srv-dropbox { 839 | .fileBrowser { 840 | .services { 841 | li.dropbox { 842 | background-position: 32px 9px, -156px 0px; 843 | } 844 | } 845 | } 846 | } 847 | &.srv-ftp { 848 | .fileBrowser { 849 | .services { 850 | li.ftp { 851 | background-position: 32px 9px, -156px 0px; 852 | } 853 | } 854 | } 855 | } 856 | &.srv-www { 857 | .fileBrowser { 858 | .services { 859 | li.www { 860 | background-position: 32px 9px, -156px 0px; 861 | } 862 | } 863 | } 864 | } 865 | } 866 | -------------------------------------------------------------------------------- /app/styles/platforms.scss: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | SCSS containing everything platform specific 4 | 5 | **/ 6 | 7 | -------------------------------------------------------------------------------- /build.hxml: -------------------------------------------------------------------------------- 1 | ## 2 | # Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | # @see https://github.com/silexlabs/cloud-explorer 4 | # 5 | # Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | # @see https://github.com/silexlabs/unifile 7 | # 8 | # @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | # Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | # License MIT 11 | ## 12 | 13 | # js lib compilation 14 | -js app/scripts/cloud-explorer.js 15 | -cp src 16 | # uncomment the following to disable traces 17 | #--no-traces 18 | ce.api.CloudExplorer -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloud-explorer", 3 | "description": "", 4 | "version": "1.0.0", 5 | "author": "Thomas Fetiveau aka zabojad", 6 | "contributors": [ 7 | "Alex Hoyau aka lexoyo" 8 | ], 9 | "dependencies": { 10 | "express": "3.4.x", 11 | "dbox": "0.6.x", 12 | "node-oauth": "0.1.x", 13 | "oauth": "", 14 | "haxe": "~0.1.9", 15 | "path": "", 16 | "connect-multiparty": "~2.0.0", 17 | "body-parser": "~1.15.0", 18 | "cookie-parser": "~1.4.1", 19 | "express-session": "~1.13.0", 20 | "unifile": "0.0.56" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/silexlabs/cloud-explorer.git" 25 | }, 26 | "keywords": [ 27 | "express", 28 | "cloud", 29 | "cms", 30 | "rest", 31 | "dropbox", 32 | "storage", 33 | "filepicker", 34 | "api", 35 | "haxe" 36 | ], 37 | "devDependencies": { 38 | "grunt": "~0.4.1", 39 | "grunt-cli": "~0.1.11", 40 | "grunt-contrib-copy": "~0.4.1", 41 | "grunt-contrib-concat": "~0.3.0", 42 | "grunt-contrib-uglify": "~0.2.0", 43 | "grunt-contrib-compass": "~0.5.0", 44 | "grunt-contrib-jshint": "~0.6.3", 45 | "grunt-contrib-cssmin": "~0.6.0", 46 | "grunt-contrib-connect": "~0.5.0", 47 | "grunt-contrib-clean": "~0.5.0", 48 | "grunt-contrib-htmlmin": "~0.1.3", 49 | "grunt-bower-install": "~0.5.0", 50 | "grunt-contrib-imagemin": "~0.2.0", 51 | "grunt-contrib-watch": "~0.5.2", 52 | "grunt-rev": "~0.1.0", 53 | "grunt-autoprefixer": "~0.2.0", 54 | "grunt-usemin": "~2.0.2", 55 | "grunt-mocha": "~0.4.0", 56 | "grunt-svgmin": "~0.2.0", 57 | "grunt-concurrent": "~0.3.0", 58 | "load-grunt-tasks": "~0.1.0", 59 | "time-grunt": "~0.1.1", 60 | "grunt-haxe": "~0.1.10", 61 | "mocha": "~2.4.5", 62 | "chai": "~3.5.0", 63 | "supertest": "~1.2.0" 64 | }, 65 | "license": "MIT", 66 | "engines": { 67 | "node": "0.10.x" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /screenshot01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/screenshot01.png -------------------------------------------------------------------------------- /screenshot02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/screenshot02.png -------------------------------------------------------------------------------- /screenshot03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/screenshot03.png -------------------------------------------------------------------------------- /screenshot04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/screenshot04.png -------------------------------------------------------------------------------- /screenshot05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/screenshot05.png -------------------------------------------------------------------------------- /screenshot06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silexlabs/cloud-explorer/06947ebed3ee0de3b99f4fc325416059cf7d0f87/screenshot06.png -------------------------------------------------------------------------------- /server/unifile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple unifile server to expose unifile api and nothing else 3 | * https://github.com/silexlabs/unifile/ 4 | * license: GPL v2 5 | */ 6 | // node modules 7 | var express = require('express'); 8 | var app = express(); 9 | var unifile = require('unifile'); 10 | var multipart = require('connect-multiparty'); 11 | var bodyParser = require('body-parser'); 12 | var cookieParser = require('cookie-parser'); 13 | var session = require('express-session'); 14 | 15 | // config 16 | var options = unifile.defaultConfig; 17 | 18 | // define users (login/password) wich will be authorized to access the www folder (read and write) 19 | options.www.USERS = { 20 | "admin": "admin" 21 | } 22 | 23 | options.www.ROOT = __dirname + "/../bin/"; 24 | 25 | // enable open pages 26 | // this is the ""self hosting mode" 27 | // auth with Mozilla persona, choose a name and brose a folder on the server where unifile is installed and which is served as http(s)://the-unifile-server.com/chosen-name/ - this is an experimental feature which still has to be fine tuned 28 | // here you can set all open pages config, see default-config.js 29 | // options.openPages.ENABLED = true; 30 | 31 | // limit the services to the tested ones 32 | options.services = ['dropbox', 'www', 'ftp']; 33 | 34 | options.staticFolders.push( 35 | // file browser 36 | { 37 | name: "/", 38 | path: __dirname + "/../app/" 39 | }, 40 | { 41 | name: "/styles/", 42 | path: __dirname + "/../.tmp/styles/" 43 | }); 44 | 45 | // parse data for file upload 46 | app.use(options.apiRoot, multipart()); 47 | 48 | // parse data for post data 49 | app.use(options.apiRoot, bodyParser.urlencoded({ 50 | extended: true 51 | })); 52 | app.use(options.apiRoot, bodyParser.json()); 53 | 54 | app.use(options.apiRoot, cookieParser()); 55 | app.use(options.apiRoot, session({ 56 | name: options.cookieName, 57 | secret: options.sessionSecret, 58 | resave: false, 59 | saveUninitialized: false, 60 | cookie: { 61 | maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days 62 | } 63 | // TIP: you may want to add a session store here 64 | // so that unifile memorizes the users connexion data 65 | // into a database or file - e.g. https://www.npmjs.com/package/connect-fs2 66 | })); 67 | 68 | // use unifile as a middleware 69 | app.use(unifile.middleware(express, app, options)); 70 | 71 | // server 'loop' 72 | var port = process.env.PORT || 6805; // 6805 is the date of sexual revolution started in paris france 8-) 73 | app.listen(port, function() { 74 | console.log('Listening on ' + port); 75 | }); 76 | 77 | 78 | /* 79 | // catch all errors and prevent nodejs to crash, production mode 80 | process.on('uncaughtException', function(err) { 81 | console.log ('---------------------'); 82 | console.error('---------------------', 'Caught exception: ', err, '---------------------'); 83 | console.log ('---------------------'); 84 | }); 85 | */ 86 | -------------------------------------------------------------------------------- /src/ce/api/CloudExplorer.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.api; 13 | 14 | import js.html.IFrameElement; 15 | 16 | import ce.core.config.Config; 17 | import ce.core.model.CEBlob; 18 | import ce.core.model.CEError; 19 | 20 | import ce.core.model.api.PickOptions; 21 | import ce.core.model.api.ReadOptions; 22 | import ce.core.model.api.ExportOptions; 23 | import ce.core.model.api.WriteOptions; 24 | 25 | import ce.core.Controller; 26 | 27 | @:expose("ce.api.CloudExplorer") 28 | class CloudExplorer { 29 | 30 | /// 31 | // API 32 | // 33 | 34 | /** 35 | * Returns a fresh instance of Cloud Explorer 36 | */ 37 | static function get(? iframeEltId : Null) : CloudExplorer { 38 | 39 | return new CloudExplorer(iframeEltId); 40 | } 41 | 42 | /** 43 | * filepicker.pick([options], onSuccess(CEBlob){}, onError(CEError){}) 44 | * @see https://developers.inkfilepicker.com/docs/web/#pick 45 | */ 46 | public function pick(arg1 : Dynamic, arg2 : Dynamic, ? arg3 : Dynamic) { 47 | 48 | if (arg1 == null || arg2 == null) { 49 | 50 | throw "Missing mandatory parameters for CloudExplorer.pick(onSuccess : CEBlob -> Void, onError : CEError -> Void)"; 51 | } 52 | var options : Null = arg3 != null ? arg1 : null; 53 | var onSuccess : CEBlob -> Void = options != null ? arg2 : arg1; 54 | var onError : CEError -> Void = options != null ? arg3 : arg2; 55 | trace("options: "+options+" onSuccess: "+onSuccess+" onError: "+onError); 56 | ctrl.pick(options, onSuccess, onError); 57 | } 58 | 59 | /** 60 | * @see https://developers.inkfilepicker.com/docs/web/#read 61 | */ 62 | //public function read(input : CEBlob, ? options : ReadOptions, onSuccess : String -> Void, onError : CEError -> Void, onProgress : Int -> Void) { 63 | public function read(arg1 : Dynamic, arg2 : Dynamic, arg3 : Dynamic, ? arg4 : Dynamic, ? arg5 : Dynamic) { 64 | 65 | var input : CEBlob = arg1; // TODO The object to read. Can be an CEBlob, a URL, a DOM File Object, or an . 66 | 67 | var options : Null = (Reflect.isObject(arg2)) ? arg2 : null; 68 | 69 | var onSuccess : String -> Void = options == null ? arg2 : arg3; 70 | var onError : CEError -> Void = options == null ? arg3 : arg4; 71 | var onProgress : Int -> Void = options == null ? arg4 : arg5; 72 | 73 | ctrl.read(input, options, onSuccess, onError, onProgress); 74 | } 75 | 76 | /** 77 | * @see https://developers.inkfilepicker.com/docs/web/#export 78 | */ 79 | public function exportFile(arg1 : Dynamic, arg2 : Dynamic, arg3 : Dynamic, ? arg4 : Dynamic) : Void { 80 | 81 | var input : CEBlob = arg1; // An InkBlob or URL pointing to data you'd like to export. If you have a DOM File object or raw data, you can use the filepicker.store call to first generate an InkBlob 82 | 83 | var options : Null = (Reflect.isObject(arg2)) ? arg2 : null; 84 | 85 | var onSuccess : CEBlob -> Void = options == null ? arg2 : arg3; 86 | var onError : CEError -> Void = options == null ? arg3 : arg4; 87 | 88 | ctrl.exportFile(input, options, onSuccess, onError); 89 | } 90 | 91 | /** 92 | * @see https://developers.inkfilepicker.com/docs/web/#write 93 | */ 94 | public function write(arg1 : Dynamic, arg2 : Dynamic, arg3 : Dynamic, arg4 : Dynamic, ? arg5 : Dynamic, ? arg6 : Dynamic) : Void { 95 | 96 | var target : CEBlob = arg1; 97 | var data : Dynamic = arg2; 98 | 99 | var options : Null = (Reflect.isObject(arg3)) ? arg3 : null; 100 | 101 | var onSuccess : CEBlob -> Void = options == null ? arg3 : arg4; 102 | var onError : CEError -> Void = options == null ? arg4 : arg5; 103 | var onProgress : Null Void> = options == null ? arg5 : arg6; 104 | 105 | ctrl.write(target, data, options, onSuccess, onError, onProgress); 106 | } 107 | 108 | /** 109 | * Non-Ink API method to check if the user is currently logged into a service. 110 | */ 111 | public function isLoggedIn(arg1 : Dynamic, ? arg2 : Dynamic, ? arg3 : Dynamic) : Void { 112 | 113 | var srvName : String = arg1; 114 | var onSuccess : Bool -> Void = arg2; 115 | var onError : CEError -> Void = arg3; 116 | 117 | return ctrl.isLoggedIn(srvName, onSuccess, onError); 118 | } 119 | 120 | /** 121 | * Non-Ink API method to ask the user to authorize a service. 122 | */ 123 | public function requestAuthorize(arg1 : Dynamic, ? arg2 : Dynamic, ? arg3 : Dynamic) : Void { 124 | 125 | var srvName : String = arg1; 126 | var onSuccess : Void -> Void = arg2; 127 | var onError : CEError -> Void = arg3; 128 | 129 | ctrl.requestAuthorize(srvName, onSuccess, onError); 130 | } 131 | 132 | 133 | /// 134 | // INTERNALS 135 | // 136 | 137 | var ctrl : ce.core.Controller; 138 | 139 | private function new(? iframeEltId : Null) { 140 | 141 | var ceIframe : IFrameElement = iframeEltId != null ? cast js.Browser.document.getElementById(iframeEltId) : null; 142 | 143 | var config : Config = new Config(); // TODO 144 | 145 | if (ceIframe == null) { 146 | 147 | ceIframe = js.Browser.document.createIFrameElement(); 148 | 149 | js.Browser.document.body.appendChild(ceIframe); 150 | 151 | } else { 152 | 153 | if (ceIframe.src != null) { 154 | 155 | for (ca in ceIframe.attributes) { 156 | 157 | if (ca.nodeName.indexOf("data-ce-") == 0) { 158 | 159 | config.readProperty(ca.nodeName.substr(8), ca.nodeValue); 160 | } 161 | } 162 | } 163 | } 164 | 165 | ctrl = new Controller(config, ceIframe); 166 | } 167 | } -------------------------------------------------------------------------------- /src/ce/core/config/Config.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.config; 13 | 14 | /* 15 | TODO 16 | - list of enabled services ? 17 | */ 18 | class Config { 19 | 20 | static inline var PROP_NAME_UNIFILE_ENDPOINT : String = "unifile-url"; 21 | 22 | static inline var PROP_NAME_CE_PATH : String = "path"; 23 | 24 | static inline var PROP_VALUE_DEFAULT_UNIFILE_ENDPOINT : String = "http://localhost:6805/api/1.0/"; 25 | 26 | static inline var PROP_VALUE_DEFAULT_CE_PATH : String = ""; 27 | 28 | public function new() { } 29 | 30 | /** 31 | * The unifile service endpoint / url 32 | * @see https://github.com/silexlabs/unifile/ 33 | */ 34 | public var unifileEndpoint (default, null) : String = PROP_VALUE_DEFAULT_UNIFILE_ENDPOINT; 35 | 36 | /** 37 | * The cloud-explorer/ folder path 38 | */ 39 | public var path (default, null) : String = PROP_VALUE_DEFAULT_CE_PATH; 40 | 41 | /// 42 | // API 43 | // 44 | 45 | public function readProperty(name : String, value : String) : Void { 46 | 47 | switch(name) { 48 | 49 | case PROP_NAME_UNIFILE_ENDPOINT: 50 | 51 | unifileEndpoint = value; 52 | 53 | case PROP_NAME_CE_PATH: 54 | 55 | path = value; 56 | 57 | default: 58 | 59 | throw "Unexpected configuration property " + name; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/ce/core/ctrl/ErrorCtrl.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.ctrl; 13 | 14 | import ce.core.view.Application; 15 | 16 | import ce.core.model.State; 17 | import ce.core.model.CEError; 18 | import ce.core.model.unifile.UnifileError; 19 | 20 | class ErrorCtrl { 21 | 22 | public function new(parent : Controller, state : State, application : Application) { 23 | 24 | this.parent = parent; 25 | this.state = state; 26 | this.application = application; 27 | } 28 | 29 | var state : State; 30 | 31 | var application : Application; 32 | 33 | var parent : Controller; 34 | 35 | /// 36 | // API 37 | // 38 | 39 | public function manageListSrvError(msg : String) : Void { 40 | 41 | switch (state.currentMode) { 42 | 43 | case SingleFileSelection(_), SingleFileExport(_) : 44 | 45 | setError(msg); 46 | 47 | case IsLoggedIn(_, onError, _) : 48 | 49 | onError(new CEError(500)); // FIXME 50 | 51 | case RequestAuthorize(_, onError, _) : 52 | 53 | onError(new CEError(500)); // FIXME 54 | 55 | state.displayState = false; 56 | } 57 | } 58 | 59 | public function manageConnectError(msg : String) : Void { 60 | 61 | switch (state.currentMode) { 62 | 63 | case SingleFileSelection(_), SingleFileExport(_) : 64 | 65 | setError(msg); 66 | 67 | case RequestAuthorize(_, onError, _) : 68 | 69 | onError(new CEError(500)); // FIXME 70 | 71 | state.displayState = false; 72 | 73 | case IsLoggedIn(_) : 74 | 75 | throw "unexpected mode " + state.currentMode; 76 | } 77 | } 78 | 79 | public function manageLoginError(msg : String) : Void { 80 | 81 | switch (state.currentMode) { 82 | 83 | case SingleFileSelection(_), SingleFileExport(_) : 84 | 85 | setError(msg); 86 | 87 | case RequestAuthorize(_, onError, _) : 88 | 89 | onError(new CEError(500)); // FIXME 90 | 91 | state.displayState = false; 92 | 93 | case IsLoggedIn(_) : 94 | 95 | throw "unexpected mode " + state.currentMode; 96 | } 97 | } 98 | 99 | /** 100 | * FIXME UnifileError should probably be converted to a generic error in service layer 101 | */ 102 | public function setUnifileError(err : UnifileError) : Void { 103 | 104 | if (err.code == CEError.CODE_UNAUTHORIZED) { 105 | 106 | var srv = state.currentLocation.service; 107 | 108 | state.serviceList.get(srv).isLoggedIn = false; 109 | 110 | parent.connect(srv); 111 | 112 | } else { 113 | 114 | setError(err.message); 115 | } 116 | } 117 | 118 | 119 | public function setError(msg : String) : Void { 120 | 121 | application.setLoaderDisplayed(false); // FIXME should this be there ? 122 | 123 | application.alertPopup.setMsg(msg, 0, [{ msg: "Continue", cb: function() { application.setAlertPopupDisplayed(false); }}]); 124 | 125 | application.setAlertPopupDisplayed(true); 126 | } 127 | } -------------------------------------------------------------------------------- /src/ce/core/model/CEBlob.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model; 13 | 14 | /** 15 | * @see https://developers.inkfilepicker.com/docs/web/#inkblob 16 | */ 17 | typedef CEBlob = { 18 | 19 | /** 20 | * The most critical part of the file, the url points to where the file is stored and acts as a sort of "file path". 21 | * The url is what is used when making the underlying GET and POST calls to Ink when you do a filepicker.read or 22 | * filepicker.write call. 23 | */ 24 | var url : String; 25 | 26 | /** 27 | * The name of the file, if available. 28 | */ 29 | var filename : Null; 30 | 31 | /** 32 | * The mimetype of the file, if available. 33 | */ 34 | var mimetype : Null; 35 | 36 | /** 37 | * The size of the file in bytes, if available. We will attach this directly to the InkBlob when we have it, 38 | * otherwise you can always get the size by calling filepicker.stat 39 | */ 40 | var size : Null; 41 | 42 | /** 43 | * If the file was stored in one of the file stores you specified or configured (S3, Rackspace, Azure, etc.), 44 | * this parameter will tell you where in the file store this file was put. 45 | */ 46 | var key : Null; 47 | 48 | /** 49 | * If the file was stored in one of the file stores you specified or configured (S3, Rackspace, Azure, etc.), 50 | * this parameter will tell you in which container this file was put. 51 | */ 52 | var container : Null; 53 | 54 | /** 55 | * This flag specifies whether the underlying file is writeable. In most cases this will be true, but if a user 56 | * uploads a photo from facebook, for instance, the original file cannot be written to. In these cases, you should 57 | * use the filepicker.exportFile call as a way to give the user the ability to save their content. 58 | */ 59 | var isWriteable : Bool; 60 | 61 | /** 62 | * The path of the InkBlob indicates its position in the hierarchy of files uploaded when {folders:true} is set. 63 | * In situations where the file was not uploaded as part of or along with a folder, path will not be defined. 64 | */ 65 | var path : Null; 66 | } -------------------------------------------------------------------------------- /src/ce/core/model/CEError.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model; 13 | 14 | /** 15 | * @see https://developers.inkfilepicker.com/docs/web/#errors 16 | */ 17 | class CEError { 18 | 19 | /** 20 | * Bad parameters were passed to the server. This often will be the case when you turned on security but 21 | * haven't passed up a policy or signature. 22 | */ 23 | static public inline var CODE_BAD_PARAMETERS : Int = 400; 24 | 25 | static public inline var CODE_UNAUTHORIZED : Int = 401; 26 | 27 | /** 28 | * The policy and/or signature don't allow you to make this request. 29 | * @see https://developers.inkfilepicker.com/docs/security/ 30 | */ 31 | static public inline var CODE_INVALID_REQUEST : Int = 403; 32 | 33 | public function new(code : Int) { } 34 | 35 | public var code (default, null) : Int; 36 | 37 | public function toString() : String { 38 | 39 | return Std.string(code); 40 | } 41 | } -------------------------------------------------------------------------------- /src/ce/core/model/DisplayMode.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model; 13 | 14 | enum DisplayMode { 15 | List; 16 | Icons; 17 | } -------------------------------------------------------------------------------- /src/ce/core/model/Location.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model; 13 | 14 | class Location { 15 | 16 | public function new(s : Service, p : String) { 17 | 18 | this.service = s; 19 | this.path = p; 20 | } 21 | 22 | public var service (default, set) : Service; 23 | 24 | public var path (default, set) : String; 25 | 26 | 27 | /// 28 | // CALLBACKS 29 | // 30 | 31 | public dynamic function onChanged() { } 32 | 33 | 34 | /// 35 | // GETTERS / SETTERS 36 | // 37 | 38 | public function set_service(v : Service) : Service { 39 | 40 | if (v == service) { 41 | 42 | return v; 43 | } 44 | service = v; 45 | 46 | onChanged(); 47 | 48 | return service; 49 | } 50 | 51 | public function set_path(v : String) : String { 52 | 53 | if (v == path) { 54 | 55 | return v; 56 | } 57 | path = v; 58 | 59 | onChanged(); 60 | 61 | return path; 62 | } 63 | } -------------------------------------------------------------------------------- /src/ce/core/model/Mode.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model; 13 | 14 | import ce.core.model.api.PickOptions; 15 | import ce.core.model.api.ExportOptions; 16 | 17 | enum Mode { 18 | 19 | SingleFileSelection(onSuccess : CEBlob -> Void, onError : CEError -> Void, options : Null); 20 | SingleFileExport(onSuccess : CEBlob -> Void, onError : CEError -> Void, input : CEBlob, options : Null); 21 | IsLoggedIn(onSuccess : Bool -> Void, onError : CEError -> Void, srvName : String); 22 | RequestAuthorize(onSuccess : Void -> Void, onError : CEError -> Void, srvName : String); 23 | } -------------------------------------------------------------------------------- /src/ce/core/model/Service.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model; 13 | 14 | @:enum 15 | abstract Service(String) from String to String { 16 | var Dropbox = "dropbox"; 17 | var Www = "www"; 18 | var Ftp = "ftp"; 19 | } -------------------------------------------------------------------------------- /src/ce/core/model/SortField.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model; 13 | 14 | @:enum 15 | abstract SortField(String) to String { 16 | var Name = "name"; 17 | var Type = "type"; 18 | var LastUpdate = "lastUpdate"; 19 | } -------------------------------------------------------------------------------- /src/ce/core/model/SortOrder.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model; 13 | 14 | @:enum 15 | abstract SortOrder(String) to String { 16 | var Asc = "asc"; 17 | var Desc = "desc"; 18 | } -------------------------------------------------------------------------------- /src/ce/core/model/State.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model; 13 | 14 | import ce.core.model.unifile.Service; 15 | import ce.core.model.unifile.File; 16 | import ce.core.model.Location; 17 | import ce.core.model.Mode; 18 | import ce.core.model.DisplayMode; 19 | import ce.core.model.SortField; 20 | import ce.core.model.SortOrder; 21 | 22 | import haxe.ds.StringMap; 23 | 24 | class State { 25 | 26 | public function new() { } 27 | 28 | public var readyState (default, set) : Bool = false; 29 | 30 | public var displayState (default, set) : Bool = false; 31 | 32 | public var newFolderMode (default, set) : Bool = false; 33 | 34 | public var displayMode (default, set) : Null = null; 35 | 36 | public var serviceList (default, set) : Null> = null; 37 | 38 | public var currentLocation (default, set) : Null = null; 39 | 40 | public var currentFileList (default, set) : Null> = null; 41 | 42 | public var currentMode (default, set) : Null = null; 43 | 44 | public var currentSortField (default, set) : Null = null; 45 | 46 | public var currentSortOrder (default, set) : Null = null; 47 | 48 | 49 | /// 50 | // CALLBACKS 51 | // 52 | 53 | public dynamic function onReadyStateChanged() { } 54 | 55 | public dynamic function onDisplayStateChanged() { } 56 | 57 | public dynamic function onServiceListChanged() { } 58 | 59 | public dynamic function onCurrentLocationChanged() { } 60 | 61 | public dynamic function onCurrentFileListChanged() { } 62 | 63 | public dynamic function onCurrentModeChanged() { } 64 | 65 | public dynamic function onNewFolderModeChanged() { } 66 | 67 | public dynamic function onDisplayModeChanged() { } 68 | 69 | public dynamic function onCurrentSortFieldChanged() { } 70 | 71 | public dynamic function onCurrentSortOrderChanged() { } 72 | 73 | public dynamic function onServiceLoginStateChanged(srvName : String) { } 74 | 75 | 76 | /// 77 | // SETTERS 78 | // 79 | 80 | public function set_currentSortField(v : Null) : Null { 81 | 82 | if (v == currentSortField) { 83 | 84 | return currentSortField; 85 | } 86 | currentSortField = v; 87 | currentSortOrder = Asc; // setting new sort field also reset the order 88 | 89 | onCurrentSortFieldChanged(); 90 | 91 | return currentSortField; 92 | } 93 | 94 | public function set_currentSortOrder(v : Null) : Null { 95 | 96 | if (v == currentSortOrder) { 97 | 98 | return currentSortOrder; 99 | } 100 | currentSortOrder = v; 101 | 102 | onCurrentSortOrderChanged(); 103 | 104 | return currentSortOrder; 105 | } 106 | 107 | public function set_newFolderMode(v : Bool) : Bool { 108 | 109 | if (v == newFolderMode) { 110 | 111 | return newFolderMode; 112 | } 113 | newFolderMode = v; 114 | 115 | onNewFolderModeChanged(); 116 | 117 | return newFolderMode; 118 | } 119 | 120 | public function set_displayMode(v : DisplayMode) : DisplayMode { 121 | 122 | if (v == displayMode) { 123 | 124 | return displayMode; 125 | } 126 | displayMode = v; 127 | 128 | onDisplayModeChanged(); 129 | 130 | return displayMode; 131 | } 132 | 133 | public function set_serviceList(v : Null>) : Null> { 134 | 135 | if (v == serviceList) { 136 | 137 | return v; 138 | } 139 | serviceList = v; 140 | 141 | for (s in serviceList) { 142 | 143 | s.onLoginStateChanged = function() { onServiceLoginStateChanged(s.name); } 144 | } 145 | 146 | onServiceListChanged(); 147 | 148 | return serviceList; 149 | } 150 | 151 | public function set_currentFileList(v : Null>) : Null> { 152 | 153 | if (v == currentFileList) { 154 | 155 | return v; 156 | } 157 | currentFileList = v; 158 | // reset both sort field and sort order 159 | currentSortField = Name; 160 | currentSortOrder = Asc; 161 | 162 | onCurrentFileListChanged(); 163 | 164 | return currentFileList; 165 | } 166 | 167 | public function set_currentMode(v : Null) : Null { 168 | 169 | if (v == currentMode) { 170 | 171 | return v; 172 | } 173 | currentMode = v; 174 | 175 | onCurrentModeChanged(); 176 | 177 | return currentMode; 178 | } 179 | 180 | public function set_readyState(v : Bool) : Bool { 181 | 182 | if (v == readyState) { 183 | 184 | return v; 185 | } 186 | readyState = v; 187 | 188 | onReadyStateChanged(); 189 | 190 | return readyState; 191 | } 192 | 193 | public function set_displayState(v : Bool) : Bool { 194 | 195 | if (v == displayState) { 196 | 197 | return v; 198 | } 199 | displayState = v; 200 | 201 | onDisplayStateChanged(); 202 | 203 | return displayState; 204 | } 205 | 206 | public function set_currentLocation(v : Location) : Location { 207 | 208 | if (v == currentLocation) { 209 | 210 | return v; 211 | } 212 | currentLocation = v; 213 | 214 | if (currentLocation != null) { 215 | 216 | currentLocation.onChanged = function() { onCurrentLocationChanged(); } 217 | } 218 | 219 | onCurrentLocationChanged(); 220 | 221 | return currentLocation; 222 | } 223 | } -------------------------------------------------------------------------------- /src/ce/core/model/api/ExportOptions.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.api; 13 | 14 | typedef ExportOptions = { 15 | 16 | /** 17 | * The mimetype of the file. Note that we try to guess the file extension of the file from this, 18 | * so image/png will result in a .png ending while image/* will not have an ending. If you don't 19 | * specify the mimetype, we will try to guess, otherwise will fall back to letting the user save 20 | * it as whatever extension they choose, which may cause issues (if they try to save text to 21 | * Facebook, for instance). 22 | */ 23 | @:optional var mimetype : Null; 24 | 25 | /** 26 | * Specify the type of the file by extension rather than mimetype. Don't use this option with 27 | * mimetype(s) specified as well. 28 | */ 29 | @:optional var extension : Null; 30 | } -------------------------------------------------------------------------------- /src/ce/core/model/api/PickOptions.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.api; 13 | 14 | /** 15 | * An optional dictionary of key-value pairs that specify how the picker behaves. 16 | */ 17 | typedef PickOptions = { 18 | 19 | /** 20 | * Specify the type of file that the user is allowed to pick. For example, if 21 | * you wanted images, specify image/* and users will only be able to select 22 | * images to upload. Similarly, you could specify application/msword for only 23 | * Word Documents. 24 | * You can also specify an array of mimetypes to allow the user to select a 25 | * file from any of the given types. 26 | */ 27 | @:optional var mimetype : Null; 28 | 29 | @:optional var mimetypes : Null>; 30 | 31 | /** 32 | * Specify the type of file that the user is allowed to pick by extension. 33 | * Don't use this option with mimetype(s) specified as well 34 | * You can also specify an array of extensions to allow the user to select 35 | * a file from any of the given types. 36 | */ 37 | @:optional var extension : Null; 38 | 39 | @:optional var extensions : Null>; 40 | 41 | /** 42 | * Where to load the Ink file picker UI into. Possible values are "window", 43 | * "modal", or the id of an iframe in the current document. Defaults to "modal". 44 | * Note that if the browser disables 3rd party cookies, the dialog will 45 | * automatically fall back to being served in a new window. 46 | */ 47 | // var container : String; 48 | 49 | /** 50 | * Specify which services are displayed on the left panel, and in which order, by 51 | * name. 52 | * Be sure that the services you select are compatible with the mimetype(s) or 53 | * extension(s) specified. 54 | * Currently, the Ink file picker supports the following services, and we're adding 55 | * more all the time: 56 | */ 57 | // service 58 | // services 59 | 60 | /** 61 | * Specifies which service to show upon opening. If not set, the user is shown their 62 | * most recently used location, or otherwise the computer upload page. 63 | */ 64 | // openTo 65 | 66 | /** 67 | * Limit file uploads to be at max maxSize bytes. 68 | */ 69 | // maxSize 70 | 71 | /** 72 | * Useful when developing, makes it so the onSuccess callback is fired immediately with 73 | * dummy data. 74 | */ 75 | //var debug : Bool = false; 76 | 77 | /** 78 | * If you have security enabled, you'll need to have a valid Ink file picker policy and signature in order to perform the requested call. This allows you to select who can and cannot perform certain actions on your site. 79 | * @see https://developers.inkfilepicker.com/docs/security/ 80 | */ 81 | // policy: POLICY 82 | // signature: SIGNATURE 83 | } -------------------------------------------------------------------------------- /src/ce/core/model/api/ReadOptions.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.api; 13 | 14 | typedef ReadOptions = { 15 | 16 | /** 17 | * Specify that you want the data to be retuned converted into base 64. 18 | * This is very useful when the contents of the file are binary rather than text, for example with images. 19 | * For your convenience, the filepicker.base64.encode and filepicker.base64.decode methods are available for your convenience. 20 | */ 21 | @:optional var base64encode : Bool; 22 | 23 | /** 24 | * If you know you want the file to be read as text, the Ink files library can be more efficient if you tell it to convert 25 | * everything into text. 26 | */ 27 | @:optional var asText : Bool; 28 | 29 | /** 30 | * Whether the data should be pulled from the browser's cache, if possible. Defaults to false. 31 | * Can make reads faster if you're sure the underlying file won't change. 32 | */ 33 | @:optional var cache : Bool; 34 | } -------------------------------------------------------------------------------- /src/ce/core/model/api/WriteOptions.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.api; 13 | 14 | typedef WriteOptions = { 15 | 16 | /** 17 | * Specify that you want the data to be first decoded from base64 before being written to the file. 18 | * For example, if you have base64 encoded image data, you can use this flag to first decode the data 19 | * before writing the image file. 20 | */ 21 | @:optional var base64decode : Bool; 22 | 23 | } -------------------------------------------------------------------------------- /src/ce/core/model/oauth/OAuthResult.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.oauth; 13 | 14 | typedef OAuthResult = { 15 | 16 | /** 17 | * true if the user chooses not to authorize the application. 18 | */ 19 | @:optional var notApproved : Bool; 20 | 21 | /** 22 | * The request token that was just authorized. The request token secret isn't sent back. 23 | */ 24 | @:optional var oauthToken : String; 25 | 26 | /** 27 | * The user's unique Dropbox ID. 28 | */ 29 | @:optional var uid : String; 30 | } -------------------------------------------------------------------------------- /src/ce/core/model/unifile/Account.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.unifile; 13 | 14 | typedef QuotaInfo = { 15 | 16 | var available : Int; 17 | var used : Int; 18 | } 19 | 20 | typedef Account = { 21 | 22 | var displayName : String; 23 | var quotaInfo : Null; 24 | } -------------------------------------------------------------------------------- /src/ce/core/model/unifile/ConnectResult.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.unifile; 13 | 14 | typedef ConnectResult = { 15 | 16 | var success : Bool; 17 | var message : String; 18 | var authorizeUrl : String; 19 | } -------------------------------------------------------------------------------- /src/ce/core/model/unifile/File.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.unifile; 13 | 14 | typedef File = { 15 | 16 | var name : String; 17 | var bytes : Int; 18 | var modified : Null; 19 | var isDir : Bool; 20 | } -------------------------------------------------------------------------------- /src/ce/core/model/unifile/LoginResult.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.unifile; 13 | 14 | typedef LoginResult = { 15 | 16 | var success : Bool; 17 | } -------------------------------------------------------------------------------- /src/ce/core/model/unifile/LogoutResult.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.unifile; 13 | 14 | typedef LogoutResult = { 15 | 16 | var success : Bool; 17 | var message : String; 18 | } -------------------------------------------------------------------------------- /src/ce/core/model/unifile/Service.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.unifile; 13 | 14 | class Service { 15 | 16 | public function new(n : ce.core.model.Service, dn : String, is : String, d : String, v : Bool, il : Bool, ic : Bool, ioa : Bool, ? a : Null) { 17 | 18 | this.name = n; 19 | this.displayName = dn; 20 | this.imageSmall = is; 21 | this.description = d; 22 | this.visible = v; 23 | this.isLoggedIn = il; 24 | this.isConnected = ic; 25 | this.isOAuth = ioa; 26 | this.account = a; 27 | } 28 | 29 | public var name (default, null) : ce.core.model.Service; 30 | public var displayName (default, null) : String; 31 | public var imageSmall (default, null) : String; 32 | public var description (default, null) : String; 33 | public var visible (default, null) : Bool; 34 | public var isLoggedIn (default, set) : Bool; 35 | public var isOAuth (default, null) : Bool; 36 | public var isConnected (default, default) : Bool; 37 | public var account (default, set) : Null; 38 | 39 | /// 40 | // CALLBACKS 41 | // 42 | 43 | public dynamic function onLoginStateChanged() : Void { } 44 | 45 | public dynamic function onAccountChanged() : Void { } 46 | 47 | 48 | /// 49 | // GETTERS / SETTERS 50 | // 51 | 52 | public function set_isLoggedIn(v : Bool) : Bool { 53 | 54 | if (v == isLoggedIn) { 55 | 56 | return v; 57 | } 58 | isLoggedIn = v; 59 | 60 | onLoginStateChanged(); 61 | 62 | return isLoggedIn; 63 | } 64 | 65 | public function set_account(v : Null) : Null { 66 | 67 | if (v == account) { 68 | 69 | return v; 70 | } 71 | account = v; 72 | 73 | onAccountChanged(); 74 | 75 | return account; 76 | } 77 | } -------------------------------------------------------------------------------- /src/ce/core/model/unifile/UnifileError.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.unifile; 13 | 14 | typedef UnifileError = { 15 | 16 | var success : Bool; 17 | var code : Int; 18 | var message : String; 19 | } -------------------------------------------------------------------------------- /src/ce/core/model/unifile/UploadResult.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.model.unifile; 13 | 14 | typedef UploadResult = { 15 | 16 | var success : Bool; 17 | 18 | } 19 | /* FIXME can also be : 20 | [ 21 | { 22 | "path": "//logo.png", 23 | "status": { 24 | "success": true 25 | } 26 | }, 27 | { 28 | "path": "//header.png", 29 | "status": { 30 | "success": true 31 | } 32 | } 33 | ] 34 | */ -------------------------------------------------------------------------------- /src/ce/core/parser/json/Json2Primitive.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.parser.json; 13 | 14 | /** 15 | * Common methods to parse JSON feeds 16 | */ 17 | class Json2Primitive { 18 | 19 | static public function checkPath( node : Dynamic, path : String, optional : Bool = false ) : Null { 20 | //trace("checking "+path+" on "+node); 21 | var pathes : Array = path.split('.'); 22 | 23 | //return doCheckPath( node, pathes, optional ); 24 | var n : Null = doCheckPath( node, pathes, optional ); 25 | 26 | if ( n == null && !optional ) { 27 | 28 | trace(path + " not found !"); 29 | } 30 | //trace("checking "+path+" returned "+n); 31 | return n; 32 | } 33 | 34 | static public function doCheckPath( node : Dynamic, pathes : Array, optional : Bool = false ) : Null { 35 | 36 | var p = pathes.shift(); 37 | 38 | if ( !Reflect.hasField( node, p ) || Reflect.field(node, p) == null ) { 39 | 40 | if (!optional) { 41 | 42 | trace(p+' not found !'); 43 | // TODO throw ? 44 | } 45 | return null; 46 | } 47 | if ( pathes.length > 0 ) { 48 | 49 | return doCheckPath( Reflect.field(node, p), pathes, optional ); 50 | } 51 | 52 | return Reflect.field( node, p ); 53 | } 54 | 55 | static public function node2String( node : Dynamic, path : String, nullable : Bool = false ) : Null { 56 | 57 | var n : Null = checkPath( node, path, nullable ); 58 | 59 | if( n == null ) { 60 | 61 | if (!nullable) { 62 | 63 | // TODO throw ? 64 | } 65 | return null; 66 | } 67 | return Std.string( n ); 68 | } 69 | 70 | static public function node2Float( node : Dynamic, path : String, nullable : Bool = false ) : Null { 71 | 72 | return Std.parseFloat( node2String( node, path, nullable ) ); 73 | } 74 | 75 | static public function node2Int( node : Dynamic, path : String, nullable : Bool = false ) : Null { 76 | 77 | return Std.parseInt( node2String( node, path, nullable ) ); 78 | } 79 | 80 | static public function node2Bool( node : Dynamic, path : String, nullable : Bool = false ) : Bool { 81 | 82 | var v : Null = node2String( node, path, nullable ); 83 | 84 | return ( v != null ? (v == "true" || v == "1") : false ); 85 | } 86 | } -------------------------------------------------------------------------------- /src/ce/core/parser/oauth/Str2OAuthResult.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.parser.oauth; 13 | 14 | import ce.core.model.oauth.OAuthResult; 15 | 16 | import ce.core.parser.json.Json2Primitive; 17 | 18 | import haxe.Json; 19 | 20 | class Str2OAuthResult { 21 | 22 | static inline var PARAM_NOT_APPROVED : String = "not_approved"; 23 | static inline var PARAM_OAUTH_TOKEN : String = "oauth_token"; 24 | static inline var PARAM_UID : String = "uid"; 25 | 26 | static public function parse(dataStr : String) : OAuthResult { 27 | 28 | if (dataStr.indexOf('?') == 0) { 29 | dataStr = dataStr.substr(1); 30 | } 31 | var dataArr : Array = dataStr.split("&"); 32 | 33 | var res : OAuthResult = {}; 34 | 35 | for (pStr in dataArr) { 36 | 37 | var kv : Array = pStr.split("="); 38 | 39 | res = parseValue(res, kv[0], kv[1]); 40 | } 41 | return res; 42 | } 43 | 44 | static private function parseValue(obj : OAuthResult, key : String, value : String) : OAuthResult { 45 | 46 | switch (key) { 47 | 48 | case PARAM_NOT_APPROVED: 49 | 50 | obj.notApproved = value.toLowerCase() == "true" || value == "1" ? true : false; 51 | 52 | case PARAM_OAUTH_TOKEN: 53 | 54 | obj.oauthToken = value; 55 | 56 | case PARAM_UID: 57 | 58 | obj.uid = value; 59 | 60 | default: 61 | 62 | throw "unexpected parameter " + key; 63 | } 64 | return obj; 65 | } 66 | } -------------------------------------------------------------------------------- /src/ce/core/parser/unifile/Json2Account.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.parser.unifile; 13 | 14 | import ce.core.model.unifile.Account; 15 | 16 | import ce.core.parser.json.Json2Primitive; 17 | 18 | import haxe.Json; 19 | 20 | class Json2Account { 21 | 22 | static public function parseAccount(? dataStr : String, ? obj : Dynamic) : Null { 23 | 24 | if (obj == null) { 25 | 26 | if (dataStr == null) return null; 27 | 28 | obj = Json.parse( dataStr ); 29 | } 30 | 31 | return { 32 | displayName: Json2Primitive.node2String(obj, "display_name", false), 33 | quotaInfo: Reflect.hasField(obj, "quota_info") ? parseQuotaInfo(Reflect.field(obj, "quota_info")) : null 34 | }; 35 | } 36 | 37 | static public function parseQuotaInfo(obj : Dynamic) : QuotaInfo { 38 | 39 | return { 40 | available: Json2Primitive.node2Int(obj, "available", false), 41 | used: Json2Primitive.node2Int(obj, "used", false) 42 | }; 43 | } 44 | } -------------------------------------------------------------------------------- /src/ce/core/parser/unifile/Json2ConnectResult.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.parser.unifile; 13 | 14 | import ce.core.model.unifile.ConnectResult; 15 | 16 | import ce.core.parser.json.Json2Primitive; 17 | 18 | import haxe.Json; 19 | 20 | class Json2ConnectResult { 21 | 22 | static public function parse(dataStr : String) : ConnectResult { 23 | 24 | var obj : Dynamic = Json.parse( dataStr ); 25 | 26 | return { 27 | success: Json2Primitive.node2Bool(obj, "success", false), 28 | message: Json2Primitive.node2String(obj, "message", false), 29 | authorizeUrl: Json2Primitive.node2String(obj, "authorize_url", false), 30 | }; 31 | } 32 | } -------------------------------------------------------------------------------- /src/ce/core/parser/unifile/Json2File.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.parser.unifile; 13 | 14 | import ce.core.model.unifile.File; 15 | 16 | import ce.core.parser.json.Json2Primitive; 17 | 18 | import haxe.Json; 19 | 20 | class Json2File { 21 | 22 | static public function parseFileCollection(dataStr : String) : Array { 23 | 24 | var col : Array = Json.parse( dataStr ); 25 | 26 | var fileCol : Array = new Array(); 27 | 28 | for (f in col) { 29 | 30 | fileCol.push(parseFile(f)); 31 | } 32 | 33 | return fileCol; 34 | } 35 | 36 | static public function parseFile(obj : Dynamic) : File { 37 | 38 | var dStr : String = Json2Primitive.node2String(obj, "modified", false); 39 | 40 | return { 41 | name: Json2Primitive.node2String(obj, "name", false), 42 | bytes: Json2Primitive.node2Int(obj, "bytes", false), 43 | modified: dStr != null ? Date.fromTime( untyped __js__("new Date(dStr).getTime()") ) : null, // FIXME would be better to get a timestamp from unifie 44 | isDir: Json2Primitive.node2Bool(obj, "is_dir", false) 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ce/core/parser/unifile/Json2LoginResult.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.parser.unifile; 13 | 14 | import ce.core.model.unifile.LoginResult; 15 | 16 | import ce.core.parser.json.Json2Primitive; 17 | 18 | import haxe.Json; 19 | 20 | class Json2LoginResult { 21 | 22 | static public function parse(dataStr : String) : LoginResult { 23 | 24 | var obj : Dynamic = Json.parse( dataStr ); 25 | 26 | return { 27 | success: Json2Primitive.node2Bool(obj, "success", false) 28 | }; 29 | } 30 | } -------------------------------------------------------------------------------- /src/ce/core/parser/unifile/Json2LogoutResult.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.parser.unifile; 13 | 14 | import ce.core.model.unifile.LogoutResult; 15 | 16 | import ce.core.parser.json.Json2Primitive; 17 | 18 | import haxe.Json; 19 | 20 | class Json2LogoutResult { 21 | 22 | static public function parse(dataStr : String) : LogoutResult { 23 | 24 | var obj : Dynamic = Json.parse( dataStr ); 25 | 26 | return { 27 | success: Json2Primitive.node2Bool(obj, "success", false), 28 | message: Json2Primitive.node2String(obj, "message", false) 29 | }; 30 | } 31 | } -------------------------------------------------------------------------------- /src/ce/core/parser/unifile/Json2Service.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.parser.unifile; 13 | 14 | import ce.core.model.unifile.Service; 15 | 16 | import ce.core.parser.json.Json2Primitive; 17 | 18 | import haxe.Json; 19 | 20 | class Json2Service { 21 | 22 | static public function parseServiceCollection(dataStr : String) : Array { 23 | 24 | var col : Array = Json.parse( dataStr ); 25 | 26 | var serviceCol : Array = new Array(); 27 | 28 | for (s in col) { 29 | 30 | serviceCol.push(parseService(s)); 31 | } 32 | 33 | return serviceCol; 34 | } 35 | 36 | static public function parseService(obj : Dynamic) : Service { 37 | 38 | return new Service( 39 | Json2Primitive.node2String(obj, "name", false), 40 | Json2Primitive.node2String(obj, "display_name", false), 41 | Json2Primitive.node2String(obj, "image_small", false), 42 | Json2Primitive.node2String(obj, "description", false), 43 | Json2Primitive.node2Bool(obj, "visible", false), 44 | Json2Primitive.node2Bool(obj, "isLoggedIn", false), 45 | Json2Primitive.node2Bool(obj, "isConnected", false), 46 | Json2Primitive.node2Bool(obj, "isOAuth", false), 47 | Reflect.hasField(obj, "user") ? Json2Account.parseAccount(null, Reflect.field(obj, "user")) : null 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ce/core/parser/unifile/Json2UnifileError.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.parser.unifile; 13 | 14 | import ce.core.model.unifile.UnifileError; 15 | 16 | import ce.core.parser.json.Json2Primitive; 17 | 18 | import haxe.Json; 19 | 20 | class Json2UnifileError { 21 | 22 | static public function parseUnifileError(dataStr : String) : UnifileError { 23 | 24 | var obj : Dynamic = Json.parse( dataStr ); 25 | 26 | return { 27 | success: Json2Primitive.node2Bool(obj, "success", false), 28 | code: Json2Primitive.node2Int(obj, "code", false), 29 | message: Json2Primitive.node2String(obj, "message", false) 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ce/core/parser/unifile/Json2UploadResult.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.parser.unifile; 13 | 14 | import ce.core.model.unifile.UploadResult; 15 | 16 | import ce.core.parser.json.Json2Primitive; 17 | 18 | import haxe.Json; 19 | 20 | class Json2UploadResult { 21 | 22 | static public function parse(dataStr : String) : UploadResult { 23 | 24 | var obj : Dynamic = Json.parse( dataStr ); 25 | 26 | return { 27 | success: Json2Primitive.node2Bool(obj, "success", false) 28 | }; 29 | } 30 | } -------------------------------------------------------------------------------- /src/ce/core/service/UnifileSrv.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.service; 13 | 14 | import ce.core.config.Config; 15 | 16 | import ce.core.parser.unifile.Json2Service; 17 | import ce.core.parser.unifile.Json2ConnectResult; 18 | import ce.core.parser.unifile.Json2LoginResult; 19 | import ce.core.parser.unifile.Json2Account; 20 | import ce.core.parser.unifile.Json2File; 21 | import ce.core.parser.unifile.Json2LogoutResult; 22 | import ce.core.parser.unifile.Json2UploadResult; 23 | import ce.core.parser.unifile.Json2UnifileError; 24 | 25 | import ce.core.model.unifile.Service; 26 | import ce.core.model.unifile.ConnectResult; 27 | import ce.core.model.unifile.LoginResult; 28 | import ce.core.model.unifile.Account; 29 | import ce.core.model.unifile.File; 30 | import ce.core.model.unifile.LogoutResult; 31 | import ce.core.model.unifile.UploadResult; 32 | import ce.core.model.unifile.UnifileError; 33 | 34 | import js.html.Blob; 35 | import js.html.DOMFormData; 36 | import js.html.XMLHttpRequest; 37 | 38 | import haxe.ds.StringMap; 39 | 40 | using StringTools; 41 | 42 | class UnifileSrv { 43 | 44 | static inline var ENDPOINT_LIST_SERVICES : String = "services/list"; 45 | static inline var ENDPOINT_CONNECT : String = "{srv}/connect"; 46 | static inline var ENDPOINT_LOGIN : String = "{srv}/login"; 47 | static inline var ENDPOINT_ACCOUNT : String = "{srv}/account"; 48 | static inline var ENDPOINT_LOGOUT : String = "{srv}/logout"; 49 | static inline var ENDPOINT_LS : String = "{srv}/exec/ls/{path}"; 50 | static inline var ENDPOINT_RM : String = "{srv}/exec/rm/{path}"; 51 | static inline var ENDPOINT_MKDIR : String = "{srv}/exec/mkdir/{path}"; 52 | static inline var ENDPOINT_CP : String = "exec/cp"; 53 | static inline var ENDPOINT_MV : String = "{srv}/exec/mv/{path}"; 54 | static inline var ENDPOINT_GET : String = "{srv}/exec/get/{uri}"; 55 | static inline var ENDPOINT_PUT : String = "{srv}/exec/put/{path}"; 56 | 57 | public function new(config : Config) : Void { 58 | 59 | this.config = config; 60 | } 61 | 62 | var config : Config; 63 | 64 | 65 | /// 66 | // API 67 | // 68 | 69 | public function generateUrl(srv : String, path : String, filename : String) : String { 70 | 71 | return config.unifileEndpoint + ENDPOINT_GET.replace("{srv}", srv) 72 | .replace("{uri}", path.length > 1 ? path.substr(1) + filename : filename); 73 | } 74 | 75 | public function explodeUrl(url : String) : { srv : String, path : String, filename : String } { 76 | 77 | if (url.indexOf(config.unifileEndpoint) != 0) { 78 | 79 | throw "ERROR: can't convert url to path: " + url; 80 | } 81 | var parsedUrl : String = url.substr(config.unifileEndpoint.length); 82 | 83 | if (parsedUrl.indexOf("/exec/get/") != parsedUrl.indexOf("/")) { 84 | 85 | throw "ERROR: can't convert url to path: " + url; 86 | } 87 | var srv : String = parsedUrl.substr(0, parsedUrl.indexOf("/")); 88 | 89 | parsedUrl = parsedUrl.substr(parsedUrl.indexOf("/exec/get/") + "/exec/get/".length); 90 | 91 | var filename : String = ""; 92 | var path : String = ""; 93 | 94 | if (parsedUrl.lastIndexOf('/') > -1) { 95 | 96 | filename = parsedUrl.substr(parsedUrl.lastIndexOf('/')+1); 97 | path = parsedUrl.substr(0, parsedUrl.lastIndexOf('/')+1); 98 | 99 | } else { 100 | 101 | filename = parsedUrl; 102 | } 103 | 104 | return { 'srv': srv, 'path': path, 'filename': filename }; 105 | } 106 | 107 | public function listServices(onSuccess : StringMap -> Void, onError : UnifileError -> Void) : Void { 108 | 109 | var req : XMLHttpRequest = new XMLHttpRequest(); 110 | 111 | req.onload = function(?_) { 112 | 113 | if (req.status != 200) { 114 | 115 | var err : UnifileError = Json2UnifileError.parseUnifileError(req.responseText); 116 | 117 | onError(err); 118 | 119 | } else { 120 | 121 | var sl : Array = Json2Service.parseServiceCollection(req.responseText); 122 | 123 | var slm : StringMap = new StringMap(); 124 | 125 | for (s in sl) { 126 | 127 | slm.set(s.name, s); 128 | } 129 | 130 | onSuccess(slm); 131 | } 132 | } 133 | 134 | req.onerror = function(?_) { 135 | 136 | onError({ success: false, code: 0, message: "The request has failed." }); 137 | } 138 | 139 | req.open("GET", config.unifileEndpoint + ENDPOINT_LIST_SERVICES); 140 | 141 | req.send(); 142 | } 143 | 144 | public function connect(srv : String, onSuccess : ConnectResult -> Void, onError : UnifileError -> Void) : Void { 145 | 146 | var req : XMLHttpRequest = new XMLHttpRequest(); 147 | 148 | req.onload = function(?_) { 149 | 150 | if (req.status != 200) { 151 | 152 | var err : UnifileError = Json2UnifileError.parseUnifileError(req.responseText); 153 | 154 | onError(err); 155 | 156 | } else { 157 | 158 | onSuccess(Json2ConnectResult.parse(req.responseText)); 159 | } 160 | } 161 | 162 | req.onerror = function(?_) { 163 | 164 | onError({ success: false, code: 0, message: "The request has failed." }); 165 | } 166 | 167 | req.open("GET", config.unifileEndpoint + ENDPOINT_CONNECT.replace("{srv}", srv)); 168 | 169 | req.send(); 170 | } 171 | 172 | public function login(srv : String, onSuccess : LoginResult -> Void, onError : UnifileError -> Void) : Void { 173 | 174 | var req : XMLHttpRequest = new XMLHttpRequest(); 175 | 176 | req.onload = function(?_) { 177 | 178 | if (req.status != 200) { 179 | 180 | var err : UnifileError = Json2UnifileError.parseUnifileError(req.responseText); 181 | 182 | onError(err); 183 | 184 | } else { 185 | 186 | onSuccess(Json2LoginResult.parse(req.responseText)); 187 | } 188 | } 189 | 190 | req.onerror = function(?_) { 191 | 192 | onError({ success: false, code: 0, message: "The request has failed." }); 193 | } 194 | 195 | req.open("GET", config.unifileEndpoint + ENDPOINT_LOGIN.replace("{srv}", srv)); 196 | 197 | req.send(); 198 | } 199 | 200 | public function account(srv : String, onSuccess : Account -> Void, onError : UnifileError -> Void) : Void { 201 | 202 | var req : XMLHttpRequest = new XMLHttpRequest(); 203 | 204 | req.onload = function(?_) { 205 | 206 | if (req.status != 200) { 207 | 208 | var err : UnifileError = Json2UnifileError.parseUnifileError(req.responseText); 209 | 210 | onError(err); 211 | 212 | } else { 213 | 214 | onSuccess(Json2Account.parseAccount(req.responseText)); 215 | } 216 | } 217 | 218 | req.onerror = function(?_) { 219 | 220 | onError({ success: false, code: 0, message: "The request has failed." }); 221 | } 222 | 223 | req.open("POST", config.unifileEndpoint + ENDPOINT_ACCOUNT.replace("{srv}", srv)); 224 | 225 | req.send(); 226 | } 227 | 228 | public function logout(srv : String, onSuccess : LogoutResult -> Void, onError : UnifileError -> Void) : Void { 229 | 230 | var req : XMLHttpRequest = new XMLHttpRequest(); 231 | 232 | req.onload = function(?_) { 233 | 234 | if (req.status != 200) { 235 | 236 | var err : UnifileError = Json2UnifileError.parseUnifileError(req.responseText); 237 | 238 | onError(err); 239 | 240 | } else { 241 | 242 | onSuccess(Json2LogoutResult.parse(req.responseText)); 243 | } 244 | } 245 | 246 | req.onerror = function(?_) { 247 | 248 | onError({ success: false, code: 0, message: "The request has failed." }); 249 | } 250 | 251 | req.open("GET", config.unifileEndpoint + ENDPOINT_LOGOUT.replace("{srv}", srv)); 252 | 253 | req.send(); 254 | } 255 | 256 | public function ls(srv : String, path : String, onSuccess : StringMap -> Void, onError : UnifileError -> Void) : Void { 257 | 258 | // on FF, ls// throws an error 259 | path = path == '/' ? '' : path; 260 | 261 | var req : XMLHttpRequest = new XMLHttpRequest(); 262 | 263 | req.onload = function(?_) { 264 | 265 | if (req.status != 200) { 266 | 267 | var err : UnifileError = Json2UnifileError.parseUnifileError(req.responseText); 268 | 269 | onError(err); 270 | 271 | } else { 272 | 273 | var fa : Array = Json2File.parseFileCollection(req.responseText); 274 | 275 | var fsm : StringMap = new StringMap(); 276 | 277 | for (f in fa) { 278 | 279 | fsm.set(f.name, f); 280 | } 281 | 282 | onSuccess(fsm); 283 | } 284 | } 285 | 286 | req.onerror = function(?_) { 287 | 288 | onError({ success: false, code: 0, message: "The request has failed." }); 289 | } 290 | 291 | req.open("GET", config.unifileEndpoint + ENDPOINT_LS.replace("{srv}", srv).replace("{path}", path), true); 292 | 293 | req.send(); 294 | } 295 | 296 | public function rm(srv : String, path : String, onSuccess : Void -> Void, onError : UnifileError -> Void) : Void { 297 | 298 | var req : XMLHttpRequest = new XMLHttpRequest(); 299 | 300 | req.onload = function(?_) { 301 | 302 | if (req.status != 200) { 303 | 304 | var err : UnifileError = Json2UnifileError.parseUnifileError(req.responseText); 305 | 306 | onError(err); 307 | 308 | } else { 309 | 310 | onSuccess(); 311 | } 312 | } 313 | 314 | req.onerror = function(?_) { 315 | 316 | onError({ success: false, code: 0, message: "The request has failed." }); 317 | } 318 | 319 | req.open("GET", config.unifileEndpoint + ENDPOINT_RM.replace("{srv}", srv).replace("{path}", path), true); 320 | 321 | req.send(); 322 | } 323 | 324 | public function mkdir(srv : String, path : String, onSuccess : Void -> Void, onError : UnifileError -> Void) : Void { 325 | 326 | var req : XMLHttpRequest = new XMLHttpRequest(); 327 | 328 | req.onload = function(?_) { 329 | 330 | if (req.status != 200) { 331 | 332 | var err : UnifileError = Json2UnifileError.parseUnifileError(req.responseText); 333 | 334 | onError(err); 335 | 336 | } else { 337 | 338 | onSuccess(); 339 | } 340 | } 341 | 342 | req.onerror = function(?_) { 343 | 344 | onError({ success: false, code: 0, message: "The request has failed." }); 345 | } 346 | 347 | req.open("GET", config.unifileEndpoint + ENDPOINT_MKDIR.replace("{srv}", srv).replace("{path}", path)); 348 | 349 | req.send(); 350 | } 351 | 352 | public function cp() : Void { 353 | 354 | 355 | } 356 | 357 | public function mv(srv : String, oldPath : String, newPath : String, onSuccess : Void -> Void, onError : UnifileError -> Void) : Void { 358 | 359 | var req : XMLHttpRequest = new XMLHttpRequest(); 360 | 361 | req.onload = function(?_) { 362 | 363 | if (req.status != 200) { 364 | 365 | var err : UnifileError = Json2UnifileError.parseUnifileError(req.responseText); 366 | 367 | onError(err); 368 | 369 | } else { 370 | 371 | onSuccess(); 372 | } 373 | } 374 | 375 | req.onerror = function(?_) { 376 | 377 | onError({ success: false, code: 0, message: "The request has failed." }); 378 | } 379 | 380 | req.open("GET", config.unifileEndpoint + ENDPOINT_MV.replace("{srv}", srv).replace("{path}", oldPath + ":" + newPath)); 381 | 382 | req.send(); 383 | } 384 | 385 | public function upload(? blobs : StringMap, ? files : js.html.FileList, srv : String, path : String, onSuccess : Void -> Void, onError : UnifileError -> Void) : Void { 386 | 387 | // enforce path as a folder path 388 | if (path != "" && path.lastIndexOf('/') != path.length - 1) { // TODO check in unifile if it's not a bug 389 | 390 | path += '/'; 391 | } 392 | var formData : DOMFormData = new DOMFormData(); 393 | 394 | if (files != null) { 395 | 396 | for (f in files) { 397 | 398 | if (Reflect.isObject(f)) { // raw data from drop event or input[type=file] contains methods we need to filter 399 | trace("appended "+f.name); 400 | untyped __js__("formData.append('data', f, f.name);"); // @see https://github.com/HaxeFoundation/haxe/issues/2867 401 | } 402 | } 403 | } 404 | if (blobs != null) { 405 | 406 | if (Lambda.count(blobs) == 1) { // FIXME this is a temporary workaround for following issue on FF: https://bugzilla.mozilla.org/show_bug.cgi?id=690659 407 | 408 | path += blobs.keys().next(); 409 | } 410 | for (fn in blobs.keys()) { 411 | 412 | untyped __js__("formData.append('data', blobs.get(fn), fn);"); // @see https://github.com/HaxeFoundation/haxe/issues/2867 413 | } 414 | } 415 | 416 | var xhttp : XMLHttpRequest = new XMLHttpRequest(); 417 | 418 | xhttp.open("POST", config.unifileEndpoint + ENDPOINT_PUT.replace("{srv}", srv).replace("{path}", path)); 419 | 420 | xhttp.onload = function(?_) { 421 | 422 | // FIXME check UploadResult (fix on unifile side difference between one file and several files upload results) 423 | //var resp : UploadResult = Json2UploadResult.parse(xhttp.responseText); 424 | 425 | if (xhttp.status == 200) { 426 | 427 | onSuccess(); 428 | 429 | } else { 430 | 431 | var err : UnifileError = Json2UnifileError.parseUnifileError(xhttp.responseText); 432 | 433 | onError(err); 434 | } 435 | }; 436 | 437 | xhttp.onerror = function(?_) { 438 | 439 | onError({ success: false, code: 0, message: "The request has failed." }); 440 | } 441 | 442 | xhttp.send(formData); 443 | } 444 | 445 | /** 446 | * Requests a file from a Unifile endpoint. 447 | */ 448 | public function get(url : String, onSuccess : String -> Void, onError : UnifileError -> Void) : Void { 449 | 450 | var req : XMLHttpRequest = new XMLHttpRequest(); 451 | 452 | req.onload = function(?_) { 453 | 454 | if (req.status != 200) { 455 | 456 | var err : UnifileError = Json2UnifileError.parseUnifileError(req.responseText); 457 | 458 | onError(err); 459 | 460 | } else { 461 | 462 | onSuccess(req.responseText); 463 | } 464 | } 465 | 466 | req.onerror = function(?_) { 467 | 468 | onError({ success: false, code: 0, message: "The request has failed." }); 469 | } 470 | 471 | req.open("GET", url); 472 | 473 | req.send(); 474 | } 475 | } -------------------------------------------------------------------------------- /src/ce/core/view/AlertPopup.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.view; 13 | 14 | import js.html.Element; 15 | 16 | using StringTools; 17 | using ce.util.HtmlTools; 18 | 19 | class AlertPopup { 20 | 21 | static inline var CLASS_ERROR : String = "error"; 22 | static inline var CLASS_WARNING : String = "warning"; 23 | 24 | static inline var SELECTOR_TEXT : String = ".txt"; 25 | static inline var SELECTOR_CHOICE_TMPL : String = ".choice"; 26 | 27 | public function new(elt : Element) { 28 | 29 | this.elt = elt; 30 | 31 | this.txtElt = elt.querySelector(SELECTOR_TEXT); 32 | 33 | this.choiceTmpl = txtElt.querySelector(SELECTOR_CHOICE_TMPL); 34 | txtElt.removeChild(choiceTmpl); 35 | 36 | this.choicesElts = []; 37 | } 38 | 39 | var elt : Element; 40 | var txtElt : Element; 41 | 42 | var choiceTmpl : Element; 43 | 44 | var choicesElts : Array; 45 | 46 | 47 | /// 48 | // API 49 | // 50 | 51 | public function setMsg(msg : String, ? level : Int = 2, ? choices : Array<{ msg : String, cb : Void -> Void }>) : Void { 52 | 53 | while (choicesElts.length > 0) { 54 | 55 | txtElt.removeChild(choicesElts.pop()); 56 | } 57 | txtElt.textContent = msg; 58 | 59 | if (choices != null) { 60 | 61 | for (c in choices) { 62 | 63 | var nc : Element = cast choiceTmpl.cloneNode(true); 64 | var tc : { msg : String, cb : Void -> Void } = c; 65 | nc.textContent = tc.msg; 66 | nc.addEventListener("click", function(?_){ tc.cb(); }); 67 | txtElt.appendChild(nc); 68 | } 69 | } 70 | switch (level) { 71 | 72 | case 0: 73 | elt.toggleClass(CLASS_ERROR, true); 74 | elt.toggleClass(CLASS_WARNING, false); 75 | 76 | case 1: 77 | elt.toggleClass(CLASS_ERROR, false); 78 | elt.toggleClass(CLASS_WARNING, true); 79 | 80 | default: 81 | elt.toggleClass(CLASS_ERROR, false); 82 | elt.toggleClass(CLASS_WARNING, false); 83 | } 84 | 85 | haxe.Timer.delay(function(){ txtElt.style.marginTop = "-" + Std.string(txtElt.offsetHeight / 2 + 20) + "px"; }, 0); 86 | } 87 | } -------------------------------------------------------------------------------- /src/ce/core/view/Application.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.view; 13 | 14 | import ce.core.model.SortField; 15 | import ce.core.model.SortOrder; 16 | import ce.core.model.Service; 17 | 18 | import ce.core.model.oauth.OAuthResult; 19 | import ce.core.parser.oauth.Str2OAuthResult; 20 | 21 | import ce.core.config.Config; 22 | 23 | import js.Browser; 24 | import js.html.Element; 25 | 26 | using ce.util.HtmlTools; 27 | using StringTools; 28 | 29 | class Application { 30 | 31 | static var oauthCbListener : String -> Void; 32 | 33 | @:expose('CEoauthCb') 34 | static function oauthCb(pStr : String) : Void { // FIXME this prevents from multi-instancing 35 | 36 | if (oauthCbListener != null) { 37 | 38 | oauthCbListener(pStr); 39 | } 40 | } 41 | 42 | static inline var PLACE_HOLDER_LOGOUT_NAME : String = "{name}"; 43 | 44 | static inline var ID_APPLICATION : String = "cloud-explorer"; 45 | 46 | static inline var CLASS_LOADING : String = "loading"; 47 | static inline var CLASS_STARTING : String = "starting"; 48 | static inline var CLASS_BROWSING : String = "browsing"; 49 | static inline var CLASS_AUTHORIZING : String = "authorizing"; 50 | static inline var CLASS_LOGGED_IN : String = "loggedin"; 51 | static inline var CLASS_ALERTING : String = "alerting"; 52 | static inline var CLASS_MAKING_NEW_FOLDER : String = "making-new-folder"; 53 | static inline var CLASS_SELECTING : String = "selecting"; 54 | 55 | static inline var CLASS_EXPORT_OVERWRITING : String = "export-overwriting"; 56 | 57 | static inline var CLASS_MODE_SINGLE_FILE_SELECTION : String = "single-file-sel-mode"; 58 | static inline var CLASS_MODE_SINGLE_FILE_EXPORT : String = "single-file-exp-mode"; 59 | static inline var CLASS_MODE_IS_LOGGED_IN : String = "is-logged-in-mode"; 60 | static inline var CLASS_MODE_REQUEST_AUTHORIZE : String = "request-authorize-mode"; 61 | 62 | static inline var CLASS_ITEMS_LIST : String = "items-list"; 63 | static inline var CLASS_ITEMS_ICONS : String = "items-icons"; 64 | 65 | static inline var CLASS_PREFIX_SORTEDBY : String = "sortedby-"; 66 | static inline var CLASS_PREFIX_SERVICE : String = "srv-"; 67 | 68 | static inline var SELECTOR_LOGOUT_BTN : String = ".logoutBtn"; 69 | static inline var SELECTOR_CLOSE_BTN : String = ".closeBtn"; 70 | static inline var SELECTOR_HOME : String = ".home"; 71 | static inline var SELECTOR_FILE_BROWSER : String = ".fileBrowser"; 72 | static inline var SELECTOR_ALERT_POPUP : String = ".alertPopup"; 73 | static inline var SELECTOR_AUTH_POPUP : String = ".authPopup"; 74 | static inline var SELECTOR_BREADCRUMB : String = ".breadcrumb"; 75 | static inline var SELECTOR_DROPZONE : String = ".dropzone"; 76 | static inline var SELECTOR_EXPORT : String = ".export"; 77 | static inline var SELECTOR_NEW_FOLDER_BTN : String = ".newFolderBtn"; 78 | static inline var SELECTOR_PARENT_FOLDER_BTN : String = ".parentFolderBtn"; 79 | static inline var SELECTOR_DELETE_BTN : String = ".deleteBtn"; 80 | static inline var SELECTOR_ITEMS_LIST_BTN : String = ".listItemsBtn"; 81 | static inline var SELECTOR_ITEMS_ICON_BTN : String = ".iconItemsBtn"; 82 | 83 | public function new(iframe : js.html.IFrameElement, config : Config) { 84 | 85 | this.iframe = iframe; 86 | this.config = config; 87 | 88 | initFrame(); 89 | 90 | oauthCbListener = listenOAuthCb; 91 | } 92 | 93 | var config : Config; 94 | 95 | var iframe : js.html.IFrameElement; 96 | 97 | var rootElt : Element; 98 | 99 | 100 | /// 101 | // CALLBACKS 102 | // 103 | 104 | public dynamic function onClicked() : Void { } 105 | 106 | public dynamic function onSortBtnClicked(f : SortField) : Void { } 107 | 108 | public dynamic function onViewReady() : Void { } 109 | 110 | public dynamic function onLogoutClicked() : Void { } 111 | 112 | public dynamic function onCloseClicked() : Void { } 113 | 114 | public dynamic function onServiceLoginRequest(name : String) : Void { } 115 | 116 | public dynamic function onServiceLogoutRequest(name : String) : Void { } 117 | 118 | public dynamic function onServiceClicked(name : String) : Void { } 119 | 120 | public dynamic function onFileClicked(id : String) : Void { } 121 | 122 | public dynamic function onFileSelectClicked(id : String) : Void { } 123 | 124 | public dynamic function onFileDeleteClicked(id : String) : Void { } 125 | 126 | public dynamic function onFileRenameRequested(id : String, value : String) : Void { } 127 | 128 | public dynamic function onFileCheckedStatusChanged(id : String) : Void { } 129 | 130 | public dynamic function onNavBtnClicked(srv : String, path : String) : Void { } 131 | 132 | public dynamic function onAuthorizationWindowBlocked() : Void { } 133 | 134 | public dynamic function onServiceAuthorizationDone(? r : Null) : Void { } 135 | 136 | public dynamic function onSaveExportClicked() : Void { } 137 | 138 | public dynamic function onOverwriteExportClicked() : Void { } 139 | 140 | public dynamic function onExportNameChanged() : Void { } 141 | 142 | public dynamic function onInputFilesChanged() : Void { } 143 | 144 | public dynamic function onFilesDropped(files : js.html.FileList) : Void { } 145 | 146 | public dynamic function onNewFolderClicked() : Void { } 147 | 148 | public dynamic function onParentFolderClicked() : Void { } 149 | 150 | public dynamic function onItemsListClicked() : Void { } 151 | 152 | public dynamic function onItemsIconClicked() : Void { } 153 | 154 | public dynamic function onDeleteClicked() : Void { } 155 | 156 | public dynamic function onNewFolderName() : Void { } 157 | 158 | 159 | /// 160 | // API 161 | // 162 | 163 | public var home (default, null) : Home; 164 | 165 | public var fileBrowser (default, null) : FileBrowser; 166 | 167 | public var authPopup (default, null) : AuthPopup; 168 | 169 | public var alertPopup (default, null) : AlertPopup; 170 | 171 | public var breadcrumb (default, null) : Breadcrumb; 172 | 173 | public var dropzone (default, null) : DropZone; 174 | 175 | public var export (default, null) : Export; 176 | 177 | public var location(get, null) : Null; 178 | 179 | public var closeBtn (default, null) : Button; 180 | 181 | public var newFolderBtn (default, null) : Button; 182 | 183 | public var parentFolderBtn (default, null) : Button; 184 | 185 | public var itemsListBtn (default, null) : Button; 186 | 187 | public var itemsIconBtn (default, null) : Button; 188 | 189 | public var deleteBtn (default, null) : Button; 190 | 191 | public var logoutBtn (default, null) : Button; 192 | 193 | public function setCurrentService(s : Service) : Void { 194 | 195 | rootElt.toggleClass(CLASS_PREFIX_SERVICE + Service.Dropbox, false); 196 | rootElt.toggleClass(CLASS_PREFIX_SERVICE + Service.Ftp, false); 197 | rootElt.toggleClass(CLASS_PREFIX_SERVICE + Service.Www, false); 198 | 199 | rootElt.toggleClass(CLASS_PREFIX_SERVICE + s, true); 200 | } 201 | 202 | public function setSortField(v : String) : Void { 203 | 204 | rootElt.toggleClass(CLASS_PREFIX_SORTEDBY + SortField.Name, false); 205 | rootElt.toggleClass(CLASS_PREFIX_SORTEDBY + SortField.Type, false); 206 | rootElt.toggleClass(CLASS_PREFIX_SORTEDBY + SortField.LastUpdate, false); 207 | 208 | rootElt.toggleClass(CLASS_PREFIX_SORTEDBY + v, true); 209 | } 210 | 211 | public function setSortOrder(v : String) : Void { 212 | 213 | rootElt.toggleClass(SortOrder.Asc, false); 214 | rootElt.toggleClass(SortOrder.Desc, false); 215 | 216 | rootElt.toggleClass(v, true); 217 | } 218 | 219 | public function setListDisplayMode() : Void { 220 | 221 | this.rootElt.toggleClass(CLASS_ITEMS_LIST, true); 222 | this.rootElt.toggleClass(CLASS_ITEMS_ICONS, false); 223 | } 224 | 225 | public function setIconDisplayMode() : Void { 226 | 227 | this.rootElt.toggleClass(CLASS_ITEMS_ICONS, true); 228 | this.rootElt.toggleClass(CLASS_ITEMS_LIST, false); 229 | } 230 | 231 | public function setDisplayed(v : Bool) : Void { 232 | 233 | iframe.style.display = v ? "block" : "none"; 234 | } 235 | 236 | public function setLoaderDisplayed(v : Bool) : Void { 237 | 238 | rootElt.toggleClass(CLASS_LOADING , v); 239 | } 240 | 241 | public function setLogoutButtonDisplayed(v : Bool) : Void { 242 | 243 | rootElt.toggleClass(CLASS_LOGGED_IN , v); 244 | } 245 | 246 | public function setHomeDisplayed(v : Bool) : Void { 247 | 248 | if (v) { 249 | 250 | cleanPreviousState(); 251 | } 252 | 253 | rootElt.toggleClass(CLASS_STARTING , v); 254 | } 255 | 256 | public function setFileBrowserDisplayed(v : Bool) : Void { 257 | 258 | if (v) { 259 | 260 | cleanPreviousState(); 261 | } 262 | 263 | rootElt.toggleClass(CLASS_BROWSING , v); 264 | } 265 | 266 | public function setExportOverwriteDisplayed(v : Bool) : Void { 267 | 268 | rootElt.toggleClass(CLASS_EXPORT_OVERWRITING , v); 269 | } 270 | 271 | public function setAuthPopupDisplayed(v : Bool) : Void { 272 | 273 | rootElt.toggleClass(CLASS_AUTHORIZING , v); 274 | } 275 | 276 | public function setAlertPopupDisplayed(v : Bool) : Void { 277 | 278 | rootElt.toggleClass(CLASS_ALERTING , v); 279 | } 280 | 281 | public function setNewFolderDisplayed(v : Bool) : Void { 282 | 283 | if (!v) { 284 | 285 | fileBrowser.newFolderName = ""; 286 | } 287 | rootElt.toggleClass(CLASS_MAKING_NEW_FOLDER , v); 288 | 289 | if (v) { 290 | 291 | fileBrowser.focusOnNewFolder(); 292 | } 293 | } 294 | 295 | public function setSelecting(v : Bool) : Void { 296 | 297 | rootElt.toggleClass(CLASS_SELECTING , v); 298 | } 299 | 300 | public function openAuthorizationWindow(url : String) : Void { 301 | 302 | // note: we might need to improve this method in order to have different possible sizes by cloud service 303 | var authPopup = Browser.window.open(url, "authPopup", "height=829,width=1035"); 304 | 305 | if (authPopup == null || authPopup.closed || authPopup.closed == null) { 306 | 307 | onAuthorizationWindowBlocked(); 308 | 309 | } else { 310 | 311 | if (authPopup.focus != null) { authPopup.focus(); } 312 | 313 | var timer = new haxe.Timer(500); 314 | 315 | timer.run = function() { 316 | //trace("authPopup= "+authPopup+" authPopup.closed= "+authPopup.closed); 317 | if (authPopup.closed) { 318 | 319 | timer.stop(); 320 | 321 | onServiceAuthorizationDone(); 322 | } 323 | } 324 | } 325 | } 326 | 327 | public function setModeState(v : ce.core.model.Mode) : Void { 328 | 329 | var cms : Null = currentModeState(); 330 | //trace("current UI mode is: "+cms); 331 | if (cms != null) { 332 | 333 | rootElt.toggleClass(cms , false); 334 | } 335 | if (v != null) { 336 | 337 | switch (v) { 338 | 339 | case SingleFileSelection(_): 340 | 341 | rootElt.toggleClass(CLASS_MODE_SINGLE_FILE_SELECTION , true); 342 | 343 | case SingleFileExport(_): 344 | 345 | rootElt.toggleClass(CLASS_MODE_SINGLE_FILE_EXPORT , true); 346 | 347 | case IsLoggedIn(_): 348 | 349 | rootElt.toggleClass(CLASS_MODE_IS_LOGGED_IN , true); 350 | 351 | case RequestAuthorize(_): 352 | 353 | rootElt.toggleClass(CLASS_MODE_REQUEST_AUTHORIZE , true); 354 | } 355 | } 356 | } 357 | 358 | /// 359 | // GETTER / SETTER 360 | // 361 | 362 | public function get_location() : Null { 363 | 364 | if (iframe == null) return null; 365 | 366 | return iframe.contentDocument.location.origin; 367 | } 368 | 369 | 370 | /// 371 | // INTERNALS 372 | // 373 | 374 | function listenOAuthCb(pStr : String) : Void { 375 | 376 | var o : OAuthResult = Str2OAuthResult.parse(pStr); 377 | 378 | onServiceAuthorizationDone(o); 379 | } 380 | 381 | function currentModeState() : Null { 382 | 383 | for (c in rootElt.className.split(" ")) { 384 | 385 | if( Lambda.has([CLASS_MODE_SINGLE_FILE_SELECTION, CLASS_MODE_SINGLE_FILE_EXPORT], c) ) { 386 | 387 | return c; 388 | } 389 | } 390 | return null; 391 | } 392 | 393 | function currentState() : Null { 394 | 395 | for (c in rootElt.className.split(" ")) { 396 | 397 | if( Lambda.has([CLASS_STARTING, CLASS_BROWSING], c) ) { 398 | 399 | return c; 400 | } 401 | } 402 | // if we're here, we have a problem (no current state ?!) 403 | return null; 404 | } 405 | 406 | private function cleanPreviousState() : Void { 407 | 408 | var cs : Null = currentState(); //trace("current state = "+cs); 409 | 410 | rootElt.toggleClass(CLASS_AUTHORIZING, false); 411 | 412 | if (cs != null) { 413 | 414 | rootElt.toggleClass(cs, false); 415 | } 416 | } 417 | 418 | private function initFrame() : Void { 419 | 420 | // init iframe 421 | iframe.style.display = "none"; 422 | iframe.style.position = "absolute"; 423 | iframe.style.top = iframe.style.left = "0"; 424 | iframe.style.width = iframe.style.height = "100%"; 425 | 426 | iframe.onload = function(?_){ initElts(); } 427 | 428 | iframe.src = config.path + "cloud-explorer.html"; 429 | 430 | //Application.oauthCb = function(p : String) { trace("oauthCb p="+p); onServiceAuthorizationDone(p); } // FIXME that's not ideal and prevent from multi instancing 431 | } 432 | 433 | private function initElts() : Void { 434 | 435 | // select elements 436 | rootElt = iframe.contentDocument.getElementById(ID_APPLICATION); 437 | 438 | logoutBtn = new Button(rootElt.querySelector(SELECTOR_LOGOUT_BTN)); 439 | logoutBtn.onClicked = onLogoutClicked; 440 | 441 | closeBtn = new Button(rootElt.querySelector(SELECTOR_CLOSE_BTN)); 442 | closeBtn.onClicked = onCloseClicked; 443 | 444 | breadcrumb = new Breadcrumb(rootElt.querySelector(SELECTOR_BREADCRUMB)); 445 | breadcrumb.onNavBtnClicked = function(srv : String, path : String) { onNavBtnClicked(srv, path); } 446 | 447 | export = new Export(rootElt.querySelector(SELECTOR_EXPORT)); 448 | export.onSaveBtnClicked = function() { onSaveExportClicked(); } 449 | export.onOverwriteBtnClicked = function() { onOverwriteExportClicked(); } 450 | export.onExportNameChanged = function() { onExportNameChanged(); } 451 | 452 | home = new Home(rootElt.querySelector(SELECTOR_HOME)); 453 | home.onServiceClicked = function(name : String) { onServiceClicked(name); } 454 | 455 | fileBrowser = new FileBrowser(rootElt.querySelector(SELECTOR_FILE_BROWSER)); 456 | fileBrowser.onServiceLogoutRequest = function(name : String) { onServiceLogoutRequest(name); } 457 | fileBrowser.onServiceLoginRequest = function(name : String) { onServiceLoginRequest(name); } 458 | fileBrowser.onServiceClicked = function(name : String) { onServiceClicked(name); } 459 | fileBrowser.onFileClicked = function(id : String) { onFileClicked(id); } 460 | fileBrowser.onFileSelectClicked = function(id : String) { onFileSelectClicked(id); } 461 | fileBrowser.onFileDeleteClicked = function(id : String) { onFileDeleteClicked(id); } 462 | fileBrowser.onFileCheckedStatusChanged = function(id : String) { onFileCheckedStatusChanged(id); } 463 | fileBrowser.onFileRenameRequested = function(id : String, value : String) { onFileRenameRequested(id, value); } 464 | fileBrowser.onNewFolderName = function() { onNewFolderName(); } 465 | fileBrowser.onSortBtnClicked = function(f:SortField) { onSortBtnClicked(f); } 466 | 467 | dropzone = new DropZone(rootElt.querySelector(SELECTOR_DROPZONE)); 468 | dropzone.onInputFilesChanged = function() { onInputFilesChanged(); } 469 | dropzone.onFilesDropped = function(files : js.html.FileList) { onFilesDropped(files); } 470 | 471 | authPopup = new AuthPopup(rootElt.querySelector(SELECTOR_AUTH_POPUP)); 472 | 473 | alertPopup = new AlertPopup(rootElt.querySelector(SELECTOR_ALERT_POPUP)); 474 | 475 | newFolderBtn = new Button(rootElt.querySelector(SELECTOR_NEW_FOLDER_BTN)); 476 | newFolderBtn.onClicked = onNewFolderClicked; 477 | 478 | parentFolderBtn = new Button(rootElt.querySelector(SELECTOR_PARENT_FOLDER_BTN)); 479 | parentFolderBtn.onClicked = onParentFolderClicked; 480 | 481 | itemsListBtn = new Button(rootElt.querySelector(SELECTOR_ITEMS_LIST_BTN)); 482 | itemsListBtn.onClicked = onItemsListClicked; 483 | 484 | itemsIconBtn = new Button(rootElt.querySelector(SELECTOR_ITEMS_ICON_BTN)); 485 | itemsIconBtn.onClicked = onItemsIconClicked; 486 | 487 | deleteBtn = new Button(rootElt.querySelector(SELECTOR_DELETE_BTN)); 488 | deleteBtn.onClicked = onDeleteClicked; 489 | 490 | rootElt.addEventListener("click", function(?_){ onClicked(); }); 491 | 492 | onViewReady(); 493 | } 494 | } -------------------------------------------------------------------------------- /src/ce/core/view/AuthPopup.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.view; 13 | 14 | import js.html.Element; 15 | 16 | using StringTools; 17 | 18 | class AuthPopup { 19 | 20 | static inline var SELECTOR_LINK : String = "a"; 21 | static inline var SELECTOR_TEXT : String = "span"; 22 | 23 | static inline var PLACE_HOLDER_SRV_NAME : String = "{srvName}"; 24 | 25 | public function new(elt : Element) { 26 | 27 | this.elt = elt; 28 | 29 | this.linkElt = elt.querySelector(SELECTOR_LINK); 30 | linkElt.addEventListener("click", function(?_){ onClicked(); }); 31 | 32 | this.textElt = elt.querySelector(SELECTOR_TEXT); 33 | this.txtTmpl = textElt.textContent; 34 | } 35 | 36 | var elt : Element; 37 | 38 | var linkElt : Element; 39 | var textElt : Element; 40 | 41 | var txtTmpl : String; 42 | 43 | /// 44 | // CALLBACKS 45 | // 46 | 47 | public dynamic function onClicked() : Void { } 48 | 49 | 50 | /// 51 | // API 52 | // 53 | 54 | public function setServerName(srvName : String) { 55 | 56 | textElt.textContent = txtTmpl.replace(PLACE_HOLDER_SRV_NAME, srvName); 57 | } 58 | } -------------------------------------------------------------------------------- /src/ce/core/view/Breadcrumb.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.view; 13 | 14 | import js.html.Element; 15 | 16 | using StringTools; 17 | 18 | class Breadcrumb { 19 | 20 | static inline var SELECTOR_PATH_ITEM_TMPL : String = "span.pathIt"; 21 | static inline var SELECTOR_PATH_SEP_TMPL : String = "span.sep"; 22 | 23 | public function new(elt : Element) { 24 | 25 | this.elt = elt; 26 | 27 | this.pathItemTmpl = elt.querySelector(SELECTOR_PATH_ITEM_TMPL); 28 | elt.removeChild(pathItemTmpl); 29 | this.pathSepTmpl = elt.querySelector(SELECTOR_PATH_SEP_TMPL); 30 | elt.removeChild(pathSepTmpl); 31 | } 32 | 33 | var elt : Element; 34 | 35 | var pathItemTmpl : Element; 36 | var pathSepTmpl : Element; 37 | 38 | 39 | /// 40 | // CALLBACKS 41 | // 42 | 43 | public dynamic function onNavBtnClicked(srv : String, path : String) : Void { } 44 | 45 | 46 | /// 47 | // API 48 | // 49 | 50 | public function setBreadcrumbPath(srv : String, path : String) : Void { 51 | 52 | while (elt.childNodes.length > 0) { 53 | 54 | elt.removeChild(elt.firstChild); 55 | } 56 | var srvIt : Element = cast pathItemTmpl.cloneNode(true); 57 | srvIt.addEventListener("click", function(?_){ onNavBtnClicked(srv, "/"); }); 58 | srvIt.textContent = srv; 59 | 60 | elt.appendChild(srvIt); 61 | 62 | var pathItems : Array = []; 63 | 64 | if (path.length > 0) { 65 | 66 | var parr : Array = path.split("/"); 67 | 68 | while (parr.length > 0) { 69 | 70 | var itPath : String = "/" + parr.join("/") + "/"; 71 | var pit : String = parr.pop(); 72 | 73 | if (pit.trim() != "") { 74 | 75 | var nit : Element = cast pathItemTmpl.cloneNode(true); 76 | nit.addEventListener("click", function(?_){ onNavBtnClicked(srv, itPath); }); 77 | nit.textContent = pit; 78 | 79 | pathItems.push(nit); 80 | } 81 | } 82 | } 83 | while (pathItems.length > 0) { 84 | 85 | elt.appendChild(pathSepTmpl.cloneNode(true)); 86 | elt.appendChild(pathItems.pop()); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/ce/core/view/Button.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.view; 13 | 14 | import js.html.Element; 15 | 16 | using ce.util.HtmlTools; 17 | 18 | class Button { 19 | 20 | static inline var ATTR_DISABLED : String = "disabled"; 21 | static inline var ATTR_VALUE_DISABLED : String = "disabled"; 22 | 23 | public function new(elt : Element) { 24 | 25 | this.elt = elt; 26 | 27 | this.elt.addEventListener( "click", function(?_){ onClicked(); } ); 28 | } 29 | 30 | var elt : Element; 31 | 32 | 33 | /// 34 | // API 35 | // 36 | 37 | public dynamic function onClicked() : Void { } 38 | 39 | public var enabled (get, set) : Bool; 40 | 41 | 42 | /// 43 | // GETTER / SETTER 44 | // 45 | 46 | private function get_enabled() : Bool { 47 | 48 | return !elt.hasAttribute(ATTR_DISABLED); 49 | } 50 | 51 | private function set_enabled(v : Bool) : Bool { 52 | 53 | if (v && elt.hasAttribute(ATTR_DISABLED)) { 54 | 55 | elt.removeAttribute(ATTR_DISABLED); 56 | } 57 | if (!v && !elt.hasAttribute(ATTR_DISABLED)) { 58 | 59 | elt.setAttribute(ATTR_DISABLED, ATTR_VALUE_DISABLED); 60 | } 61 | return v; 62 | } 63 | } -------------------------------------------------------------------------------- /src/ce/core/view/DropZone.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.view; 13 | 14 | import js.html.Element; 15 | import js.html.InputElement; 16 | import js.html.FileList; 17 | 18 | using ce.util.HtmlTools; 19 | 20 | class DropZone { 21 | 22 | static inline var SELECTOR_INPUT : String = "div input"; 23 | static inline var SELECTOR_BUTTON : String = "div button"; 24 | 25 | static inline var CLASS_DRAGGINGOVER : String = "draggingover"; 26 | 27 | public function new(elt : Element) { 28 | 29 | this.elt = elt; 30 | 31 | this.inputElt = cast elt.querySelector(SELECTOR_INPUT); 32 | inputElt.addEventListener("change", function(?_){ onInputFilesChanged(); }); 33 | 34 | this.btnElt = elt.querySelector(SELECTOR_BUTTON); 35 | btnElt.addEventListener("click", function(?_){ onBtnClicked(); }); 36 | 37 | this.elt.addEventListener('dragover', function(e) { 38 | 39 | e.preventDefault(); 40 | 41 | e.dataTransfer.dropEffect = 'copy'; 42 | 43 | return false; 44 | }); 45 | 46 | this.elt.addEventListener('dragenter', function(e) { 47 | 48 | this.elt.toggleClass(CLASS_DRAGGINGOVER, true); 49 | }); 50 | 51 | this.elt.addEventListener('dragleave', function(e) { 52 | 53 | this.elt.toggleClass(CLASS_DRAGGINGOVER, false); 54 | }); 55 | 56 | this.elt.addEventListener('drop', function(e) { 57 | 58 | e.preventDefault(); 59 | e.stopPropagation(); 60 | 61 | this.elt.toggleClass(CLASS_DRAGGINGOVER, false); // useful ? 62 | 63 | var fileList : FileList = e.dataTransfer.files; 64 | 65 | if (fileList.length > 0) { 66 | 67 | onFilesDropped(fileList); 68 | } 69 | }); 70 | } 71 | 72 | var elt : Element; 73 | var btnElt : Element; 74 | 75 | public var inputElt (default, null) : InputElement; 76 | 77 | 78 | /// 79 | // CALLBACKS 80 | // 81 | 82 | public dynamic function onInputFilesChanged() : Void { } 83 | 84 | public dynamic function onFilesDropped(files : FileList) : Void { } 85 | 86 | 87 | /// 88 | // INTERNALS 89 | // 90 | 91 | private function onBtnClicked() : Void { 92 | 93 | inputElt.click(); 94 | } 95 | } -------------------------------------------------------------------------------- /src/ce/core/view/Export.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.view; 13 | 14 | import js.html.Element; 15 | import js.html.InputElement; 16 | 17 | using StringTools; 18 | 19 | class Export { 20 | 21 | static inline var SELECTOR_INPUT : String = "input"; 22 | static inline var SELECTOR_PATH : String = "span.path"; 23 | static inline var SELECTOR_EXT : String = "span.ext"; 24 | static inline var SELECTOR_SAVE_BUTTON : String = ".saveBtn"; 25 | static inline var SELECTOR_OVERWRITE_BUTTON : String = ".overwriteBtn"; 26 | 27 | public function new(elt : Element) { 28 | 29 | this.elt = elt; 30 | 31 | this.inputElt = cast elt.querySelector(SELECTOR_INPUT); 32 | inputElt.addEventListener("input", function(?_) { onExportNameChanged(); }); 33 | 34 | this.pathElt = elt.querySelector(SELECTOR_PATH); 35 | 36 | this.extElt = elt.querySelector(SELECTOR_EXT); 37 | 38 | this.saveBtnElt = elt.querySelector(SELECTOR_SAVE_BUTTON); 39 | saveBtnElt.addEventListener("click", function(?_){ onSaveBtnClicked(); }); 40 | 41 | this.overwriteBtnElt = elt.querySelector(SELECTOR_OVERWRITE_BUTTON); 42 | overwriteBtnElt.addEventListener("click", function(?_){ onOverwriteBtnClicked(); }); 43 | } 44 | 45 | var elt : Element; 46 | 47 | var inputElt : InputElement; 48 | var extElt : Element; 49 | var pathElt : Element; 50 | var saveBtnElt : Element; 51 | var overwriteBtnElt : Element; 52 | 53 | public var exportName (get, set) : Null; 54 | 55 | public var ext (null, set) : Null; 56 | 57 | public var path (null, set) : Null; 58 | 59 | 60 | /// 61 | // CALLBACKS 62 | // 63 | 64 | public dynamic function onNavBtnClicked(srv : String, path : String) : Void { } 65 | 66 | public dynamic function onSaveBtnClicked() : Void { } 67 | 68 | public dynamic function onOverwriteBtnClicked() : Void { } 69 | 70 | public dynamic function onExportNameChanged() : Void { } 71 | 72 | 73 | /// 74 | // GETTERS / SETTERS 75 | // 76 | 77 | public function get_exportName() : Null { 78 | 79 | return inputElt.value; 80 | } 81 | 82 | public function set_exportName(v : Null) : Null { 83 | 84 | inputElt.value = v; 85 | 86 | return v; 87 | } 88 | 89 | public function set_ext(v : Null) : Null { 90 | 91 | extElt.textContent = v; 92 | 93 | return v; 94 | } 95 | 96 | public function set_path(v : Null) : Null { 97 | 98 | pathElt.textContent = v; 99 | 100 | return v; 101 | } 102 | } -------------------------------------------------------------------------------- /src/ce/core/view/FileBrowser.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.view; 13 | 14 | import js.html.Element; 15 | import js.html.InputElement; 16 | import js.html.KeyboardEvent; 17 | import js.html.NodeList; 18 | 19 | import haxe.ds.StringMap; 20 | 21 | import ce.core.model.SortField; 22 | import ce.core.model.SortOrder; 23 | 24 | using ce.util.HtmlTools; 25 | using ce.util.FileTools; 26 | using StringTools; 27 | 28 | class FileBrowser { 29 | 30 | static inline var SELECTOR_SRV_LIST : String = ".services ul"; 31 | static inline var SELECTOR_FILES_LIST : String = ".files ul"; 32 | 33 | static inline var SELECTOR_SRV_ITEM_TMPL : String = "li"; 34 | static inline var SELECTOR_NEW_FOLDER_ITEM : String = ".folder.new"; 35 | static inline var SELECTOR_FOLDER_ITEM_TMPL : String = ".folder:nth-last-child(-n+1)"; 36 | static inline var SELECTOR_FILE_ITEM_TMPL : String = ".file"; 37 | static inline var SELECTOR_CONTEXT_MENU_ITEMS : String = "ul.contextMenu li"; 38 | 39 | static inline var SELECTOR_NAME_BTN : String = ".titles .fileName"; 40 | static inline var SELECTOR_TYPE_BTN : String = ".titles .fileType"; 41 | static inline var SELECTOR_DATE_BTN : String = ".titles .lastUpdate"; 42 | 43 | static inline var CLASS_SELECT_FOLDER : String = "selectFolders"; 44 | static inline var CLASS_SRV_CONNECTED : String = "connected"; 45 | 46 | public function new(elt : Element) { 47 | 48 | this.elt = elt; 49 | 50 | this.srvItemElts = new StringMap(); 51 | 52 | this.srvListElt = elt.querySelector(SELECTOR_SRV_LIST); 53 | this.srvItemTmpl = srvListElt.querySelector(SELECTOR_SRV_ITEM_TMPL); 54 | srvListElt.removeChild(srvItemTmpl); 55 | 56 | this.fileListElt = elt.querySelector(SELECTOR_FILES_LIST); 57 | 58 | this.fileItemTmpl = fileListElt.querySelector(SELECTOR_FILE_ITEM_TMPL); 59 | fileListElt.removeChild(fileItemTmpl); 60 | 61 | this.newFolderItem = fileListElt.querySelector(SELECTOR_NEW_FOLDER_ITEM); 62 | this.newFolderInput = cast newFolderItem.querySelector("input"); 63 | newFolderInput.addEventListener("keydown", function(e : KeyboardEvent){ 64 | 65 | untyped { 66 | if (e.keyIdentifier != null && e.keyIdentifier.toLowerCase() == "enter" || 67 | e.key != null && e.key.toLowerCase() == "enter") { 68 | 69 | onNewFolderName(); 70 | } 71 | } 72 | }); 73 | newFolderInput.addEventListener("focusout", function(?_){ onNewFolderName(); }); 74 | 75 | this.folderItemTmpl = fileListElt.querySelector(SELECTOR_FOLDER_ITEM_TMPL); 76 | fileListElt.removeChild(folderItemTmpl); 77 | 78 | var nameBtn = elt.querySelector(SELECTOR_NAME_BTN); 79 | nameBtn.addEventListener("click", function(?_){ onSortBtnClicked(Name); }); 80 | var typeBtn = elt.querySelector(SELECTOR_TYPE_BTN); 81 | typeBtn.addEventListener("click", function(?_){ onSortBtnClicked(Type); }); 82 | var dateBtn = elt.querySelector(SELECTOR_DATE_BTN); 83 | dateBtn.addEventListener("click", function(?_){ onSortBtnClicked(LastUpdate); }); 84 | 85 | this.fileListItems = []; 86 | 87 | this.filters = null; 88 | } 89 | 90 | var elt : Element; 91 | 92 | // lists 93 | var srvListElt : Element; 94 | var fileListElt : Element; 95 | 96 | // templates 97 | var srvItemTmpl : Element; 98 | var fileItemTmpl : Element; 99 | var folderItemTmpl : Element; 100 | 101 | // items 102 | var newFolderItem : Element; 103 | var newFolderInput : InputElement; 104 | 105 | var srvItemElts : StringMap; 106 | 107 | public var filters (default, set) : Null>; 108 | 109 | public var fileListItems (default, null) : Array; 110 | 111 | public var newFolderName (get, set) : Null; 112 | 113 | 114 | /// 115 | // CALLBACKS 116 | // 117 | 118 | public dynamic function onServiceLoginRequest(name : String) : Void { } 119 | 120 | public dynamic function onServiceLogoutRequest(name : String) : Void { } 121 | 122 | public dynamic function onServiceClicked(name : String) : Void { } 123 | 124 | public dynamic function onFileSelected(id : String) : Void { } 125 | 126 | public dynamic function onFileClicked(id : String) : Void { } 127 | 128 | public dynamic function onFileSelectClicked(id : String) : Void { } 129 | 130 | public dynamic function onFileDeleteClicked(id : String) : Void { } 131 | 132 | public dynamic function onFileCheckedStatusChanged(id : String) : Void { } 133 | 134 | public dynamic function onFileRenameRequested(id : String, value : String) : Void { } 135 | 136 | public dynamic function onNewFolderName() : Void { } 137 | 138 | public dynamic function onSortBtnClicked(field : SortField) : Void { } 139 | 140 | 141 | /// 142 | // API 143 | // 144 | /* 145 | public function setEmptyMsgDisplay(v : Bool) : Void { 146 | 147 | 148 | } 149 | */ 150 | 151 | public function resetList() : Void { 152 | 153 | while(srvListElt.childNodes.length > 0) { 154 | 155 | srvListElt.removeChild(srvListElt.childNodes.item(0)); 156 | } 157 | } 158 | 159 | public function removeService(name : String) : Void { 160 | 161 | srvListElt.removeChild(srvItemElts.get(name)); 162 | } 163 | 164 | public function addService(name : String, displayName : String, ? connected : Bool) : Void { 165 | 166 | var newItem : Element = cast srvItemTmpl.cloneNode(true); 167 | newItem.className = name; 168 | //newItem.setAttribute("title", newItem.getAttribute("title").replace("{srvName}", displayName)); 169 | 170 | newItem.addEventListener("click", function(?_){ onServiceClicked(name); }); 171 | 172 | var lis : NodeList = newItem.querySelectorAll(SELECTOR_CONTEXT_MENU_ITEMS); 173 | 174 | for (i in 0...lis.length) { 175 | 176 | var li : Element = cast lis[i]; 177 | 178 | li.textContent = li.textContent.replace("{srvName}", displayName); 179 | li.addEventListener("click", function(e:js.html.MouseEvent){ 180 | 181 | e.stopPropagation(); 182 | 183 | if (li.classList.contains("login")) { 184 | 185 | onServiceLoginRequest(name); 186 | 187 | } else if (li.classList.contains("logout")) { 188 | 189 | onServiceLogoutRequest(name); 190 | } 191 | 192 | }); 193 | } 194 | 195 | srvListElt.appendChild(newItem); 196 | 197 | srvItemElts.set(name, newItem); 198 | 199 | if (connected) setSrvConnected(name , connected); 200 | } 201 | 202 | public function setSrvConnected(name : String, connected : Bool) : Void { 203 | 204 | srvItemElts.get(name).toggleClass(CLASS_SRV_CONNECTED , connected); 205 | } 206 | 207 | public function resetFileList() : Void { 208 | 209 | while(fileListItems.length > 0) { 210 | 211 | fileListElt.removeChild(fileListItems.pop().elt); 212 | } 213 | } 214 | 215 | public function addFolder(id : String, name : String, ? lastUpdate : Null, ? selectable : Bool = true) : Void { 216 | 217 | var newItem : Element = cast folderItemTmpl.cloneNode(true); 218 | 219 | var fli : FileListItem = new FileListItem(newItem); 220 | fli.name = name; 221 | fli.lastUpdate = lastUpdate; 222 | fli.onClicked = function() { onFileClicked(id); } 223 | fli.onSelectClicked = function() { onFileSelectClicked(id); } 224 | fli.onDeleteClicked = function() { onFileDeleteClicked(id); } 225 | fli.onRenameRequested = function() { onFileRenameRequested(id, fli.renameValue); } 226 | fli.onCheckedStatusChanged = function() { onFileCheckedStatusChanged(id); } 227 | fli.selectable = selectable; 228 | 229 | fileListItems.push(fli); 230 | 231 | fileListElt.insertBefore(newItem, newFolderItem); 232 | } 233 | 234 | public function addFile(id : String, name : String, type : Null, lastUpdate : Date) : Void { 235 | 236 | var newItem : Element = cast fileItemTmpl.cloneNode(true); 237 | 238 | var fli : FileListItem = new FileListItem(newItem); 239 | fli.name = name; 240 | if (type != null) { 241 | 242 | fli.type = type; 243 | } 244 | fli.lastUpdate = lastUpdate; 245 | fli.onClicked = function() { onFileClicked(id); } 246 | fli.onDeleteClicked = function() { onFileDeleteClicked(id); } 247 | fli.onRenameRequested = function() { onFileRenameRequested(id, fli.renameValue); } 248 | fli.onCheckedStatusChanged = function() { onFileCheckedStatusChanged(id); } 249 | 250 | fileListItems.push(fli); 251 | 252 | applyFilters(fli); 253 | 254 | fileListElt.insertBefore(newItem, newFolderItem); 255 | } 256 | /* 257 | return function(date, uppercase) { 258 | return new Date(date).toLocaleDateString() 259 | } 260 | */ 261 | 262 | public function focusOnNewFolder() : Void { 263 | 264 | newFolderInput.focus(); 265 | } 266 | 267 | public function sort(byField : SortField, order : SortOrder) : Void { 268 | 269 | fileListItems.sort(function(a:FileListItem,b:FileListItem){ 270 | 271 | switch (order) { 272 | 273 | case Asc: 274 | 275 | return Reflect.getProperty(a, byField) > Reflect.getProperty(b, byField) ? 1 : -1; 276 | 277 | case Desc: 278 | 279 | return Reflect.getProperty(a, byField) < Reflect.getProperty(b, byField) ? 1 : -1; 280 | } 281 | }); 282 | 283 | for (fit in fileListItems) { 284 | 285 | fileListElt.insertBefore(fit.elt, newFolderItem); 286 | } 287 | } 288 | 289 | 290 | /// 291 | // GETTERS / SETTERS 292 | // 293 | 294 | public function get_newFolderName() : Null { 295 | 296 | return newFolderInput.value; 297 | } 298 | 299 | public function set_newFolderName(v : Null) : Null { 300 | 301 | newFolderInput.value = v; 302 | 303 | return v; 304 | } 305 | 306 | 307 | public function set_filters(v : Null>) : Null> { 308 | 309 | if (filters == v) { 310 | 311 | return v; 312 | } 313 | filters = v; 314 | 315 | if (filters != null && filters.indexOf(ce.util.FileTools.DIRECTORY_MIME_TYPE) > -1) { 316 | 317 | elt.toggleClass(CLASS_SELECT_FOLDER, true); 318 | 319 | } else { 320 | 321 | elt.toggleClass(CLASS_SELECT_FOLDER, false); 322 | } 323 | for (f in fileListItems) { 324 | 325 | applyFilters(f); 326 | } 327 | return filters; 328 | } 329 | 330 | /// 331 | // INTERNALS 332 | // 333 | 334 | private function applyFilters(f : FileListItem) : Void { 335 | 336 | if (f.type != FileTools.DIRECTORY_MIME_TYPE) { 337 | 338 | if (filters == null || filters.indexOf(f.type) != -1) { 339 | 340 | f.filteredOut = false; 341 | 342 | } else { 343 | 344 | f.filteredOut = true; 345 | } 346 | } 347 | } 348 | } 349 | 350 | class FileListItem { 351 | 352 | static inline var CLASS_RENAMING : String = "renaming"; 353 | static inline var CLASS_NOT_SELECTABLE : String = "nosel"; 354 | static inline var CLASS_FILTERED_OUT : String = "filteredOut"; 355 | static inline var CLASS_FOLDER : String = "folder"; // not ideal we have this in 3 constants FIXME 356 | static inline var CLASS_IMAGE : String = "image"; 357 | static inline var CLASS_SOUND : String = "sound"; 358 | static inline var CLASS_VIDEO : String = "video"; 359 | 360 | public function new(elt : Element) { 361 | 362 | this.elt = elt; 363 | 364 | this.checkBoxElt = cast elt.querySelector("input[type='checkbox']"); 365 | checkBoxElt.addEventListener("change", function(?_){ onCheckedStatusChanged(); }); 366 | 367 | this.nameElt = elt.querySelector("span.fileName"); 368 | nameElt.addEventListener( "click", function(?_){ 369 | 370 | if (filteredOut) return; 371 | 372 | onClicked(); 373 | } ); 374 | 375 | this.renameInput = cast elt.querySelector("input[type='text']"); 376 | renameInput.addEventListener("keydown", function(e : KeyboardEvent){ 377 | untyped { 378 | if (e.keyIdentifier != null && e.keyIdentifier.toLowerCase() == "enter" || 379 | e.key != null && e.key.toLowerCase() == "enter") { 380 | 381 | elt.toggleClass(CLASS_RENAMING, false); 382 | onRenameRequested(); 383 | } 384 | } 385 | }); 386 | renameInput.addEventListener("focusout", function(?_){ 387 | 388 | elt.toggleClass(CLASS_RENAMING, false); 389 | onRenameRequested(); 390 | }); 391 | 392 | this.typeElt = elt.querySelector("span.fileType"); 393 | this.dateElt = elt.querySelector("span.lastUpdate"); 394 | 395 | this.renameBtn = elt.querySelector("button.rename"); 396 | this.renameBtn.addEventListener( "click", function(?_){ 397 | 398 | elt.toggleClass(CLASS_RENAMING, true); 399 | 400 | renameInput.value = nameElt.textContent; 401 | renameInput.focus(); 402 | }); 403 | 404 | this.deleteBtn = elt.querySelector("button.delete"); 405 | this.deleteBtn.addEventListener( "click", function(?_){ onDeleteClicked(); } ); 406 | 407 | this.selectBtn = elt.querySelector("button.select"); 408 | if (selectBtn != null) { 409 | selectBtn.addEventListener( "click", function(?_){ 410 | 411 | if (filteredOut) return; 412 | 413 | onSelectClicked(); 414 | } ); 415 | } 416 | } 417 | 418 | var checkBoxElt : InputElement; 419 | var nameElt : Element; 420 | var renameInput : InputElement; 421 | var typeElt : Element; 422 | var dateElt : Element; 423 | 424 | var renameBtn : Element; 425 | var deleteBtn : Element; 426 | var selectBtn : Null; 427 | 428 | /// 429 | // PROPERTIES 430 | // 431 | 432 | public var elt (default, null) : Element; 433 | 434 | public var isChecked (get, null) : Bool; 435 | 436 | public var name (get, set) : String; 437 | 438 | public var renameValue (get, set) : String; 439 | 440 | public var type (get, set) : String; 441 | 442 | @:isVar public var lastUpdate (get, set) : Date; 443 | 444 | public var selectable (get, set) : Bool; 445 | 446 | public var filteredOut (get, set) : Bool; 447 | 448 | /// 449 | // GETTERS / SETTERS 450 | // 451 | 452 | public function get_isChecked() : Bool { 453 | 454 | return checkBoxElt.checked; 455 | } 456 | 457 | public function get_renameValue() : String { 458 | 459 | return renameInput.value; 460 | } 461 | 462 | public function set_renameValue(v : String) : String { 463 | 464 | renameInput.value = v; 465 | 466 | return v; 467 | } 468 | 469 | public function get_name() : String { 470 | 471 | return nameElt.textContent; 472 | } 473 | 474 | public function set_name(v : String) : String { 475 | 476 | nameElt.textContent = v; 477 | 478 | return v; 479 | } 480 | 481 | public function get_type() : String { 482 | 483 | if (elt.hasClass(CLASS_FOLDER)) { 484 | 485 | return FileTools.DIRECTORY_MIME_TYPE; 486 | } 487 | return typeElt.textContent; 488 | } 489 | 490 | public function set_type(v : String) : String { 491 | 492 | typeElt.textContent = v; 493 | 494 | elt.toggleClass(CLASS_IMAGE, v.indexOf("image/") == 0); 495 | elt.toggleClass(CLASS_SOUND, v.indexOf("audio/") == 0); 496 | elt.toggleClass(CLASS_VIDEO, v.indexOf("video/") == 0); 497 | 498 | return v; 499 | } 500 | 501 | public function get_lastUpdate() : Null { 502 | 503 | return lastUpdate; 504 | } 505 | 506 | public function set_lastUpdate(v : Null) : Null { 507 | 508 | lastUpdate = v; 509 | 510 | if (v != null) { 511 | 512 | dateElt.textContent = DateTools.format(lastUpdate, "%d/%m/%Y"); // FIXME "%x %X" not implemented yet in haxe/js 513 | 514 | } else { 515 | 516 | dateElt.innerHTML = " "; 517 | } 518 | return v; 519 | } 520 | 521 | public function get_selectable() : Bool { 522 | 523 | return !elt.hasClass(CLASS_NOT_SELECTABLE); 524 | } 525 | 526 | public function set_selectable(v : Bool) : Bool { 527 | 528 | elt.toggleClass(CLASS_NOT_SELECTABLE, !v); 529 | 530 | return v; 531 | } 532 | 533 | public function get_filteredOut() : Bool { 534 | 535 | return elt.hasClass(CLASS_FILTERED_OUT); 536 | } 537 | 538 | public function set_filteredOut(v : Bool) : Bool { 539 | 540 | elt.toggleClass(CLASS_FILTERED_OUT, v); 541 | 542 | return v; 543 | } 544 | 545 | /// 546 | // CALLBACKS 547 | // 548 | 549 | public dynamic function onCheckedStatusChanged() : Void { } 550 | 551 | public dynamic function onDeleteClicked() : Void { } 552 | 553 | public dynamic function onRenameRequested() : Void { } 554 | 555 | public dynamic function onSelectClicked() : Void { } 556 | 557 | public dynamic function onClicked() : Void { } 558 | } -------------------------------------------------------------------------------- /src/ce/core/view/Home.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.core.view; 13 | 14 | import js.html.Element; 15 | 16 | class Home { 17 | 18 | static inline var SELECTOR_SRV_LIST : String = "ul"; 19 | static inline var SELECTOR_SRV_ITEM_TMPL : String = "li"; 20 | 21 | public function new(elt : Element) { 22 | 23 | this.elt = elt; 24 | 25 | this.listElt = elt.querySelector(SELECTOR_SRV_LIST); 26 | 27 | this.srvItemTmpl = elt.querySelector(SELECTOR_SRV_ITEM_TMPL); 28 | listElt.removeChild(srvItemTmpl); 29 | } 30 | 31 | var elt : Element; 32 | 33 | var listElt : Element; 34 | 35 | var srvItemTmpl : Element; 36 | 37 | 38 | /// 39 | // CALLBACK 40 | // 41 | 42 | public dynamic function onServiceClicked(name : String) : Void { } 43 | 44 | 45 | /// 46 | // API 47 | // 48 | 49 | public function resetList() : Void { 50 | 51 | while(listElt.childNodes.length > 0) { 52 | 53 | listElt.removeChild(listElt.firstChild); 54 | } 55 | } 56 | 57 | public function addService(name : String, displayName : String, description : String) : Void { 58 | 59 | var newSrvIt : Element = cast srvItemTmpl.cloneNode(true); 60 | 61 | newSrvIt.textContent = displayName; 62 | newSrvIt.className = name; 63 | 64 | // TODO description as tooltip ? 65 | 66 | newSrvIt.addEventListener( "click", function(?_){ onServiceClicked(name); } ); 67 | 68 | listElt.appendChild(newSrvIt); 69 | } 70 | } -------------------------------------------------------------------------------- /src/ce/util/HtmlTools.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.util; 13 | 14 | import js.html.Element; 15 | import js.html.Event; 16 | 17 | #if js 18 | import js.html.Node; 19 | #else 20 | import cocktail.core.dom.Node; 21 | #end 22 | 23 | using Lambda; 24 | 25 | /** 26 | * This class provides "jquery-like" HTML Element manipulation helper methods. 27 | * Designed to be imported with 'using' and to be chainable. 28 | */ 29 | class HtmlTools { 30 | 31 | /** 32 | * Returns an HTML element's classes as an array of lower-case strings. 33 | * Passing an array of string as the second parameter will replace the element's classes. 34 | */ 35 | public static function classes( el:Element , ?cl : Array ) : Array { 36 | 37 | if( cl != null ){ 38 | //trace('setting classes ${cl.join(",")}'); 39 | el.className = cl.join(" "); 40 | } 41 | return el.className.split(" ") 42 | .filter( function(s) return ( s != '' ) ) 43 | .map( function(s) return s.toLowerCase() ); 44 | } 45 | 46 | /** 47 | * Toggles a class on an HTML element. 48 | * Returns the element 49 | */ 50 | public static function toggleClass( el:Element, cl:String, flag : Bool ) : Element { 51 | if( flag ){ 52 | addClass( el , cl ); 53 | }else{ 54 | removeClass( el , cl ); 55 | } 56 | return el; 57 | } 58 | 59 | /** 60 | * Checks if the given class is present for the given HTML element. 61 | * The method ignores case. 62 | */ 63 | public inline static function hasClass( el:Element, cl:String ) : Bool { 64 | return classes(el).has( cl.toLowerCase() ); 65 | } 66 | 67 | /** 68 | * Adds a class to an HTML element 69 | * The 'cl' parameter can contain several space-separated classes 70 | * Returns the element 71 | */ 72 | public static function addClass( el:Element , cl :String ){ 73 | 74 | var cls = classes(el); 75 | var changed = false; 76 | for( c in cl.split(" ") ){ 77 | if( !cls.has( c.toLowerCase() ) ){ 78 | cls.push( c.toLowerCase() ); 79 | changed = true; 80 | } 81 | } 82 | if( changed ) 83 | classes(el,cls); 84 | return el; 85 | } 86 | 87 | /** 88 | * Removes a class from an HTML element 89 | * The 'cl' parameter can contain several space-separated classes 90 | * Returns the element 91 | */ 92 | public static function removeClass( el:Element , cl :String){ 93 | var cls = classes(el); 94 | var changed = false; 95 | for( c in cl.split(" ") ){ 96 | if( cls.remove( c.toLowerCase() ) ) 97 | changed = true; 98 | } 99 | if( changed ) 100 | classes(el,cls); 101 | return el; 102 | } 103 | 104 | /** 105 | * Calculates an HTML element's "absolute" offset by recursively adding his ancestors' offsets 106 | * Returned x corresponds to offsetLeft, y corresponds to offsetRight 107 | */ 108 | public static function offset( el:Element ) : { x : Int, y : Int }{ 109 | var pos = { x : el.offsetLeft , y : el.offsetTop }; 110 | var parent = parentElement(el); 111 | while( parent != null ){ 112 | pos.x += parent.offsetLeft; 113 | pos.y += parent.offsetTop; 114 | parent = parentElement(parent); 115 | } 116 | return pos; 117 | } 118 | 119 | /** 120 | * Returns an HTML Element's parent HTML Element, 121 | * if the parent is not an Element (ie a Document), null is returned 122 | */ 123 | public static function parentElement( el : Element ) : Element { 124 | var parent = el.parentNode; 125 | if( parent != null && parent.nodeType == #if js Node.ELEMENT_NODE #else cocktail.core.dom.DOMConstants.ELEMENT_NODE #end ){ 126 | return cast(parent); 127 | } 128 | return null; 129 | } 130 | 131 | /** 132 | * Searches for a vendor-prefixed method called 'field' on an HTML Element, 133 | * and calls it with arguments 'args'. 134 | * For non-JS platforms (Cocktail), the 'field' method is called directly 135 | * TODO: Optimize by making it a macro 136 | */ 137 | public static function vendorPrefixCall( el : Node , field : String , ?args : Array = null ) : Dynamic { 138 | 139 | if( args == null ) args = []; 140 | 141 | #if !js 142 | return Reflect.callMethod( el , Reflect.field( el, field ) , args ); 143 | #else 144 | 145 | for( prefixed in vendorPrefix(field) ){ 146 | var v = Reflect.field( el , prefixed ); 147 | if( untyped __js__("typeof v") != "undefined" ){ 148 | return Reflect.callMethod( el , v , args ); 149 | } 150 | } 151 | 152 | return null; 153 | 154 | #end 155 | } 156 | 157 | /** 158 | * Return an array of all possible vendor-prefixed versions of 'field': 159 | * [not prefixed], webkit, moz, ms, o 160 | * Not supposed to work for CSS properties, only Javascript events, methods and properties 161 | */ 162 | public static function vendorPrefix( field : String , ?capitalize : Bool = true ) : Array { 163 | var prefixes = ["","webkit","moz","ms","o"]; 164 | var fields = [field]; 165 | 166 | // exception for webkitIsFullScreen 167 | if( field == "fullScreen" ){ 168 | fields.push("isFullScreen"); 169 | } 170 | 171 | var prefixed = []; 172 | for( p in prefixes ){ 173 | for( f in fields ){ 174 | prefixed.push( p + ( capitalize ? ( f.substr(0,1).toUpperCase() + f.substr(1) ) : f ) ); 175 | } 176 | } 177 | return prefixed; 178 | } 179 | 180 | /** 181 | * Searches for a vendor-prefixed property called 'field' on an HTML Element, 182 | * and returns its value. 183 | * For non-JS platforms (Cocktail), the 'field' method is called directly 184 | * TODO: Optimize by making it a macro 185 | */ 186 | public static function vendorPrefixProperty( el : Node , field : String ) : Dynamic { 187 | #if !js 188 | // if we're in Cocktail = no prefix 189 | return Reflect.field(el , field); 190 | #else 191 | for( prefixed in vendorPrefix( field ) ){ 192 | var v = Reflect.field( el , prefixed ); 193 | if( untyped __js__("typeof v") != "undefined" ){ 194 | return v; 195 | } 196 | } 197 | return null; 198 | #end 199 | } 200 | 201 | /** 202 | * Adds event listener to an HTML Node 203 | * 'event' may be a space separated list of event types 204 | */ 205 | public inline static function addEvent( el : Node , event : String , callback : Event -> Void ){ 206 | addEvents( el, event.split(" "), callback ); 207 | } 208 | 209 | /** 210 | * Adds several event listeners to an HTML Node 211 | */ 212 | public inline static function addEvents( el : Node , events : Array , callback : Event -> Void ){ 213 | for( e in events ){ 214 | el.addEventListener( e , callback ); 215 | } 216 | } 217 | 218 | } -------------------------------------------------------------------------------- /src/ce/util/OptionTools.hx: -------------------------------------------------------------------------------- 1 | /** 2 | * Cloud Explorer, lightweight frontend component for file browsing with cloud storage services. 3 | * @see https://github.com/silexlabs/cloud-explorer 4 | * 5 | * Cloud Explorer works as a frontend interface for the unifile node.js module: 6 | * @see https://github.com/silexlabs/unifile 7 | * 8 | * @author Thomas Fétiveau, http://www.tokom.fr & Alexandre Hoyau, http://lexoyo.me 9 | * Copyrights SilexLabs 2013 - http://www.silexlabs.org/ - 10 | * License MIT 11 | */ 12 | package ce.util; 13 | 14 | import ce.core.model.api.ExportOptions; 15 | import ce.core.model.api.PickOptions; 16 | import ce.core.model.api.ReadOptions; 17 | import ce.core.model.api.WriteOptions; 18 | 19 | class OptionTools { 20 | 21 | static public function normalizePickOptions(o : Null) : Null { 22 | 23 | if (o == null) return o; 24 | 25 | if (o.mimetype != null) o.mimetype = o.mimetype.toLowerCase(); 26 | if (o.extension != null) o.extension = o.extension.toLowerCase(); 27 | 28 | if (o.mimetypes != null) { 29 | for (mi in 0...o.mimetypes.length) { 30 | o.mimetypes[mi] = o.mimetypes[mi].toLowerCase(); 31 | } 32 | } 33 | if (o.extensions != null) { 34 | for (ei in 0...o.extensions.length) { 35 | o.extensions[ei] = o.extensions[ei].toLowerCase(); 36 | } 37 | } 38 | 39 | return o; 40 | } 41 | 42 | static public function normalizeExportOptions(o : Null) : Null { 43 | 44 | if (o == null) return o; 45 | 46 | if (o.mimetype != null) o.mimetype = o.mimetype.toLowerCase(); 47 | if (o.extension != null) o.extension = o.extension.toLowerCase(); 48 | 49 | return o; 50 | } 51 | 52 | static public function normalizeReadOptions(o : Null) : Null { 53 | 54 | // nothing 55 | 56 | return o; 57 | } 58 | 59 | static public function normalizeWriteOptions(o : Null) : Null { 60 | 61 | // nothing 62 | 63 | return o; 64 | } 65 | } --------------------------------------------------------------------------------