├── .editorconfig ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── dev ├── index.html └── index.js ├── dist ├── MaterialIcons-Regular.eot ├── MaterialIcons-Regular.ttf ├── MaterialIcons-Regular.woff ├── MaterialIcons-Regular.woff2 ├── jquery.min.js ├── rg-uploader.css ├── rg-uploader.js └── rg-uploader.plugins.js ├── docs ├── assets │ ├── data.json │ ├── demo.css │ ├── ico-symbol.png │ ├── img-demo-1.jpg │ ├── img-demo-2.jpg │ ├── img-demo-3.jpg │ ├── prism.css │ └── prism.js ├── dist │ ├── MaterialIcons-Regular.eot │ ├── MaterialIcons-Regular.ttf │ ├── MaterialIcons-Regular.woff │ ├── MaterialIcons-Regular.woff2 │ ├── jquery.min.js │ ├── rg-uploader.css │ ├── rg-uploader.js │ └── rg-uploader.plugins.js ├── editor.html ├── index.html ├── plugins.html └── style.html ├── package.json ├── src ├── component │ ├── Plugin.js │ ├── Queue.js │ ├── Uploader.js │ ├── index.js │ └── lib │ │ ├── defaultOptions.js │ │ ├── fileUpload.js │ │ ├── index.js │ │ ├── keyboardEvent.js │ │ ├── language.js │ │ ├── template.js │ │ └── util.js ├── fonts │ └── material-icons │ │ ├── MaterialIcons-Regular.eot │ │ ├── MaterialIcons-Regular.ijmap │ │ ├── MaterialIcons-Regular.svg │ │ ├── MaterialIcons-Regular.ttf │ │ ├── MaterialIcons-Regular.woff │ │ ├── MaterialIcons-Regular.woff2 │ │ ├── README.md │ │ ├── codepoints │ │ └── material-icons.css ├── plugins │ ├── changeQueue.plugin.js │ ├── changeQueueStyle.plugin.js │ ├── dnd.plugin.js │ ├── index.js │ ├── preview.plugin.js │ ├── sizeinfo.plugin.js │ └── thumbnail.plugin.js ├── rg-uploader.js └── scss │ ├── index.scss │ ├── lib.scss │ ├── plugins.scss │ └── queue-style.scss ├── upload ├── json │ └── data.json ├── script-node │ ├── index.js │ └── move.js └── script-php │ ├── data.php │ ├── remove.php │ ├── upload.php │ └── upload_base64.php └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | 4 | node_modules/ 5 | README.html 6 | .idea/ 7 | 8 | upload/attachments/ 9 | upload/tmp/ 10 | 11 | *.user.* 12 | 13 | yarn-error.log 14 | yarn.lock 15 | bun.lockb 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | upload/ 2 | .idea/ 3 | docs/ 4 | node_modules/ 5 | 6 | .editorconfig 7 | yarn.lock 8 | yarn-error.log 9 | webpack.config.js 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Redgoose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rg-uploader 2 | 3 | 첨부파일을 업로드하고, 업로드한 파일들을 관리하고 쉽게 활용하기 위한 목적으로 만들어진 파일 업로더 컴포넌트입니다. 4 | 업로드한 파일을 다양한 방법으로 조작하거나 다른곳에다 활용할 수 있는 가능성이 큰 컴포넌트입니다. 5 | 6 | 7 | 8 | ## Demo 9 | 10 | 다음 링크를 통하여 어떠한 기능을 가지고 있는지 확인할 수 있습니다. 11 | 12 | https://redgoose-dev.github.io/rg-uploader/ 13 | 14 | 15 | 16 | ## Feature 17 | 18 | `rg-uploader`는 다음과 같은 기능을 가지고 있습니다. 19 | 20 | ### file management 21 | 22 | 업로드한 파일을 관리할 수 있으며, 이미지 파일은 어떤 이미지인지 확인 가능합니다. 그리고 파일들을 컨트롤할 수 있는 다양한 방법들을 제공하고 원하는 기능을 개발할 수 있습니다. 23 | 24 | ### queue system 25 | 26 | 파일이 업로드되면 목록에 올라가며 어떤 파일이 올라가 있는지 확인할 수 있습니다. 그리고 그 파일들을 선택하여 컨트롤하거나 외부 컴포넌트에 활용할 수 있습니다. 27 | 28 | ### queue style 29 | 30 | 파일목록이 단순히 파일이름 목록만으로 표시되는것을 원하지 않을수도 있습니다. 사진을 주로 올린다면 목록에서 사진을 바로 볼 수 있는 형태로 표시되기를 원할수도 있습니다. 31 | css로 다양한 스타일을 만들거나 수정하고, 선택할 수 있습니다. 32 | 33 | ### plugins 34 | 35 | 상황에 따라서 활용하는 기능이 다르며 모든 기능을 기본기능으로 다 집어넣을 수 없기 때문에 플러그인을 통하여 자유롭게 기능을 골라쓰거나 만들 수 있습니다. 36 | 업로더에서 가장 멋진 요소라고 할 수 있습니다. 37 | 38 | 39 | 40 | ## Installation and using 41 | 42 | 다음과 같이 모듈을 설치할 수 있습니다. 43 | 44 | ``` 45 | npm install --save rg-uploader 46 | ``` 47 | or 48 | ``` 49 | yarn add rg-uploader 50 | ``` 51 | 52 | ``` 53 | import RG_Uploader from 'rg-uploader'; 54 | import 'rg-uploader/dist/rg-uploader.css'; 55 | 56 | const rgUploader = new RG_Uploader.core(document.getElementById('id'), {}); 57 | ``` 58 | 59 | 60 | 61 | ## Documentation 62 | 63 | 업로더가 많은 상황을 고려하고 만들어서 소스의 덩치가 자연스럽게 커지게 되었습니다. 64 | 사용법이나 활용에 위키 다음 링크의 위키 문서를 참조해주세요. 65 | 66 | https://github.com/redgoose-dev/rg-uploader/wiki 67 | 68 | 69 | 70 | ## Thankful vendors 71 | 72 | `rg-uploader`는 다음과 같은 라이브러리를 사용하고 있으며 좋은 라이브러리 제공에 감사를 표합니다. 73 | 74 | * jQuery - http://jquery.com 75 | * Croppie - https://foliotek.github.io/Croppie/ 76 | * Sortable - https://github.com/RubaXa/Sortable 77 | * unsplash(sample image) - https://unsplash.com 78 | * Material design icons - https://design.google.com/icons/ 79 | 80 | 81 | 82 | ## Browser Compatibility 83 | 84 | 다름과 같은 브라우저 버전에서 원활한 작동이 가능합니다. 좀더 낮은 버전에서도 작동할 수 있습니다. 85 | 86 | * edge 25+ 87 | * safari 9+ 88 | * chrome 52+ 89 | -------------------------------------------------------------------------------- /dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | rg-uploader 8 | 9 | 10 | 19 | 20 | 21 |
22 |
23 |

rg-uploader development

24 |
25 | 26 |
27 |
28 |

File upload

29 |

아래 업로드 버튼을 눌러서 파일을 추가합니다.

30 |
31 |
32 |
33 |
    34 |
    35 |
    36 | 54 |
    55 |
    56 |
    external dropzone
    57 |
    58 |

    59 | 60 |

    61 |
    62 | 63 | 64 | -------------------------------------------------------------------------------- /dev/index.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import RGUploader from '../src/rg-uploader'; 3 | import * as plugins from '../src/plugins'; 4 | import Sortable from 'sortablejs'; 5 | import croppie from 'croppie'; 6 | 7 | window.rgUploader = new RGUploader(document.getElementById('dev'), { 8 | autoUpload: true, 9 | uploadScript: '/upload', 10 | removeScript: '/remove', 11 | allowFileTypes : ['jpeg', 'jpg', 'png', 'gif', 'zip', 'pdf', 'txt', 'swf'], 12 | uploadHeaders: { 'UPLOAD_AUTH': '1' }, 13 | removeHeaders: { 'REMOVE_AUTH': '1' }, 14 | externalFileForm : document.querySelector('#external'), 15 | queue: { 16 | height: 180, 17 | datas: '/data', 18 | buttons: [ 19 | { 20 | name: 'make thumbnail image', 21 | iconName: 'apps', 22 | className: 'btn-make-thumbnail', 23 | show(file) 24 | { 25 | return !!(file.type && file.type.split('/')[0] === 'image'); 26 | }, 27 | action(app, file) 28 | { 29 | if (!app.plugin.child.thumbnail) return false; 30 | const plugin = app.plugin.child.thumbnail; 31 | plugin.assignOption({ 32 | doneCallback(res) { 33 | app.queue.import([res]); 34 | } 35 | }); 36 | plugin.open(file); 37 | }, 38 | }, 39 | { 40 | name: 'remove queue', 41 | iconName: 'close', 42 | className: 'btn-remove-queue', 43 | action(app, file) 44 | { 45 | app.queue.removeQueue(file.id, false, true); 46 | }, 47 | }, 48 | { 49 | name: 'foo', 50 | iconName: 'extension', 51 | className: 'btn-foooooo', 52 | action(app, file) 53 | { 54 | app.queue.changeId(file.id, 111111); 55 | }, 56 | } 57 | ], 58 | }, 59 | plugin: [ 60 | { 61 | name: 'changeQueue', 62 | obj: new plugins.ChangeQueue({ 63 | class_sortable: Sortable, 64 | endChangeItem(app) 65 | { 66 | console.log('USER::endChangeItem', app.queue.items); 67 | } 68 | }, $) 69 | }, 70 | { 71 | name: 'changeQueueStyle', 72 | obj: new plugins.ChangeQueueStyle(document.querySelector('.rg-uploader > header'), $), 73 | }, 74 | { 75 | name: 'dragAndDrop', 76 | obj: new plugins.DragAndDrop(document.querySelector('.external-dropzone'), $), 77 | }, 78 | { 79 | name: 'preview', 80 | obj: new plugins.Preview($), 81 | }, 82 | { 83 | name: 'sizeinfo', 84 | obj: new plugins.SizeInfo(document.querySelector('.size-info'), $), 85 | }, 86 | { 87 | name: 'thumbnail', 88 | obj: new plugins.Thumbnail({ 89 | class_croppie: croppie.Croppie, 90 | }, $), 91 | }, 92 | ], 93 | uploadComplete(file, app) 94 | { 95 | console.log('USER::uploadComplete', file); 96 | }, 97 | removeDataFilter(res) 98 | { 99 | console.log('USER::removeDataFilter', res); 100 | return { 101 | state: 'success', 102 | } 103 | }, 104 | uploadCompleteAll(app) 105 | { 106 | console.log('uploadCompleteAll', app) 107 | }, 108 | uploadDataFilter(src) 109 | { 110 | console.log('uploadDataFilter', src); 111 | return { 112 | ...src, 113 | }; 114 | }, 115 | init(app) 116 | { 117 | app.$container.find('.toggle-select-all').on('click', () => app.queue.toggleSelectAllQueues()); 118 | } 119 | }); 120 | -------------------------------------------------------------------------------- /dist/MaterialIcons-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/dist/MaterialIcons-Regular.eot -------------------------------------------------------------------------------- /dist/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/dist/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /dist/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/dist/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /dist/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/dist/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /dist/rg-uploader.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Material Icons;font-style:normal;font-weight:400;src:url(MaterialIcons-Regular.eot);src:local("Material Icons"),local("MaterialIcons-Regular"),url(MaterialIcons-Regular.woff2) format("woff2"),url(MaterialIcons-Regular.woff) format("woff"),url(MaterialIcons-Regular.ttf) format("truetype")}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:24px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:"liga"}.rg-uploader .queues>.style-list{padding:10px}.rg-uploader .queues>.style-list li{position:relative;margin:3px 0 0;background:#fff;box-shadow:0 1px 8px rgba(0,0,0,.15)}.rg-uploader .queues>.style-list li:first-child{margin-top:0}.rg-uploader .queues>.style-list li.complete>div{display:flex;align-items:center}.rg-uploader .queues>.style-list li.complete>div .col{-webkit-flex:1;flex:1;padding:5px}.rg-uploader .queues>.style-list li.complete>div figure.col{display:none}.rg-uploader .queues>.style-list li.complete>div .bd{margin:0;padding:10px 8px 10px 10px;font-size:12px;color:#222;word-break:break-all}.rg-uploader .queues>.style-list li.complete>div .bd hr{display:none}.rg-uploader .queues>.style-list li.complete>div .bd .filetype{margin:0 5px 0 0}.rg-uploader .queues>.style-list li.complete>div .bd .state:before{content:" - "}.rg-uploader .queues>.style-list li.complete>div nav.col{-webkit-flex:inherit;flex:inherit;padding-right:8px;text-align:right}.rg-uploader .queues>.style-list li.complete.selected:after{content:"";display:block;position:absolute;left:0;right:0;top:0;bottom:0;z-index:2;border:3px solid #4a90e2;pointer-events:none}.rg-uploader .queues>.style-list li.loading>div{display:flex;align-items:center}.rg-uploader .queues>.style-list li.loading>div .col{-webkit-flex:1;flex:1;padding:5px}.rg-uploader .queues>.style-list li.loading>div figure.col{display:none}.rg-uploader .queues>.style-list li.loading>div .bd{margin:0;padding:10px 8px 10px 10px;font-size:12px;color:#222;word-break:break-all}.rg-uploader .queues>.style-list li.loading>div .bd hr{display:none}.rg-uploader .queues>.style-list li.loading>div .bd .state:before{content:" - "}.rg-uploader .queues>.style-list li.loading>div .bar{position:absolute;padding:0;left:0;right:0;top:0;bottom:0}.rg-uploader .queues>.style-list li.loading>div .bar>p{margin:0;position:relative;height:100%;background:#4a90e2;white-space:nowrap;overflow:hidden}.rg-uploader .queues>.style-list li.loading>div .bar span{position:absolute;display:block;right:8px;top:50%;margin-top:-6px;font-size:11px;color:#fff;line-height:normal}.rg-uploader .queues>.style-list li.loading>div nav.col{-webkit-flex:inherit;flex:inherit;padding-right:8px;text-align:right}.rg-uploader .queues>.style-list li.loading.ready .bar{display:none}.rg-uploader .queues>.style-list li.loading.ready .bd{color:#888}.rg-uploader .queues>.style-list li.error{padding:10px;background:#ce3e3e;color:#fff;font-size:12px;word-break:break-all}.rg-uploader .queues>.style-list li.error figure.col{display:none}.rg-uploader .queues>.style-list li.error .bd{margin:0}.rg-uploader .queues>.style-list li.error .bd .state:before{content:" - "}.rg-uploader .queues>.style-list li.error .bd hr{display:none}@media(max-width:640px){.rg-uploader .queues>.style-list li.complete>div{display:block}.rg-uploader .queues>.style-list li.complete>div p.col{padding:10px 10px 0}.rg-uploader .queues>.style-list li.complete>div p.col br{display:inherit;line-height:20px}.rg-uploader .queues>.style-list li.complete>div p.col .state:before{display:none}.rg-uploader .queues>.style-list li.complete>div p.col .size,.rg-uploader .queues>.style-list li.complete>div p.col .state{font-weight:700}.rg-uploader .queues>.style-list li.complete>div nav.col{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;margin:0 10px;padding:0}.rg-uploader .queues>.style-list li.complete>div nav.col button{padding:8px 0 12px;-webkit-flex:1;flex:1}.rg-uploader .queues>.style-list li.error>div p.col br{display:inherit;line-height:20px}.rg-uploader .queues>.style-list li.error>div p.col .state:before{display:none}.rg-uploader .queues>.style-list li.error>div p.col .size,.rg-uploader .queues>.style-list li.error>div p.col .state{font-weight:700}}.rg-uploader .queues>.style-web{padding:10px}.rg-uploader .queues>.style-web li{position:relative;margin-top:3px;background:#fff;box-shadow:0 1px 8px rgba(0,0,0,.15)}.rg-uploader .queues>.style-web li:first-child{margin-top:0}.rg-uploader .queues>.style-web li.complete>div{display:flex;align-items:center}.rg-uploader .queues>.style-web li.complete>div .col{-webkit-flex:1;flex:1;padding:5px}.rg-uploader .queues>.style-web li.complete>div figure.col{-webkit-flex:inherit;flex:inherit;width:70px;height:70px;margin:0;padding:0;text-indent:-9999px;background:no-repeat 50% 50%;background-size:cover}.rg-uploader .queues>.style-web li.complete>div figure.col.not-image{position:relative;background:#999}.rg-uploader .queues>.style-web li.complete>div figure.col.not-image:after,.rg-uploader .queues>.style-web li.complete>div figure.col.not-image:before{content:"";display:block;position:absolute;left:50%;top:50%;background:#ddd;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rg-uploader .queues>.style-web li.complete>div figure.col.not-image:before{width:30px;height:1px;margin-left:-15px;margin-top:-.5px}.rg-uploader .queues>.style-web li.complete>div figure.col.not-image:after{height:30px;width:1px;margin-top:-15px;margin-left:-.5px}.rg-uploader .queues>.style-web li.complete>div .bd{margin:0;padding-left:10px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:12px;color:#222;word-break:break-all}.rg-uploader .queues>.style-web li.complete>div .bd hr{border:none;margin:0;height:5px;background:none}.rg-uploader .queues>.style-web li.complete>div .bd .filetype{display:block;margin:0 0 3px;font-size:10px;color:#777}.rg-uploader .queues>.style-web li.complete>div .bd .size,.rg-uploader .queues>.style-web li.complete>div .bd .state{font-weight:700;font-size:10px}.rg-uploader .queues>.style-web li.complete>div nav.col{-webkit-flex:inherit;flex:inherit;text-align:right}.rg-uploader .queues>.style-web li.complete.selected:after{content:"";display:block;position:absolute;left:0;right:0;top:0;bottom:0;z-index:2;border:3px solid #4a90e2;pointer-events:none}.rg-uploader .queues>.style-web li.loading>div{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center}.rg-uploader .queues>.style-web li.loading>div .col{-webkit-flex:1;flex:1;padding:5px}.rg-uploader .queues>.style-web li.loading>div figure.col{display:none}.rg-uploader .queues>.style-web li.loading>div .bar{position:absolute;padding:0;left:0;right:0;top:0;bottom:0}.rg-uploader .queues>.style-web li.loading>div .bar>p{position:relative;margin:0;height:34px;background:#4a90e2;white-space:nowrap;overflow:hidden}.rg-uploader .queues>.style-web li.loading>div .bar span{position:absolute;display:block;right:8px;top:50%;margin-top:-6px;font-size:11px;color:#fff;line-height:normal}.rg-uploader .queues>.style-web li.loading>div .bd{margin:0;padding:10px 8px 10px 10px;font-size:12px;color:#222;word-break:break-all}.rg-uploader .queues>.style-web li.loading>div .bd hr{display:none}.rg-uploader .queues>.style-web li.loading>div .bd .state:before{content:" - "}.rg-uploader .queues>.style-web li.loading>div nav.col{-webkit-flex:inherit;flex:inherit;text-align:right}.rg-uploader .queues>.style-web li.loading.ready .bar{display:none}.rg-uploader .queues>.style-web li.loading.ready .bd{color:#888}.rg-uploader .queues>.style-web li.error{padding:10px;background:#ce3e3e;color:#fff;font-size:12px;word-break:break-all}.rg-uploader .queues>.style-web li.error figure.col{display:none}.rg-uploader .queues>.style-web li.error .bd{margin:0}.rg-uploader .queues>.style-web li.error .bd .state:before{content:" - "}.rg-uploader .queues>.style-web li.error .bd hr{display:none}@media(max-width:640px){.rg-uploader .queues>.style-web li.complete>div{position:relative;display:block;padding-left:70px}.rg-uploader .queues>.style-web li.complete>div figure.col{position:absolute;left:0;top:0;bottom:0;height:auto}.rg-uploader .queues>.style-web li.complete>div .bd{padding:8px 5px 8px 10px}.rg-uploader .queues>.style-web li.complete>div nav.col{display:flex;align-items:center;text-align:center;margin:0 10px;padding:0}.rg-uploader .queues>.style-web li.complete>div nav.col button{padding:8px 0 12px;flex:1}}.rg-uploader .queues>.style-album{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;padding:5px}.rg-uploader .queues>.style-album li{width:20%;padding:5px}@media(max-width:1024px){.rg-uploader .queues>.style-album li{width:20%}}@media(max-width:768px){.rg-uploader .queues>.style-album li{width:25%}}@media(max-width:480px){.rg-uploader .queues>.style-album li{width:50%}}.rg-uploader .queues>.style-album li>div{position:relative;overflow:hidden;box-shadow:0 1px 8px rgba(0,0,0,.15)}.rg-uploader .queues>.style-album li>div figure.col{margin:0;min-height:80px;height:10vw;max-height:150px;background:no-repeat 50% 50%;background-size:cover}.rg-uploader .queues>.style-album li>div figure.col.not-image{position:relative;background:#999}.rg-uploader .queues>.style-album li>div figure.col.not-image:after{content:"no img";position:absolute;left:0;right:0;top:50%;margin-top:-6px;font-family:Arial,sans-serif;color:#ddd;font-size:11px;text-align:center}.rg-uploader .queues>.style-album li>div .bd{display:block;margin:8px;font-family:Arial,sans-serif;word-break:break-all;font-size:11px;line-height:13px;color:#222}.rg-uploader .queues>.style-album li>div .bd hr{margin:0;height:4px;border:none;background:none}.rg-uploader .queues>.style-album li>div .bd .filetype{display:block;margin:0 0 2px;font-size:10px;color:#888}.rg-uploader .queues>.style-album li>div .bd .size,.rg-uploader .queues>.style-album li>div .bd .state{font-weight:700;font-size:10px}.rg-uploader .queues>.style-album li>div nav.col{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;border-top:1px solid #eee;margin:0;padding:0;text-align:center;white-space:nowrap}.rg-uploader .queues>.style-album li>div nav.col button{padding:8px 0;-webkit-flex:1;flex:1}.rg-uploader .queues>.style-album li.selected>div:after{content:"";display:block;position:absolute;left:0;right:0;top:0;bottom:0;z-index:2;border:3px solid #4a90e2;pointer-events:none}.rg-uploader .queues>.style-album li.complete figure.col{text-indent:-9999px}.rg-uploader .queues>.style-album li.loading figure.col{background:#eee}.rg-uploader .queues>.style-album li.loading figure.col p{margin:0;position:relative;height:100%;background:#4a90e2}.rg-uploader .queues>.style-album li.loading figure.col p span{position:absolute;display:block;right:8px;top:50%;margin-top:-6px;font-size:11px;color:#fff;line-height:normal}.rg-uploader .queues>.style-album li.loading .bar,.rg-uploader .queues>.style-album li.loading figure.col:after{display:none}.rg-uploader .queues>.style-album li.loading.ready .bd{color:#888}.rg-uploader .queues>.style-album li.error figure.col{background:#ce3e3e}@media(min-width:1440px){.rg-uploader .queues.is-large-size>.style-album>li{width:16.6666%}}@media(min-width:1920px){.rg-uploader .queues.is-large-size>.style-album>li{width:12.5%}}@media(min-width:2400px){.rg-uploader .queues.is-large-size>.style-album>li{width:10%}}.rg-uploader .rg-uploader-body>.preview{flex:none}.rg-uploader .preview{flex:none;width:150px;height:100%;overflow:hidden}.rg-uploader .preview.hide{display:none}.rg-uploader .preview figure{height:100%;margin:0;background:no-repeat 50% 50%;background-size:cover;text-indent:-9999px}.rg-uploader .preview .not-image{position:relative;background:#7e7f80!important}.rg-uploader .preview .not-image:after{content:"not image";position:absolute;top:50%;left:0;right:0;margin-top:-7px;text-indent:0;font-family:Arial,sans-serif;color:#ccc;font-size:11px;text-align:center}@media(max-width:640px){.rg-uploader .preview{display:none}}.rg-uploader>footer>div{width:140px;padding:0 10px 0 0}.rg-uploader>footer>div>p{margin:0;font-family:Arial,sans-serif;text-align:right;font-size:12px;color:#222}.rg-uploader>footer>div>p em{font-style:normal}.rg-uploader>header nav{position:absolute;top:50%;right:10px;height:28px;margin-top:-14px;border:1px solid #4a90e2;border-radius:3px;font-size:0;overflow:hidden}.rg-uploader>header nav button{display:inline-block;height:100%;margin:0;padding:0 7px;background:none;cursor:pointer;border:none;border-left:1px solid #4a90e2}.rg-uploader>header nav button:first-child{border-left:none}.rg-uploader>header nav button i{font-size:20px;line-height:22px;color:#4a90e2}.rg-uploader>header nav button.on{background:#4a90e2;cursor:default;outline:0}.rg-uploader>header nav button.on i{color:#fff}@media(max-width:640px){.rg-uploader>header nav{position:static;margin:8px 0 0;height:34px;display:-webkit-flex;display:flex}.rg-uploader>header nav button{-webkit-flex:1;flex:1;padding:0}.rg-uploader>header nav button i{font-size:20px;line-height:22px}}.rg-uploader .queues.drop-mode:after{content:"";position:absolute;display:block;left:0;right:0;top:0;bottom:0;border:2px dashed #4a90e2;pointer-events:none}.rg-plugin-thumbnail{position:fixed;left:0;right:0;top:0;bottom:0;z-index:99999;display:none}.rg-plugin-thumbnail,.rg-plugin-thumbnail *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.rg-plugin-thumbnail.show{display:block}.rg-plugin-thumbnail>.bg{position:absolute;left:0;right:0;top:0;bottom:0;background:rgba(0,0,0,.8)}.rg-plugin-thumbnail>.wrap{position:absolute;left:50%;top:50%;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.25)}.rg-plugin-thumbnail>.wrap .img-wrap{padding-bottom:60px;height:100%}.rg-plugin-thumbnail>.wrap .img-wrap figure{margin:0;height:100%;background:#333}.rg-plugin-thumbnail>.wrap .img-wrap .croppie-container{position:relative;padding:0}.rg-plugin-thumbnail>.wrap .img-wrap .cr-slider-wrap{position:absolute;left:0;right:0;bottom:20px;z-index:2}.rg-plugin-thumbnail>.wrap .body{position:absolute;left:0;right:0;bottom:0;height:60px;background:#fff;display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center}.rg-plugin-thumbnail>.wrap .body .meta{-webkit-flex:1;flex:1;padding-left:10px;padding-right:20px}.rg-plugin-thumbnail>.wrap .body .meta p{margin:0;font-size:13px;color:#222}.rg-plugin-thumbnail>.wrap .body nav{padding-right:10px;text-align:right}.rg-plugin-thumbnail>.wrap .body nav button{margin:0;padding:5px;border:none;background:none;cursor:pointer}.rg-plugin-thumbnail>.wrap .body nav button i{vertical-align:top;color:#57595b}.rg-plugin-thumbnail>.wrap .body nav button+button{margin-left:8px}.rg-plugin-changeQueue li{cursor:move}.rg-plugin-changeQueue .sortable-ghost{opacity:.2}.rg-uploader .bracket:before{content:"("}.rg-uploader .bracket:after{content:")"}.rg-uploader .bracket.large:before{content:"["}.rg-uploader .bracket.large:after{content:"]"}.rg-uploader,.rg-uploader-popup,.rg-uploader-popup body{overflow:hidden}.rg-uploader{border:1px solid #ccc;border-radius:4px;box-sizing:border-box}.rg-uploader em{font-style:normal}.rg-uploader .queues{position:relative;height:100%;box-sizing:border-box}.rg-uploader .queues>ul{margin:0;padding:0;height:100%;list-style:none;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box}.rg-uploader .queues nav{font-size:0;box-sizing:border-box}.rg-uploader .queues nav button{margin:0;padding:4px;border:none;font-size:0;background:none;cursor:pointer}.rg-uploader .queues nav button.on i{color:#4a90e2}.rg-uploader .queues nav button.disabled{cursor:not-allowed}.rg-uploader .queues nav button.disabled i{color:#b2b5b9}.rg-uploader .queues nav i{font-size:16px;color:#57595b}@media(max-width:640px){.rg-uploader .queues{overflow:auto}}.rg-uploader-header{position:relative;padding:10px 130px 10px 10px;border-bottom:2px solid #ccc}.rg-uploader-header h1{margin:0;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:18px;color:#222;font-weight:400}.rg-uploader-header p{margin:3px 0 0;font-size:12px;color:#777}@media(max-width:640px){.rg-uploader-header{padding:10px}}.rg-uploader-footer{display:flex;display:-webkit-flex;align-items:center;border-top:2px solid #ccc;overflow:hidden}.rg-uploader-footer nav{flex:1;font-size:0}.rg-uploader-footer nav>*{display:inline-block;margin:0;padding:10px;vertical-align:middle;font-size:0;border:none;border-right:1px solid #ddd;overflow:hidden;outline:none;cursor:pointer;background:none}.rg-uploader-footer nav>:hover{background:#f4f4f4}.rg-uploader-footer nav>*>*{display:inline-block;vertical-align:middle}.rg-uploader-footer nav>:first-child{border-bottom-left-radius:4px}.rg-uploader-footer nav>.disabled i,.rg-uploader-footer nav>.disabled span{color:#999}.rg-uploader-footer nav span{margin:0;font-family:Arial,sans-serif;font-size:12px;color:#222}.rg-uploader-footer nav i{margin:0 3px 0 0;font-size:16px;color:#222}.rg-uploader-footer nav .add-file input[type=file]{position:absolute;visibility:hidden}@media(max-width:640px){.rg-uploader-footer nav span{display:none}.rg-uploader-footer nav i{margin:0;font-size:18px}}.rg-uploader-body{display:flex;min-height:150px}.rg-uploader-body>.queues{flex:1}@media(max-width:640px){.rg-uploader-body{height:auto!important;min-height:100px}} -------------------------------------------------------------------------------- /dist/rg-uploader.plugins.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.plugins=t():(e.RG_Uploader=e.RG_Uploader||{},e.RG_Uploader.plugins=t())}(window,(function(){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="./",n(n.s=0)}([function(e,t,n){"use strict";function i(e,t){this.name="Change Queue",this.sortable=null;var n=t||n||jQuery,i=this,o=null;function r(){for(var e=[],t=[],r=o.queue.$queue.children("li").map((function(){return o.queue.findItem(n(this).data("id"))})),s=0;s'+t.iconName+"");!function(e){e.on("click",(function(e){if(n(this).hasClass("on"))return!1;o.queue.changeStyle(n(this).data("style"))}))}(r),i.$nav.append(r)})),i.$buttons=i.$nav.children("button")}function s(){i.$buttons.removeClass("on").filter(".style-"+name).addClass("on")}n&&(this.name="Change Queue Style",this.buttonsParams=[{name:"list",title:"list style",iconName:"menu",default:!0},{name:"web",title:"web style",iconName:"view_list",default:!1},{name:"album",title:"album style",iconName:"view_module",default:!1}],this.$nav=null,this.$buttons=null,this.init=function(t){o=t;var a=e?n(e):o.$container.children("header");i.$nav=n(''),a.append(i.$nav),r(),s(o.queue.style)},this.eventListener=function(e,t){switch(e){case"queue.changeStyle":s(t.style)}})};var s=function(e,t){this.name="Drag And Drop",this.areaElements=[];var n=t||n||jQuery,i=this,o=null;if(n){var r=function(e){if(o.uploader.uploading)return alert(o.lang("error_add_upload")),!1;o.uploader.start(e||[])};this.init=function(t){o=t;var s=e?n(e):null;if(this.areaElements.push(o.queue.$queue.parent().get(0)),s&&s.each((function(){i.areaElements.push(this)})),this.areaElements.length){var a=function(e){if(!(window.File&&window.FileList&&window.FileReader&&window.Blob))return!1;if(!e.length)return!1;for(var t=n.Deferred(),i=!1,o=function(e){if(e.stopPropagation(),e.preventDefault(),"dragover"===e.type){if(i)return!1;i=!0,"all"===e.dataTransfer.effectAllowed&&n(e.currentTarget).addClass("drop-mode"),e.dataTransfer.dropEffect="copy"}else i=!1,n(e.currentTarget).removeClass("drop-mode");return!1},r=function(e){e.stopPropagation(),e.preventDefault(),o(e);var n=e.dataTransfer?e.dataTransfer.files:null;return n&&n.length&&t.notify(n),!1},s=0;s
    '),n.$preview.width(o),i.$container.find("[data-comp=queue]").prepend(n.$preview),r()},this.eventListener=function(e,t){switch(e){case"queue.selectQueue":var o=t.$selectElement.data("id"),s=i.queue.findItem(o),a=i.queue.items.files[s],l=t.$selectElement.hasClass("selected")&&a.type&&"image"===a.type.split("/")[0]?a.fullSrc:null;r(l);break;case"queue.changeStyle":"list"===t.style?n.$preview.removeClass("hide"):n.$preview.addClass("hide")}}}};var l=function(e,t){this.name="Size info",this.size={current:0,total:0};var n=t||n||jQuery,i=this,o=null,r=null,s=null,a=null;function l(e){if(0===e)return"0";var t=parseInt(Math.floor(Math.log(e)/Math.log(1024)));return Math.round(e/Math.pow(1024,t),2)+""+["Bytes","KB","MB","GB","TB"][t]}n&&(this.init=function(t){o=t,(r=e?n(e):o.$container.find(".size-info")).length||o.plugin.error(name),r.append('

    Size : /

    '),s=r.find("[data-text=currentSize]"),a=r.find("[data-text=totalSize]"),this.size.total=o.options.limitSizeTotal,this.update()},this.update=function(){s.text(l(this.size.current)),a.text(l(this.size.total))},this.eventListener=function(e,t){switch(e){case"queue.uploadComplete":i.size.current+=t.file.size,i.update(o.queue.getSize());break;case"queue.removeQueue":i.size.current=o.queue.getSize(),i.update()}})};function u(e,t){this.name="Make thumbnail";var n=t||n||jQuery,i=this,o=null,r=!1,s=null;function a(e){if(!e&&r)return!1;r=!0,i.$el.wrap.width("100%").height("100%").css({marginLeft:0,marginTop:0,left:0,top:0}),i.croppie&&p(!0)}function l(e){if(!e&&!r)return!1;r=!1,i.$el.wrap.width(i.options.width).height(i.options.height).css({marginLeft:0-.5*i.options.width+"px",marginTop:0-.5*i.options.height+"px",left:"50%",top:"50%"}),i.croppie&&p(!0)}function u(e){var t=n(window);if(!i.croppie)return clearTimeout(s),!1;s&&clearTimeout(s),s=setTimeout((function(){if(r&&t.width()<640)return a(!0),!1;t.width()<640?a(!0):t.width()>640&&l(!0)}),300)}function p(e){var t=e?i.croppie.get():{};c(),i.options.croppie.boundary={width:i.options.mobileSize>n(window).width()?n(window).width():i.options.width,height:(i.options.mobileSize>n(window).width()?n(window).height():i.options.height)-60},i.croppie=new Croppie(i.$el.figure.get(0),i.options.croppie),e&&i.rebind({src:i.file.fullSrc,points:t.points},(function(){i.croppie.setZoom(t.zoom)}))}function c(){i.croppie&&(i.croppie.destroy(),i.croppie=null)}function d(e){i.croppie.result(i.options.output).then((function(e){i.options.uploadScript?n.post(i.options.uploadScript,{name:i.file.name,image:e,id:f()},(function(e){try{e=JSON.parse(e)}catch(e){return alert("parse error"),!1}if("error"===e.state)return alert(e.response.message),!1;i.options.doneCallback&&i.options.doneCallback(e.response,o,i.file)})):i.options.doneCallback&&i.options.doneCallback({id:f(),name:"thumb-"+i.file.name,src:e,type:"image/"+i.options.output.format,size:0},o,i.file),i.close()}),(function(e){console.log("ERROR",e)}))}function f(e){e=e||10;for(var t,n,i=(+new Date).toString().split("").reverse(),o="",r=0;r

    message

    '),i.$el.wrap=i.$el.con.children(".wrap"),i.$el.bg=i.$el.con.children(".bg"),i.$el.figure=i.$el.con.find(".img-wrap figure"),i.$el.meta=i.$el.con.find(".meta > p"),i.$el.btn_done=i.$el.con.find(".btn-done"),i.$el.btn_close=i.$el.con.find(".btn-close"),n("body").append(i.$el.con),i.$el.bg.on("click",(function(){i.close()})),i.$el.btn_close.on("click",(function(){i.close()})),i.$el.btn_done.on("click",d),n(window).on("resize.rgUploader",u)},this.open=function(e,t){t=t||{},this.$el.con.addClass("show"),n("html").addClass("rg-uploader-popup"),this.file=e,n(window).width() h1 { 82 | margin: 0; 83 | font-size: 30px; 84 | font-weight: 600; 85 | } 86 | .demo-container > p { 87 | margin: 1rem 0; 88 | font-size: 15px; 89 | color: #222; 90 | } 91 | .demo-container > section { 92 | margin-top: 60px; 93 | } 94 | .demo-container > p + section { 95 | margin-top: 2.5rem; 96 | } 97 | .demo-container > section:first-child { 98 | margin-top: 0; 99 | } 100 | 101 | .demo-container > section > h1 { 102 | position: relative; 103 | padding-top: 8px; 104 | margin: 0 0 1rem 0; 105 | font-size: 1.5rem; 106 | color: #222; 107 | font-weight: 600; 108 | } 109 | .demo-container > section > h1:before { 110 | content: ''; 111 | position: absolute; 112 | left: 0; 113 | top: 0; 114 | width: 15px; 115 | height: 2px; 116 | background: #222; 117 | } 118 | .demo-container > section > p { 119 | margin: 1rem 0; 120 | font-size: 0.9375rem; 121 | color: #222; 122 | } 123 | .demo-container > section > p > a { 124 | color: #7b25ff; 125 | } 126 | .demo-container > section > .example { 127 | margin: 1rem 0; 128 | } 129 | .demo-container .code { 130 | margin: 1rem 0; 131 | } 132 | .demo-container .code > h1 { 133 | margin: 20px 0 8px; 134 | font-size: 15px; 135 | font-weight: bold; 136 | color: #222; 137 | } 138 | .demo-container .code > p { 139 | margin: 1rem 0; 140 | font-size: 14px; 141 | color: #222; 142 | } 143 | .demo-container .code pre { 144 | margin: 0; 145 | font-size: .8125rem; 146 | } 147 | 148 | @media (max-width: 640px) { 149 | .demo-container > h1 { 150 | font-size: 20px; 151 | } 152 | .demo-container > section { 153 | margin-top: 60px; 154 | } 155 | .demo-container > section > h1 { 156 | font-size: 18px; 157 | } 158 | .demo-container .code pre { 159 | font-size: 10px; 160 | } 161 | } 162 | 163 | 164 | /* to editor */ 165 | .to-editor { 166 | margin: 1rem 0; 167 | } 168 | .to-editor h2 { 169 | margin: 10px 0 5px; 170 | font-size: 1rem; 171 | } 172 | .to-editor textarea, 173 | .to-editor figure { 174 | margin: 0; 175 | padding: 10px; 176 | border: 1px solid #bbb; 177 | } 178 | .to-editor textarea { 179 | width: 100%; height: 100px; 180 | font-size: 0.875rem; 181 | line-height: 1.5; 182 | -webkit-box-sizing: border-box; 183 | -moz-box-sizing: border-box; 184 | box-sizing: border-box; 185 | } 186 | .to-editor figure { 187 | margin: 0; 188 | min-height: 100px; 189 | } 190 | .to-editor figure img { 191 | vertical-align: top; 192 | width: 100px; 193 | } 194 | 195 | 196 | /* drop */ 197 | .rg-external-dropzone { 198 | position: relative; 199 | display: block; 200 | padding: 40px 0; 201 | margin: 15px 0; 202 | background: #4A90E2; 203 | text-align: center; 204 | color: #fff; 205 | } 206 | .rg-external-dropzone.drop-mode { 207 | background: #999; 208 | color: #fff; 209 | } 210 | -------------------------------------------------------------------------------- /docs/assets/ico-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/docs/assets/ico-symbol.png -------------------------------------------------------------------------------- /docs/assets/img-demo-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/docs/assets/img-demo-1.jpg -------------------------------------------------------------------------------- /docs/assets/img-demo-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/docs/assets/img-demo-2.jpg -------------------------------------------------------------------------------- /docs/assets/img-demo-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/docs/assets/img-demo-3.jpg -------------------------------------------------------------------------------- /docs/assets/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript */ 2 | /** 3 | * okaidia theme for JavaScript, CSS and HTML 4 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 5 | * @author ocodia 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: #f8f8f2; 11 | background: none; 12 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | border-radius: 0.3em; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #272822; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1em; 47 | border-radius: .3em; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: slategray; 56 | } 57 | 58 | .token.punctuation { 59 | color: #f8f8f2; 60 | } 61 | 62 | .namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.constant, 69 | .token.symbol, 70 | .token.deleted { 71 | color: #f92672; 72 | } 73 | 74 | .token.boolean, 75 | .token.number { 76 | color: #ae81ff; 77 | } 78 | 79 | .token.selector, 80 | .token.attr-name, 81 | .token.string, 82 | .token.char, 83 | .token.builtin, 84 | .token.inserted { 85 | color: #a6e22e; 86 | } 87 | 88 | .token.operator, 89 | .token.entity, 90 | .token.url, 91 | .language-css .token.string, 92 | .style .token.string, 93 | .token.variable { 94 | color: #f8f8f2; 95 | } 96 | 97 | .token.atrule, 98 | .token.attr-value, 99 | .token.function { 100 | color: #e6db74; 101 | } 102 | 103 | .token.keyword { 104 | color: #66d9ef; 105 | } 106 | 107 | .token.regex, 108 | .token.important { 109 | color: #fd971f; 110 | } 111 | 112 | .token.important, 113 | .token.bold { 114 | font-weight: bold; 115 | } 116 | .token.italic { 117 | font-style: italic; 118 | } 119 | 120 | .token.entity { 121 | cursor: help; 122 | } 123 | -------------------------------------------------------------------------------- /docs/assets/prism.js: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript */ 2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(g?b[1].length:0),_=b.index+b[0].length,A=m,S=y,P=r.length;P>A&&_>S;++A)S+=(r[A].matchedStr||r[A]).length,w>=S&&(++m,y=S);if(r[m]instanceof a||r[A-1].greedy)continue;k=A-m,v=e.slice(y,S),b.index-=y}if(b){g&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),j=[m,k];x&&j.push(x);var N=new a(l,c?n.tokenize(b,c):b,d,b,h);j.push(N),O&&j.push(O),Array.prototype.splice.apply(r,j)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.matchedStr=a||null,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var i={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=(o?" ":"")+s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 3 | Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; 4 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); 5 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; 6 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*\*?|\/|~|\^|%|\.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; 7 | -------------------------------------------------------------------------------- /docs/dist/MaterialIcons-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/docs/dist/MaterialIcons-Regular.eot -------------------------------------------------------------------------------- /docs/dist/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/docs/dist/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /docs/dist/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/docs/dist/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /docs/dist/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/docs/dist/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /docs/dist/rg-uploader.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Material Icons;font-style:normal;font-weight:400;src:url(MaterialIcons-Regular.eot);src:local("Material Icons"),local("MaterialIcons-Regular"),url(MaterialIcons-Regular.woff2) format("woff2"),url(MaterialIcons-Regular.woff) format("woff"),url(MaterialIcons-Regular.ttf) format("truetype")}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:24px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:"liga"}.rg-uploader .queues>.style-list{padding:10px}.rg-uploader .queues>.style-list li{position:relative;margin:3px 0 0;background:#fff;box-shadow:0 1px 8px rgba(0,0,0,.15)}.rg-uploader .queues>.style-list li:first-child{margin-top:0}.rg-uploader .queues>.style-list li.complete>div{display:flex;align-items:center}.rg-uploader .queues>.style-list li.complete>div .col{-webkit-flex:1;flex:1;padding:5px}.rg-uploader .queues>.style-list li.complete>div figure.col{display:none}.rg-uploader .queues>.style-list li.complete>div .bd{margin:0;padding:10px 8px 10px 10px;font-size:12px;color:#222;word-break:break-all}.rg-uploader .queues>.style-list li.complete>div .bd hr{display:none}.rg-uploader .queues>.style-list li.complete>div .bd .filetype{margin:0 5px 0 0}.rg-uploader .queues>.style-list li.complete>div .bd .state:before{content:" - "}.rg-uploader .queues>.style-list li.complete>div nav.col{-webkit-flex:inherit;flex:inherit;padding-right:8px;text-align:right}.rg-uploader .queues>.style-list li.complete.selected:after{content:"";display:block;position:absolute;left:0;right:0;top:0;bottom:0;z-index:2;border:3px solid #4a90e2;pointer-events:none}.rg-uploader .queues>.style-list li.loading>div{display:flex;align-items:center}.rg-uploader .queues>.style-list li.loading>div .col{-webkit-flex:1;flex:1;padding:5px}.rg-uploader .queues>.style-list li.loading>div figure.col{display:none}.rg-uploader .queues>.style-list li.loading>div .bd{margin:0;padding:10px 8px 10px 10px;font-size:12px;color:#222;word-break:break-all}.rg-uploader .queues>.style-list li.loading>div .bd hr{display:none}.rg-uploader .queues>.style-list li.loading>div .bd .state:before{content:" - "}.rg-uploader .queues>.style-list li.loading>div .bar{position:absolute;padding:0;left:0;right:0;top:0;bottom:0}.rg-uploader .queues>.style-list li.loading>div .bar>p{margin:0;position:relative;height:100%;background:#4a90e2;white-space:nowrap;overflow:hidden}.rg-uploader .queues>.style-list li.loading>div .bar span{position:absolute;display:block;right:8px;top:50%;margin-top:-6px;font-size:11px;color:#fff;line-height:normal}.rg-uploader .queues>.style-list li.loading>div nav.col{-webkit-flex:inherit;flex:inherit;padding-right:8px;text-align:right}.rg-uploader .queues>.style-list li.loading.ready .bar{display:none}.rg-uploader .queues>.style-list li.loading.ready .bd{color:#888}.rg-uploader .queues>.style-list li.error{padding:10px;background:#ce3e3e;color:#fff;font-size:12px;word-break:break-all}.rg-uploader .queues>.style-list li.error figure.col{display:none}.rg-uploader .queues>.style-list li.error .bd{margin:0}.rg-uploader .queues>.style-list li.error .bd .state:before{content:" - "}.rg-uploader .queues>.style-list li.error .bd hr{display:none}@media(max-width:640px){.rg-uploader .queues>.style-list li.complete>div{display:block}.rg-uploader .queues>.style-list li.complete>div p.col{padding:10px 10px 0}.rg-uploader .queues>.style-list li.complete>div p.col br{display:inherit;line-height:20px}.rg-uploader .queues>.style-list li.complete>div p.col .state:before{display:none}.rg-uploader .queues>.style-list li.complete>div p.col .size,.rg-uploader .queues>.style-list li.complete>div p.col .state{font-weight:700}.rg-uploader .queues>.style-list li.complete>div nav.col{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;margin:0 10px;padding:0}.rg-uploader .queues>.style-list li.complete>div nav.col button{padding:8px 0 12px;-webkit-flex:1;flex:1}.rg-uploader .queues>.style-list li.error>div p.col br{display:inherit;line-height:20px}.rg-uploader .queues>.style-list li.error>div p.col .state:before{display:none}.rg-uploader .queues>.style-list li.error>div p.col .size,.rg-uploader .queues>.style-list li.error>div p.col .state{font-weight:700}}.rg-uploader .queues>.style-web{padding:10px}.rg-uploader .queues>.style-web li{position:relative;margin-top:3px;background:#fff;box-shadow:0 1px 8px rgba(0,0,0,.15)}.rg-uploader .queues>.style-web li:first-child{margin-top:0}.rg-uploader .queues>.style-web li.complete>div{display:flex;align-items:center}.rg-uploader .queues>.style-web li.complete>div .col{-webkit-flex:1;flex:1;padding:5px}.rg-uploader .queues>.style-web li.complete>div figure.col{-webkit-flex:inherit;flex:inherit;width:70px;height:70px;margin:0;padding:0;text-indent:-9999px;background:no-repeat 50% 50%;background-size:cover}.rg-uploader .queues>.style-web li.complete>div figure.col.not-image{position:relative;background:#999}.rg-uploader .queues>.style-web li.complete>div figure.col.not-image:after,.rg-uploader .queues>.style-web li.complete>div figure.col.not-image:before{content:"";display:block;position:absolute;left:50%;top:50%;background:#ddd;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rg-uploader .queues>.style-web li.complete>div figure.col.not-image:before{width:30px;height:1px;margin-left:-15px;margin-top:-.5px}.rg-uploader .queues>.style-web li.complete>div figure.col.not-image:after{height:30px;width:1px;margin-top:-15px;margin-left:-.5px}.rg-uploader .queues>.style-web li.complete>div .bd{margin:0;padding-left:10px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:12px;color:#222;word-break:break-all}.rg-uploader .queues>.style-web li.complete>div .bd hr{border:none;margin:0;height:5px;background:none}.rg-uploader .queues>.style-web li.complete>div .bd .filetype{display:block;margin:0 0 3px;font-size:10px;color:#777}.rg-uploader .queues>.style-web li.complete>div .bd .size,.rg-uploader .queues>.style-web li.complete>div .bd .state{font-weight:700;font-size:10px}.rg-uploader .queues>.style-web li.complete>div nav.col{-webkit-flex:inherit;flex:inherit;text-align:right}.rg-uploader .queues>.style-web li.complete.selected:after{content:"";display:block;position:absolute;left:0;right:0;top:0;bottom:0;z-index:2;border:3px solid #4a90e2;pointer-events:none}.rg-uploader .queues>.style-web li.loading>div{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center}.rg-uploader .queues>.style-web li.loading>div .col{-webkit-flex:1;flex:1;padding:5px}.rg-uploader .queues>.style-web li.loading>div figure.col{display:none}.rg-uploader .queues>.style-web li.loading>div .bar{position:absolute;padding:0;left:0;right:0;top:0;bottom:0}.rg-uploader .queues>.style-web li.loading>div .bar>p{position:relative;margin:0;height:34px;background:#4a90e2;white-space:nowrap;overflow:hidden}.rg-uploader .queues>.style-web li.loading>div .bar span{position:absolute;display:block;right:8px;top:50%;margin-top:-6px;font-size:11px;color:#fff;line-height:normal}.rg-uploader .queues>.style-web li.loading>div .bd{margin:0;padding:10px 8px 10px 10px;font-size:12px;color:#222;word-break:break-all}.rg-uploader .queues>.style-web li.loading>div .bd hr{display:none}.rg-uploader .queues>.style-web li.loading>div .bd .state:before{content:" - "}.rg-uploader .queues>.style-web li.loading>div nav.col{-webkit-flex:inherit;flex:inherit;text-align:right}.rg-uploader .queues>.style-web li.loading.ready .bar{display:none}.rg-uploader .queues>.style-web li.loading.ready .bd{color:#888}.rg-uploader .queues>.style-web li.error{padding:10px;background:#ce3e3e;color:#fff;font-size:12px;word-break:break-all}.rg-uploader .queues>.style-web li.error figure.col{display:none}.rg-uploader .queues>.style-web li.error .bd{margin:0}.rg-uploader .queues>.style-web li.error .bd .state:before{content:" - "}.rg-uploader .queues>.style-web li.error .bd hr{display:none}@media(max-width:640px){.rg-uploader .queues>.style-web li.complete>div{position:relative;display:block;padding-left:70px}.rg-uploader .queues>.style-web li.complete>div figure.col{position:absolute;left:0;top:0;bottom:0;height:auto}.rg-uploader .queues>.style-web li.complete>div .bd{padding:8px 5px 8px 10px}.rg-uploader .queues>.style-web li.complete>div nav.col{display:flex;align-items:center;text-align:center;margin:0 10px;padding:0}.rg-uploader .queues>.style-web li.complete>div nav.col button{padding:8px 0 12px;flex:1}}.rg-uploader .queues>.style-album{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;padding:5px}.rg-uploader .queues>.style-album li{width:20%;padding:5px}@media(max-width:1024px){.rg-uploader .queues>.style-album li{width:20%}}@media(max-width:768px){.rg-uploader .queues>.style-album li{width:25%}}@media(max-width:480px){.rg-uploader .queues>.style-album li{width:50%}}.rg-uploader .queues>.style-album li>div{position:relative;overflow:hidden;box-shadow:0 1px 8px rgba(0,0,0,.15)}.rg-uploader .queues>.style-album li>div figure.col{margin:0;min-height:80px;height:10vw;max-height:150px;background:no-repeat 50% 50%;background-size:cover}.rg-uploader .queues>.style-album li>div figure.col.not-image{position:relative;background:#999}.rg-uploader .queues>.style-album li>div figure.col.not-image:after{content:"no img";position:absolute;left:0;right:0;top:50%;margin-top:-6px;font-family:Arial,sans-serif;color:#ddd;font-size:11px;text-align:center}.rg-uploader .queues>.style-album li>div .bd{display:block;margin:8px;font-family:Arial,sans-serif;word-break:break-all;font-size:11px;line-height:13px;color:#222}.rg-uploader .queues>.style-album li>div .bd hr{margin:0;height:4px;border:none;background:none}.rg-uploader .queues>.style-album li>div .bd .filetype{display:block;margin:0 0 2px;font-size:10px;color:#888}.rg-uploader .queues>.style-album li>div .bd .size,.rg-uploader .queues>.style-album li>div .bd .state{font-weight:700;font-size:10px}.rg-uploader .queues>.style-album li>div nav.col{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;border-top:1px solid #eee;margin:0;padding:0;text-align:center;white-space:nowrap}.rg-uploader .queues>.style-album li>div nav.col button{padding:8px 0;-webkit-flex:1;flex:1}.rg-uploader .queues>.style-album li.selected>div:after{content:"";display:block;position:absolute;left:0;right:0;top:0;bottom:0;z-index:2;border:3px solid #4a90e2;pointer-events:none}.rg-uploader .queues>.style-album li.complete figure.col{text-indent:-9999px}.rg-uploader .queues>.style-album li.loading figure.col{background:#eee}.rg-uploader .queues>.style-album li.loading figure.col p{margin:0;position:relative;height:100%;background:#4a90e2}.rg-uploader .queues>.style-album li.loading figure.col p span{position:absolute;display:block;right:8px;top:50%;margin-top:-6px;font-size:11px;color:#fff;line-height:normal}.rg-uploader .queues>.style-album li.loading .bar,.rg-uploader .queues>.style-album li.loading figure.col:after{display:none}.rg-uploader .queues>.style-album li.loading.ready .bd{color:#888}.rg-uploader .queues>.style-album li.error figure.col{background:#ce3e3e}@media(min-width:1440px){.rg-uploader .queues.is-large-size>.style-album>li{width:16.6666%}}@media(min-width:1920px){.rg-uploader .queues.is-large-size>.style-album>li{width:12.5%}}@media(min-width:2400px){.rg-uploader .queues.is-large-size>.style-album>li{width:10%}}.rg-uploader .rg-uploader-body>.preview{flex:none}.rg-uploader .preview{flex:none;width:150px;height:100%;overflow:hidden}.rg-uploader .preview.hide{display:none}.rg-uploader .preview figure{height:100%;margin:0;background:no-repeat 50% 50%;background-size:cover;text-indent:-9999px}.rg-uploader .preview .not-image{position:relative;background:#7e7f80!important}.rg-uploader .preview .not-image:after{content:"not image";position:absolute;top:50%;left:0;right:0;margin-top:-7px;text-indent:0;font-family:Arial,sans-serif;color:#ccc;font-size:11px;text-align:center}@media(max-width:640px){.rg-uploader .preview{display:none}}.rg-uploader>footer>div{width:140px;padding:0 10px 0 0}.rg-uploader>footer>div>p{margin:0;font-family:Arial,sans-serif;text-align:right;font-size:12px;color:#222}.rg-uploader>footer>div>p em{font-style:normal}.rg-uploader>header nav{position:absolute;top:50%;right:10px;height:28px;margin-top:-14px;border:1px solid #4a90e2;border-radius:3px;font-size:0;overflow:hidden}.rg-uploader>header nav button{display:inline-block;height:100%;margin:0;padding:0 7px;background:none;cursor:pointer;border:none;border-left:1px solid #4a90e2}.rg-uploader>header nav button:first-child{border-left:none}.rg-uploader>header nav button i{font-size:20px;line-height:22px;color:#4a90e2}.rg-uploader>header nav button.on{background:#4a90e2;cursor:default;outline:0}.rg-uploader>header nav button.on i{color:#fff}@media(max-width:640px){.rg-uploader>header nav{position:static;margin:8px 0 0;height:34px;display:-webkit-flex;display:flex}.rg-uploader>header nav button{-webkit-flex:1;flex:1;padding:0}.rg-uploader>header nav button i{font-size:20px;line-height:22px}}.rg-uploader .queues.drop-mode:after{content:"";position:absolute;display:block;left:0;right:0;top:0;bottom:0;border:2px dashed #4a90e2;pointer-events:none}.rg-plugin-thumbnail{position:fixed;left:0;right:0;top:0;bottom:0;z-index:99999;display:none}.rg-plugin-thumbnail,.rg-plugin-thumbnail *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.rg-plugin-thumbnail.show{display:block}.rg-plugin-thumbnail>.bg{position:absolute;left:0;right:0;top:0;bottom:0;background:rgba(0,0,0,.8)}.rg-plugin-thumbnail>.wrap{position:absolute;left:50%;top:50%;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.25)}.rg-plugin-thumbnail>.wrap .img-wrap{padding-bottom:60px;height:100%}.rg-plugin-thumbnail>.wrap .img-wrap figure{margin:0;height:100%;background:#333}.rg-plugin-thumbnail>.wrap .img-wrap .croppie-container{position:relative;padding:0}.rg-plugin-thumbnail>.wrap .img-wrap .cr-slider-wrap{position:absolute;left:0;right:0;bottom:20px;z-index:2}.rg-plugin-thumbnail>.wrap .body{position:absolute;left:0;right:0;bottom:0;height:60px;background:#fff;display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center}.rg-plugin-thumbnail>.wrap .body .meta{-webkit-flex:1;flex:1;padding-left:10px;padding-right:20px}.rg-plugin-thumbnail>.wrap .body .meta p{margin:0;font-size:13px;color:#222}.rg-plugin-thumbnail>.wrap .body nav{padding-right:10px;text-align:right}.rg-plugin-thumbnail>.wrap .body nav button{margin:0;padding:5px;border:none;background:none;cursor:pointer}.rg-plugin-thumbnail>.wrap .body nav button i{vertical-align:top;color:#57595b}.rg-plugin-thumbnail>.wrap .body nav button+button{margin-left:8px}.rg-plugin-changeQueue li{cursor:move}.rg-plugin-changeQueue .sortable-ghost{opacity:.2}.rg-uploader .bracket:before{content:"("}.rg-uploader .bracket:after{content:")"}.rg-uploader .bracket.large:before{content:"["}.rg-uploader .bracket.large:after{content:"]"}.rg-uploader,.rg-uploader-popup,.rg-uploader-popup body{overflow:hidden}.rg-uploader{border:1px solid #ccc;border-radius:4px;box-sizing:border-box}.rg-uploader em{font-style:normal}.rg-uploader .queues{position:relative;height:100%;box-sizing:border-box}.rg-uploader .queues>ul{margin:0;padding:0;height:100%;list-style:none;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box}.rg-uploader .queues nav{font-size:0;box-sizing:border-box}.rg-uploader .queues nav button{margin:0;padding:4px;border:none;font-size:0;background:none;cursor:pointer}.rg-uploader .queues nav button.on i{color:#4a90e2}.rg-uploader .queues nav button.disabled{cursor:not-allowed}.rg-uploader .queues nav button.disabled i{color:#b2b5b9}.rg-uploader .queues nav i{font-size:16px;color:#57595b}@media(max-width:640px){.rg-uploader .queues{overflow:auto}}.rg-uploader-header{position:relative;padding:10px 130px 10px 10px;border-bottom:2px solid #ccc}.rg-uploader-header h1{margin:0;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:18px;color:#222;font-weight:400}.rg-uploader-header p{margin:3px 0 0;font-size:12px;color:#777}@media(max-width:640px){.rg-uploader-header{padding:10px}}.rg-uploader-footer{display:flex;display:-webkit-flex;align-items:center;border-top:2px solid #ccc;overflow:hidden}.rg-uploader-footer nav{flex:1;font-size:0}.rg-uploader-footer nav>*{display:inline-block;margin:0;padding:10px;vertical-align:middle;font-size:0;border:none;border-right:1px solid #ddd;overflow:hidden;outline:none;cursor:pointer;background:none}.rg-uploader-footer nav>:hover{background:#f4f4f4}.rg-uploader-footer nav>*>*{display:inline-block;vertical-align:middle}.rg-uploader-footer nav>:first-child{border-bottom-left-radius:4px}.rg-uploader-footer nav>.disabled i,.rg-uploader-footer nav>.disabled span{color:#999}.rg-uploader-footer nav span{margin:0;font-family:Arial,sans-serif;font-size:12px;color:#222}.rg-uploader-footer nav i{margin:0 3px 0 0;font-size:16px;color:#222}.rg-uploader-footer nav .add-file input[type=file]{position:absolute;visibility:hidden}@media(max-width:640px){.rg-uploader-footer nav span{display:none}.rg-uploader-footer nav i{margin:0;font-size:18px}}.rg-uploader-body{display:flex;min-height:150px}.rg-uploader-body>.queues{flex:1}@media(max-width:640px){.rg-uploader-body{height:auto!important;min-height:100px}} -------------------------------------------------------------------------------- /docs/dist/rg-uploader.plugins.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.plugins=t():(e.RG_Uploader=e.RG_Uploader||{},e.RG_Uploader.plugins=t())}(window,(function(){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="./",n(n.s=0)}([function(e,t,n){"use strict";function i(e,t){this.name="Change Queue",this.sortable=null;var n=t||n||jQuery,i=this,o=null;function r(){for(var e=[],t=[],r=o.queue.$queue.children("li").map((function(){return o.queue.findItem(n(this).data("id"))})),s=0;s'+t.iconName+"");!function(e){e.on("click",(function(e){if(n(this).hasClass("on"))return!1;o.queue.changeStyle(n(this).data("style"))}))}(r),i.$nav.append(r)})),i.$buttons=i.$nav.children("button")}function s(){i.$buttons.removeClass("on").filter(".style-"+name).addClass("on")}n&&(this.name="Change Queue Style",this.buttonsParams=[{name:"list",title:"list style",iconName:"menu",default:!0},{name:"web",title:"web style",iconName:"view_list",default:!1},{name:"album",title:"album style",iconName:"view_module",default:!1}],this.$nav=null,this.$buttons=null,this.init=function(t){o=t;var a=e?n(e):o.$container.children("header");i.$nav=n(''),a.append(i.$nav),r(),s(o.queue.style)},this.eventListener=function(e,t){switch(e){case"queue.changeStyle":s(t.style)}})};var s=function(e,t){this.name="Drag And Drop",this.areaElements=[];var n=t||n||jQuery,i=this,o=null;if(n){var r=function(e){if(o.uploader.uploading)return alert(o.lang("error_add_upload")),!1;o.uploader.start(e||[])};this.init=function(t){o=t;var s=e?n(e):null;if(this.areaElements.push(o.queue.$queue.parent().get(0)),s&&s.each((function(){i.areaElements.push(this)})),this.areaElements.length){var a=function(e){if(!(window.File&&window.FileList&&window.FileReader&&window.Blob))return!1;if(!e.length)return!1;for(var t=n.Deferred(),i=!1,o=function(e){if(e.stopPropagation(),e.preventDefault(),"dragover"===e.type){if(i)return!1;i=!0,"all"===e.dataTransfer.effectAllowed&&n(e.currentTarget).addClass("drop-mode"),e.dataTransfer.dropEffect="copy"}else i=!1,n(e.currentTarget).removeClass("drop-mode");return!1},r=function(e){e.stopPropagation(),e.preventDefault(),o(e);var n=e.dataTransfer?e.dataTransfer.files:null;return n&&n.length&&t.notify(n),!1},s=0;s
    '),n.$preview.width(o),i.$container.find("[data-comp=queue]").prepend(n.$preview),r()},this.eventListener=function(e,t){switch(e){case"queue.selectQueue":var o=t.$selectElement.data("id"),s=i.queue.findItem(o),a=i.queue.items.files[s],l=t.$selectElement.hasClass("selected")&&a.type&&"image"===a.type.split("/")[0]?a.fullSrc:null;r(l);break;case"queue.changeStyle":"list"===t.style?n.$preview.removeClass("hide"):n.$preview.addClass("hide")}}}};var l=function(e,t){this.name="Size info",this.size={current:0,total:0};var n=t||n||jQuery,i=this,o=null,r=null,s=null,a=null;function l(e){if(0===e)return"0";var t=parseInt(Math.floor(Math.log(e)/Math.log(1024)));return Math.round(e/Math.pow(1024,t),2)+""+["Bytes","KB","MB","GB","TB"][t]}n&&(this.init=function(t){o=t,(r=e?n(e):o.$container.find(".size-info")).length||o.plugin.error(name),r.append('

    Size : /

    '),s=r.find("[data-text=currentSize]"),a=r.find("[data-text=totalSize]"),this.size.total=o.options.limitSizeTotal,this.update()},this.update=function(){s.text(l(this.size.current)),a.text(l(this.size.total))},this.eventListener=function(e,t){switch(e){case"queue.uploadComplete":i.size.current+=t.file.size,i.update(o.queue.getSize());break;case"queue.removeQueue":i.size.current=o.queue.getSize(),i.update()}})};function u(e,t){this.name="Make thumbnail";var n=t||n||jQuery,i=this,o=null,r=!1,s=null;function a(e){if(!e&&r)return!1;r=!0,i.$el.wrap.width("100%").height("100%").css({marginLeft:0,marginTop:0,left:0,top:0}),i.croppie&&p(!0)}function l(e){if(!e&&!r)return!1;r=!1,i.$el.wrap.width(i.options.width).height(i.options.height).css({marginLeft:0-.5*i.options.width+"px",marginTop:0-.5*i.options.height+"px",left:"50%",top:"50%"}),i.croppie&&p(!0)}function u(e){var t=n(window);if(!i.croppie)return clearTimeout(s),!1;s&&clearTimeout(s),s=setTimeout((function(){if(r&&t.width()<640)return a(!0),!1;t.width()<640?a(!0):t.width()>640&&l(!0)}),300)}function p(e){var t=e?i.croppie.get():{};c(),i.options.croppie.boundary={width:i.options.mobileSize>n(window).width()?n(window).width():i.options.width,height:(i.options.mobileSize>n(window).width()?n(window).height():i.options.height)-60},i.croppie=new Croppie(i.$el.figure.get(0),i.options.croppie),e&&i.rebind({src:i.file.fullSrc,points:t.points},(function(){i.croppie.setZoom(t.zoom)}))}function c(){i.croppie&&(i.croppie.destroy(),i.croppie=null)}function d(e){i.croppie.result(i.options.output).then((function(e){i.options.uploadScript?n.post(i.options.uploadScript,{name:i.file.name,image:e,id:f()},(function(e){try{e=JSON.parse(e)}catch(e){return alert("parse error"),!1}if("error"===e.state)return alert(e.response.message),!1;i.options.doneCallback&&i.options.doneCallback(e.response,o,i.file)})):i.options.doneCallback&&i.options.doneCallback({id:f(),name:"thumb-"+i.file.name,src:e,type:"image/"+i.options.output.format,size:0},o,i.file),i.close()}),(function(e){console.log("ERROR",e)}))}function f(e){e=e||10;for(var t,n,i=(+new Date).toString().split("").reverse(),o="",r=0;r

    message

    '),i.$el.wrap=i.$el.con.children(".wrap"),i.$el.bg=i.$el.con.children(".bg"),i.$el.figure=i.$el.con.find(".img-wrap figure"),i.$el.meta=i.$el.con.find(".meta > p"),i.$el.btn_done=i.$el.con.find(".btn-done"),i.$el.btn_close=i.$el.con.find(".btn-close"),n("body").append(i.$el.con),i.$el.bg.on("click",(function(){i.close()})),i.$el.btn_close.on("click",(function(){i.close()})),i.$el.btn_done.on("click",d),n(window).on("resize.rgUploader",u)},this.open=function(e,t){t=t||{},this.$el.con.addClass("show"),n("html").addClass("rg-uploader-popup"),this.file=e,n(window).width() 2 | 3 | 4 | redgoose upload component 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 |
    16 |
    redgoose file uploader
    17 |

    redgoose file uploader

    18 | 24 |
    25 | 26 |
    27 |
    28 |

    textarea & image elements

    29 |

    30 | textarea나 다른 영역이나 에디터에다 파일 정보를 삽입할 수 있습니다.
    31 | 이 예제는 파일 목록의 `본문삽입`버튼을 누르면 textarea에는 파일이름, 이미지 영역에는 이미지태그가 들어갑니다. (이미지영역의 이미지는 css로 사이즈를 작게 조절하도록 하겠습니다.) 32 |

    33 |
    34 |
    35 |

    textarea

    36 | 37 |

    image area

    38 |
    39 |
    40 |
    41 |
    42 |
    43 |
    44 |

    File upload

    45 |

    아래 업로드 버튼을 눌러서 파일을 추가하거나 이 영역에 파일을 드래그하여 추가합니다.

    46 |
    47 |
    48 |
    49 |
      50 |
      51 |
      52 | 66 |
      67 |
      68 |
      69 |
      var plug_editor = new RG_Uploader.core(document.getElementById('comp_editor'), {
       70 | 	queue : {
       71 | 		datas : 'data.json',
       72 | 		buttons : [
       73 | 			{
       74 | 				name : 'open file',
       75 | 				iconName : 'open_in_new',
       76 | 				className : 'btn-open-file',
       77 | 				action : function(app, file) {
       78 | 					console.log('action : open file');
       79 | 					window.open(file.src);
       80 | 				}
       81 | 			},
       82 | 			{
       83 | 				name : 'insert editor',
       84 | 				iconName : 'center_focus_strong',
       85 | 				className : 'btn-insert-editor',
       86 | 				action : function(app, file) {
       87 | 					var $textarea = $('#target_textarea');
       88 | 					var $figure = $('#target_image');
       89 | 					var src = ((/^data:/g).test(file.src)) ? file.name : file.src;
       90 | 					$textarea.val($textarea.val() + ' [' + src + ']');
       91 | 					$figure.append('<img src="' + file.src + '" alt="' + file.name + '">');
       92 | 				}
       93 | 			},
       94 | 			{
       95 | 				name : 'remove queue',
       96 | 				iconName : 'close',
       97 | 				className : 'btn-remove-queue',
       98 | 				action : function(app, file) {
       99 | 					app.queue.removeQueue(file.id, false, true);
      100 | 				}
      101 | 			}
      102 | 		]
      103 | 	},
      104 | 	plugin : [
      105 | 		{ name : 'preview', obj : new RG_Uploader.plugins.Preview() },
      106 | 		{ name : 'dnd', obj : new RG_Uploader.plugins.DragAndDrop() },
      107 | 		{ name : 'sizeInfo', obj : new RG_Uploader.plugins.SizeInfo() },
      108 | 		{ name : 'changeQueueStyle', obj : new RG_Uploader.plugins.ChangeQueueStyle() }
      109 | 	]
      110 | });
      111 |
      112 |
      113 |
      114 |
      115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | redgoose upload component 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
      15 |
      16 |
      redgoose file uploader
      17 |

      redgoose file uploader

      18 | 24 |
      25 | 26 |
      27 |
      28 |

      Basic

      29 |

      아무런 옵션이 옵션이 없는 기본적인 형태의 컴포넌트입니다.

      30 |
      31 |
      32 |
      33 |

      File upload

      34 |

      아래 업로드 버튼을 눌러서 파일을 추가합니다.

      35 |
      36 |
      37 |
      38 |
        39 |
        40 |
        41 | 54 |
        55 |
        56 |
        57 |
        var basic = new RG_Uploader.core(document.getElementById('element_id'));
        58 |
        59 |
        60 | 61 |
        62 |

        External form

        63 |

        컴포넌트 외부의 업로드 폼을 지정하여 업로드할 수 있습니다. 그리고 파일을 선택하면 바로 업로드합니다.

        64 |
        65 |
        66 |
        67 |
        68 |
          69 |
          70 |
          71 |
          72 |

          73 | 74 |

          75 |
          76 |

          input form 엘리먼트를 원하는 위치에 두고 셀렉터를 지정해둡니다.

          77 |
          78 |
          <input type="file" id="ext_input" multiple/>
          79 |
          80 |

          업로더 인스턴스 객체를 만들면서 옵션으로 input form을 셀렉터로 넣어줍니다.

          81 |
          82 |
          var extInputForm = new RG_Uploader.core(document.getElementById('comp_ext_form'), {
           83 | 	autoUpload : true,
           84 | 	externalFileForm : document.getElementById('ext_input')
           85 | });
          86 |
          87 |
          88 |
          89 |
          90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/style.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | redgoose upload component 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
          15 |
          16 |
          redgoose file uploader
          17 |

          redgoose file uploader

          18 | 24 |
          25 | 26 |
          27 |

          28 | Queue 스타일에 대한 데모입니다.
          29 | 기본적으로 `list, web, album` 스타일이 구현되어있고, 새로운 스타일을 추가하거나 css를 수정하거나 속성을 덮어씌워서 스타일을 커스터마이즈 할 수 있습니다. 30 |

          31 |

          32 | 편의를 위하여 업로드된 파일의 정보 데이터를 불러왔습니다. 33 |

          34 | 35 |
          36 |

          style - list(default)

          37 |

          목록형태의 스타일입니다.

          38 |
          39 | 60 |
          61 |
          62 |
          var style_list = new RG_Uploader.core(document.getElementById('element_id'), {
           63 | 	queue : {
           64 | 		style : 'list',
           65 | 		datas : 'data.json'
           66 | 	}
           67 | });
          68 |
          69 |
          70 | 71 |
          72 |

          style - web

          73 |

          작은 썸네일 이미지와 파일정보가 나란히 표시됩니다.

          74 |
          75 | 96 |
          97 |
          98 |
          var style_web = new RG_Uploader.core(document.getElementById('element_id'), {
           99 | 	queue : {
          100 | 		style : 'web',
          101 | 		height: 250,
          102 | 		datas : 'data.json'
          103 | 	}
          104 | });
          105 |
          106 |
          107 | 108 |
          109 |

          style - album

          110 |

          사진과 정보가 위아래로 정렬된 형태로 표현됩니다.

          111 |
          112 | 133 |
          134 | 135 |
          136 |
          var style_album = new RG_Uploader.core(document.getElementById('element_id'), {
          137 | 	queue : {
          138 | 		style : 'album',
          139 | 		height: 250,
          140 | 		datas : 'data.json'
          141 | 	}
          142 | });
          143 |
          144 |
          145 |
          146 |
          147 | 148 | 149 | 150 | 151 | 152 | 153 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rg-uploader", 3 | "version": "1.4.3", 4 | "main": "./src/rg-uploader.js", 5 | "scripts": { 6 | "dev": "./node_modules/.bin/webpack-dev-server --mode development --port 4000", 7 | "prebuild": "rm -rf dist && rm -rf docs/dist", 8 | "build": "./node_modules/.bin/webpack", 9 | "postbuild": "cp ./node_modules/jquery/dist/jquery.min.js ./dist && rm -rf docs/dist && cp -r dist ./docs/", 10 | "version-patch": "npm version patch", 11 | "docs": "./node_modules/.bin/http-server ./docs -p 5000 -s" 12 | }, 13 | "description": "File upload component", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/redgoose-dev/rg-uploader.git" 17 | }, 18 | "keywords": [ 19 | "uploader", 20 | "upload", 21 | "component", 22 | "file-uploader" 23 | ], 24 | "author": "redgoose ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/redgoose-dev/rg-uploader/issues" 28 | }, 29 | "homepage": "https://github.com/redgoose-dev/rg-uploader#readme", 30 | "dependencies": { 31 | "croppie": "2.6.4", 32 | "jquery": "3.4.1", 33 | "sortablejs": "1.10.2" 34 | }, 35 | "devDependencies": { 36 | "css-loader": "3.4.2", 37 | "detect-file-type": "0.2.8", 38 | "express": "4.17.1", 39 | "file-loader": "6.0.0", 40 | "html-loader": "1.0.0", 41 | "html-webpack-plugin": "^4.0.2", 42 | "http-server": "0.12.1", 43 | "mini-css-extract-plugin": "0.9.0", 44 | "multer": "1.4.2", 45 | "optimize-css-assets-webpack-plugin": "5.0.3", 46 | "query-string": "6.11.1", 47 | "sass": "1.26.3", 48 | "sass-loader": "8.0.2", 49 | "style-loader": "1.1.3", 50 | "terser-webpack-plugin": "2.3.5", 51 | "webpack": "4.42.1", 52 | "webpack-cli": "3.3.11", 53 | "webpack-dev-server": "3.10.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/component/Plugin.js: -------------------------------------------------------------------------------- 1 | export default class Plugin { 2 | 3 | constructor(parent) 4 | { 5 | this.parent = parent; 6 | this.names = []; 7 | this.child = {}; 8 | } 9 | 10 | /** 11 | * event listener 12 | * 13 | * queue.changeStyle 14 | * queue.selectQueue 15 | * queue.addProgress 16 | * queue.updateProgress 17 | * queue.uploadComplete 18 | * queue.removeQueue 19 | * 20 | * @Param {String} type 21 | * @Param {Any} value 22 | */ 23 | eventListener(type, value) 24 | { 25 | this.names.forEach((name) => { 26 | let evt = this.child[name].eventListener; 27 | if (!evt || !(typeof evt === 'function')) return; 28 | evt(type, value); 29 | }); 30 | } 31 | 32 | /** 33 | * error plugin 34 | * 플러그인 작동에 문제가 생겨 호출되어 객체를 삭제한다. 35 | * 36 | * @Param {String} childName error plugin name 37 | */ 38 | error(childName) 39 | { 40 | this.names.splice(this.names.indexOf(childName), 1); 41 | delete this.child[childName]; 42 | } 43 | 44 | /** 45 | * init 46 | */ 47 | init() 48 | { 49 | // init plugins 50 | let items = this.parent.options.plugin; 51 | if (items && items.length) 52 | { 53 | items.forEach((item) => { 54 | if (!item.name) return; 55 | if (!item.obj || !(typeof item.obj === 'object')) return; 56 | if (!item.obj.init || !(typeof item.obj.init === 'function')) return; 57 | 58 | this.names.push(item.name); 59 | this.child[item.name] = item.obj; 60 | 61 | // play init() 62 | this.child[item.name].init(this.parent); 63 | }); 64 | } 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /src/component/Queue.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import * as lib from './lib'; 3 | 4 | const selectedClassName = 'selected'; 5 | 6 | export default class Queue { 7 | 8 | constructor(parent) 9 | { 10 | this.name = 'Queue'; 11 | this.parent = parent; 12 | this.options = parent.options.queue; 13 | this.items = { ids: [], files: [] }; 14 | this.style = 'list'; 15 | this.$wrap = lib.util.findDOM(parent.$container, 'element', 'queue'); 16 | this.$queue = this.$wrap.children('ul'); 17 | this.keyboardEvent = new lib.keyboardEvent( 18 | parent.options.eventPrefixName, 19 | [ 20 | { key : 'ctrl', code : 17 }, 21 | { key : 'cmd', code : 91 } 22 | ] 23 | ); 24 | } 25 | 26 | /** 27 | * init 28 | */ 29 | init() 30 | { 31 | // set queue height 32 | if (this.options.height) 33 | { 34 | lib.util.findDOM(this.parent.$container, 'comp', 'queue').height(this.options.height); 35 | } 36 | 37 | // set style 38 | this.changeStyle(this.options.style); 39 | 40 | // set events 41 | // 빈 영역을 누르면 큐 선택이 해제됩니다. 42 | this.$wrap.on('click', () => this.toggleSelectAllQueues(false)); 43 | 44 | // import queue datas 45 | if (this.options.datas) 46 | { 47 | this.import(this.options.datas); 48 | } 49 | } 50 | 51 | /** 52 | * init select queue event 53 | * 54 | * @param {Object} $el 55 | */ 56 | initSelectQueueEvent($el) 57 | { 58 | // select queue event 59 | $el.on('click', (e) => { 60 | this.selectQueue($(e.currentTarget).data('id')); 61 | return false; 62 | }); 63 | } 64 | 65 | /** 66 | * create navigation buttons 67 | * 68 | * @param {Object} options 69 | * @param {Object} file 70 | */ 71 | createNavigationButtons(options, file) 72 | { 73 | if (!options || !options.length) return; 74 | 75 | let $buttons = []; 76 | options.forEach((item) => { 77 | if (!item.name || !item.iconName || !item.action) return; 78 | if (item.show && item.show(file) === false) return; 79 | 80 | let className = (item.className) ? `class="${item.className}"` : ''; 81 | let $item = $(``); 84 | 85 | $item.on('click', (e) => { 86 | item.action(this.parent, file); 87 | e.stopPropagation(); 88 | }); 89 | 90 | $buttons.push($item); 91 | }); 92 | 93 | return $buttons; 94 | } 95 | 96 | /** 97 | * find item 98 | * 99 | * @param {int} id 100 | * @return {int} 101 | */ 102 | findItem(id) 103 | { 104 | let n = this.items.ids.indexOf(Number(id)); 105 | return typeof n === 'number' ? n : undefined; 106 | } 107 | 108 | /** 109 | * change style 110 | * 111 | * @param {String} styleName 112 | */ 113 | changeStyle(styleName) 114 | { 115 | this.$queue.removeClass(`style-${this.style}`).addClass(`style-${styleName}`); 116 | this.style = styleName; 117 | 118 | // send event to plugin 119 | this.parent.eventReceiver('queue.changeStyle', { style : styleName }); 120 | } 121 | 122 | /** 123 | * import 124 | * 125 | * @param {Array|String} src 126 | */ 127 | import(src) 128 | { 129 | if (!src) return; 130 | 131 | if (typeof src === 'string') 132 | { 133 | $.get(src, (res) => { 134 | if (typeof res === 'string') 135 | { 136 | try { 137 | res = JSON.parse(res); 138 | } catch(e) { 139 | res = []; 140 | } 141 | } 142 | if (!(res && res instanceof Array)) 143 | { 144 | alert(lib.language('error_import')); 145 | return; 146 | } 147 | if (res.length) 148 | { 149 | res.forEach((item) => { 150 | this.addComplete(item); 151 | }); 152 | } 153 | }); 154 | } 155 | else if (src instanceof Array) 156 | { 157 | src.forEach((item) => { 158 | this.addComplete(item); 159 | }); 160 | } 161 | } 162 | 163 | /** 164 | * delete queue 165 | * 166 | * @param {Array} ids 167 | */ 168 | delete(ids) 169 | { 170 | if (!ids || !ids.length) return; 171 | 172 | ids.forEach((id) => { 173 | this.removeQueue(id, false, true); 174 | }); 175 | } 176 | 177 | /** 178 | * select queue 179 | * `id`값이 없으면 선택된 큐를 확인하고 선택을 토글링 합니다. 180 | * 181 | * @param {number} id 182 | */ 183 | selectQueue(id) 184 | { 185 | const $queues = this.$queue.children(); 186 | 187 | if (id) 188 | { 189 | let $el = this.selectQueueElement(id); 190 | if (this.keyboardEvent.isPressKeyCode) 191 | { 192 | $el.toggleClass(selectedClassName); 193 | } 194 | else 195 | { 196 | let isSelected = $el.hasClass(selectedClassName); 197 | let selectCount = $queues.filter(`.${selectedClassName}`).length; 198 | 199 | $queues.removeClass(selectedClassName); 200 | 201 | if (!isSelected || selectCount > 1) 202 | { 203 | $el.addClass(selectedClassName); 204 | } 205 | } 206 | // send event to plugin 207 | this.parent.eventReceiver('queue.selectQueue', { 208 | $selectElements: this.$queue.children(`.${selectedClassName}`), 209 | $selectElement: this.selectQueueElement(id), 210 | }); 211 | } 212 | else 213 | { 214 | this.toggleSelectAllQueues(!($queues.filter(`.${selectedClassName}`).length > 0)); 215 | } 216 | } 217 | 218 | /** 219 | * Toggle select all queues 220 | * 모든 큐들을 선택하거나 해제합니다. 221 | * 222 | * @param {Boolean} sw 223 | */ 224 | toggleSelectAllQueues(sw=undefined) 225 | { 226 | const $queues = this.$queue.children(); 227 | 228 | if (sw === undefined) 229 | { 230 | this.toggleSelectAllQueues(!($queues.filter(`.${selectedClassName}`).length > 0)); 231 | } 232 | else 233 | { 234 | if (sw) 235 | { 236 | $queues.addClass(selectedClassName); 237 | } 238 | else 239 | { 240 | $queues.removeClass(selectedClassName); 241 | } 242 | } 243 | 244 | // send event to plugin 245 | this.parent.eventReceiver('queue.selectQueue', { 246 | $selectElements: this.$queue.children(`.${selectedClassName}`), 247 | $selectElement: $queues.eq(0), 248 | }); 249 | } 250 | 251 | /** 252 | * select queue element 253 | * 254 | * @param {String|Number} id 255 | * @return {Object} 256 | */ 257 | selectQueueElement(id=null) 258 | { 259 | return this.$queue.children(`li[data-id=${id}]`); 260 | } 261 | 262 | /** 263 | * add queue 264 | */ 265 | add(file) 266 | { 267 | // set file values 268 | file.fullSrc = this.parent.options.srcPrefixName + file.src; 269 | 270 | this.items.ids.push(Number(file.id)); 271 | this.items.files.push(file); 272 | } 273 | 274 | /** 275 | * remove queue 276 | */ 277 | remove(id) 278 | { 279 | let n = this.findItem(id); 280 | this.items.ids.splice(n, 1); 281 | this.items.files.splice(n, 1); 282 | } 283 | 284 | /** 285 | * add progress queue 286 | * 287 | * @param {Object} file 288 | */ 289 | addProgress(file) 290 | { 291 | let $item = $(lib.template.loading); 292 | let $removeButton = lib.util.findDOM($item, 'element', 'removeQueue').children('button'); 293 | 294 | // add item in queue index 295 | this.add(file); 296 | 297 | // input meta 298 | $item.attr('data-id', file.id); 299 | lib.util.findDOM($item, 'text', 'filename').text(file.name); 300 | 301 | // reset percentage 302 | lib.util.findDOM($item, 'element', 'progress').width('0%').find('em').text('0'); 303 | 304 | // init remove queue event 305 | $removeButton.on('click', (e) => { 306 | const id = parseInt($(e.currentTarget).closest('li').data('id')); 307 | this.removeQueue(id, true, false); 308 | this.parent.uploader.readyItems.forEach((item, n) => { 309 | if (item.id === id) 310 | { 311 | this.parent.uploader.readyItems.splice(n, 1); 312 | return false; 313 | } 314 | }); 315 | }); 316 | 317 | // append element 318 | this.$queue.append($item); 319 | 320 | // send event to plugin 321 | this.parent.eventReceiver('queue.addProgress', { $el: $item, file: file }); 322 | } 323 | 324 | /** 325 | * add complete queue 326 | * 327 | * @param {Object} file 328 | * @param {Object} $beforeElement 329 | */ 330 | addComplete(file, $beforeElement=null) 331 | { 332 | let id = file.id; 333 | let $el = $(lib.template.complete); 334 | 335 | // set elements in queue 336 | let $previewImages = lib.util.findDOM($el, 'element', 'previewImage'); 337 | let $customButtons = lib.util.findDOM($el, 'element', 'customButtons'); 338 | let $fileType = lib.util.findDOM($el, 'text', 'filetype'); 339 | let $fileName = lib.util.findDOM($el, 'text', 'filename'); 340 | let $state = lib.util.findDOM($el, 'text', 'state'); 341 | let $fileSize = lib.util.findDOM($el, 'text', 'filesize'); 342 | 343 | // add queue index 344 | this.add(file); 345 | 346 | // set queue id 347 | $el.attr('data-id', id); 348 | 349 | // insert queue data 350 | $fileType.text(file.type ? file.type : `application/${file.name.split('.').pop()}`); 351 | $fileName.text(file.name); 352 | $state.text('uploaded'); 353 | $fileSize.text((file.size) ? lib.util.bytesToSize(file.size) : 'none'); 354 | $customButtons.html(''); 355 | 356 | // check image and assign preview background 357 | if (file.type && file.type.split('/')[0] === 'image') 358 | { 359 | $previewImages.css('background-image', `url(${file.fullSrc})`); 360 | } 361 | 362 | // set toggle select event 363 | this.initSelectQueueEvent($el); 364 | 365 | // create queue navigation buttons 366 | let $buttons = this.createNavigationButtons(this.options.buttons, file); 367 | if ($buttons && $buttons.length) 368 | { 369 | $customButtons.append($buttons); 370 | } 371 | 372 | // append queue 373 | if ($beforeElement && $beforeElement.length) 374 | { 375 | $beforeElement.after($el); 376 | } 377 | else 378 | { 379 | this.$queue.append($el); 380 | } 381 | 382 | // send event to plugin 383 | this.parent.eventReceiver('queue.uploadComplete', { 384 | $selectElement : $el, 385 | id : id, 386 | file : file 387 | }); 388 | } 389 | 390 | /** 391 | * add error queue 392 | * 393 | * @param {Object} file 394 | * @param {Object} $beforeElement 395 | */ 396 | addError(file, $beforeElement) 397 | { 398 | const id = file.id; 399 | const $el = $(lib.template.error); 400 | 401 | const $fileType = lib.util.findDOM($el, 'text', 'filetype'); 402 | const $fileName = lib.util.findDOM($el, 'text', 'filename'); 403 | const $state = lib.util.findDOM($el, 'text', 'state'); 404 | 405 | // add queue index 406 | this.add(file); 407 | 408 | // set queue id 409 | $el.attr('data-id', id); 410 | 411 | $fileType.text(file.type); 412 | $fileName.text(file.name); 413 | $state.text(file.message); 414 | 415 | // append error queue 416 | if ($beforeElement && $beforeElement.length) 417 | { 418 | $beforeElement.after($el); 419 | } 420 | else 421 | { 422 | this.$queue.append($el); 423 | } 424 | 425 | setTimeout(() => { 426 | this.removeQueue(id, false, false); 427 | }, 3000); 428 | } 429 | 430 | /** 431 | * remove queue 432 | * 433 | * @param {Number} id 434 | * @param {Boolean} isLoadingQueue 435 | * @param {Boolean} useScript 436 | */ 437 | removeQueue(id, isLoadingQueue, useScript=false) 438 | { 439 | const self = this; 440 | const parent = this.parent; 441 | const { options } = parent; 442 | 443 | function removeElement(id) 444 | { 445 | self.selectQueueElement(id).fadeOut(400, function() { 446 | $(this).remove(); 447 | self.remove(id); 448 | self.parent.eventReceiver('queue.removeQueue', {}); 449 | }); 450 | } 451 | 452 | if (isLoadingQueue) 453 | { 454 | this.selectQueueElement(id).filter('.loading').remove(); 455 | 456 | // send event to plugin 457 | this.parent.eventReceiver('queue.removeQueue', {}); 458 | } 459 | else 460 | { 461 | let file = this.items.files[this.findItem(id)]; 462 | 463 | if (file < 0) 464 | { 465 | console.error('Not found queue id'); 466 | return false; 467 | } 468 | 469 | if (useScript && options.removeScript && !file.isLocalFile) 470 | { 471 | // remove parameters filter 472 | if (lib.util.checkFunction(options.removeParamsFilter)) 473 | { 474 | file = options.removeParamsFilter(file, this.parent); 475 | } 476 | 477 | // play remove file script 478 | $.ajax({ 479 | url: lib.util.getFunctionReturn(options.removeScriptFunc, options.removeScript, file), 480 | type: 'post', 481 | data: file, 482 | headers: options.removeHeaders ? options.removeHeaders : ((options.uploadHeaders) ? options.uploadHeaders : null), 483 | success: function(res, state) { 484 | if (typeof res === 'string') 485 | { 486 | try { 487 | res = JSON.parse(res); 488 | } catch(e) { 489 | res = { state : 'error', response : res }; 490 | } 491 | } 492 | 493 | // filtering response 494 | if (lib.util.checkFunction(options.removeDataFilter)) 495 | { 496 | res = options.removeDataFilter(res, parent); 497 | } 498 | 499 | // act 500 | if (res && res.state && res.state === 'success') 501 | { 502 | removeElement(id); 503 | } 504 | else 505 | { 506 | alert(lib.language('error_remove_error')); 507 | return false; 508 | } 509 | } 510 | }); 511 | } 512 | else 513 | { 514 | removeElement(id); 515 | } 516 | } 517 | }; 518 | 519 | /** 520 | * update queue 521 | * 522 | * @param {Object} res 523 | */ 524 | updateProgress(res) 525 | { 526 | let $el = this.$queue.children(`li[data-id=${res.id}]`); 527 | let $progress = lib.util.findDOM($el, 'element', 'progress'); 528 | 529 | // check 530 | if (!(res && res.data)) return; 531 | 532 | // get percent 533 | let percent = parseInt((res.data.loaded / res.data.total) * 100); 534 | $progress.width(`${percent}%`).find('em').text(percent); 535 | 536 | this.parent.eventReceiver('queue.updateProgress', { 537 | $selectElement : $el, 538 | id: res.id, 539 | loaded : res.data.loaded, 540 | total : res.data.total 541 | }); 542 | }; 543 | 544 | /** 545 | * upload result 546 | * 547 | * @param {String} state (success|error) 548 | * @param {Object} file 549 | */ 550 | uploadResult(state, file) 551 | { 552 | if (!state) return false; 553 | let $loading = this.selectQueueElement(file.id); 554 | this.remove(file.id); 555 | switch(state) { 556 | case 'success': 557 | this.addComplete(file, $loading); 558 | break; 559 | case 'error': 560 | this.addError(file, $loading); 561 | break; 562 | } 563 | this.removeQueue(file.id, true); 564 | }; 565 | 566 | /** 567 | * get files size (total) 568 | * 569 | * @return {int} 570 | */ 571 | getSize() 572 | { 573 | let size = 0; 574 | this.items.files.forEach((item) => { 575 | size += item.size; 576 | }); 577 | return size; 578 | }; 579 | 580 | /** 581 | * change id 582 | * 특정 id가 있는 큐를 찾아서 새로운 id로 바꿉니다. 583 | * 584 | * @param {number} id 585 | * @param {number} newId 586 | */ 587 | changeId(id, newId) 588 | { 589 | if (newId === undefined) return; 590 | const { files, ids } = this.items; 591 | let index = this.findItem(id); 592 | if (index !== undefined) 593 | { 594 | let queue = this.selectQueueElement(id)[0]; 595 | // edit id from queue element 596 | if (queue) queue.setAttribute('data-id', newId); 597 | // edit id from uploader object 598 | ids[index] = newId; 599 | files[index].id = newId; 600 | } 601 | }; 602 | 603 | } 604 | -------------------------------------------------------------------------------- /src/component/Uploader.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import * as lib from './lib'; 3 | 4 | export default class Uploader { 5 | 6 | constructor(parent) 7 | { 8 | this.parent = parent; 9 | this.name = 'Uploader'; 10 | this.queue = parent.queue; 11 | this.$uploadElement = null; 12 | this.readyItems = []; 13 | this.uploading = false; 14 | 15 | // set start upload element 16 | const $startUpload = lib.util.findDOM(parent.$container, 'element', 'startUpload'); 17 | 18 | // set upload element 19 | this.$uploadElement = lib.util.findDOM(parent.$container, 'element', 'addFiles'); 20 | this.addUploadElements($(parent.options.externalFileForm)); 21 | 22 | if (!(this.$uploadElement && this.$uploadElement.length)) return; 23 | 24 | // init change event 25 | this.$uploadElement.each((k, o) => { 26 | $(o).on('change', () => { 27 | // check auto upload 28 | this.pushReady(); 29 | 30 | // start upload 31 | if (parent.options.autoUpload) 32 | { 33 | this.start(); 34 | } 35 | }); 36 | }); 37 | 38 | // init start upload button 39 | if ($startUpload && $startUpload.length) 40 | { 41 | $startUpload.on('click', () => { 42 | this.start(); 43 | return false; 44 | }); 45 | } 46 | } 47 | 48 | /** 49 | * get total ready items size 50 | * 51 | * @param {Array} items 52 | * @return {int} 53 | */ 54 | static getTotalReadySize(items) 55 | { 56 | let size = 0; 57 | for (let i=0; i { 74 | for (let i=0; i limitCount) 109 | { 110 | alert(lib.language('error_upload_limit', [options.queue.limit])); 111 | return false; 112 | } 113 | 114 | // check total upload size 115 | let size = this.parent.queue.getSize() + this.constructor.getTotalReadySize(this.readyItems) + this.constructor.getTotalReadySize(files); 116 | if (options.limitSizeTotal < size) 117 | { 118 | alert(lib.language('error_limit_size')); 119 | return false; 120 | } 121 | 122 | // check items and add items ready for upload 123 | for (let i=0; i options.limitSize) 134 | { 135 | actError('filesize', lib.language('error_limit_size2')); 136 | continue; 137 | } 138 | 139 | // set unique id 140 | files[i].id = lib.util.getUniqueNumber(); 141 | 142 | // push upload item 143 | this.readyItems.push(files[i]); 144 | 145 | // push new ready items 146 | newReadyItems.push(files[i]); 147 | } 148 | 149 | newReadyItems.forEach((item) => { 150 | this.parent.queue.addProgress(item); 151 | }); 152 | }; 153 | 154 | /** 155 | * push ready queue 156 | */ 157 | pushReady() 158 | { 159 | let items = this.constructor.mergeFileList(this.$uploadElement); 160 | 161 | // check items 162 | if (!items.length) 163 | { 164 | alert(lib.language('error_not_upload_file')); 165 | return false; 166 | } 167 | 168 | // push upload items 169 | this.pushReadyUploadFiles(items); 170 | 171 | // reset form 172 | this.resetEvent(this.$uploadElement); 173 | }; 174 | 175 | /** 176 | * start upload 177 | * 178 | * @param {Array} files 179 | */ 180 | start(files=[]) 181 | { 182 | // push parameter files 183 | if (files && files.length) 184 | { 185 | this.pushReadyUploadFiles(files); 186 | } 187 | 188 | if (this.uploading) 189 | { 190 | alert(lib.language('error_now_uploading')); 191 | } 192 | else 193 | { 194 | this.play(); 195 | } 196 | }; 197 | 198 | /** 199 | * play upload 200 | */ 201 | play() 202 | { 203 | const { options } = this.parent; 204 | 205 | if (!this.readyItems.length) return; 206 | 207 | this.uploading = true; 208 | 209 | // change ready to loading 210 | let $el = this.parent.queue.selectQueueElement(this.readyItems[0].id); 211 | $el.removeClass('ready'); 212 | lib.util.findDOM($el, 'text', 'state').text('loading...'); 213 | lib.util.findDOM($el, 'element', 'removeQueue').remove(); 214 | 215 | // act upload 216 | let userParams = (lib.util.checkFunction(options.uploadParamsFilter)) && options.uploadParamsFilter(this.readyItems[0], this.parent); 217 | 218 | let upload = lib.fileUpload( 219 | lib.util.getFunctionReturn(options.uploadScriptFunc, options.uploadScript), 220 | this.readyItems[0], 221 | userParams, 222 | options.uploadHeaders, 223 | (src) => { 224 | return (lib.util.checkFunction(options.uploadDataFilter)) ? options.uploadDataFilter(src, this.parent) : src; 225 | }, 226 | ); 227 | 228 | // callback upload event 229 | upload 230 | .done((res, file) => { 231 | this.uploadComplete('success', res, file); 232 | }) 233 | .progress((res, file) => { 234 | this.uploadProgress(res, file); 235 | }) 236 | .fail((message, file) => { 237 | this.uploadComplete('error', message, file); 238 | }); 239 | }; 240 | 241 | /** 242 | * upload progress event 243 | * 244 | * @param {Object} res 245 | * @param {File} file 246 | */ 247 | uploadProgress(res, file) 248 | { 249 | this.parent.queue.updateProgress({ 250 | id : file.id, 251 | data : res 252 | }); 253 | if (this.parent.options.uploadProgress) 254 | { 255 | this.parent.options.uploadProgress(res, file, this.parent); 256 | } 257 | }; 258 | 259 | /** 260 | * upload complete event 261 | * 262 | * @param {String} state (success|error) 263 | * @param {Object} res 264 | * @param {Object} file 265 | */ 266 | uploadComplete(state, res, file) 267 | { 268 | switch(state) 269 | { 270 | case 'success': 271 | file = $.extend({}, file, res); 272 | delete file.slice; 273 | this.parent.queue.uploadResult('success', file); 274 | 275 | // callback 276 | if (this.parent.options.uploadComplete) 277 | { 278 | this.parent.options.uploadComplete(file, this.parent); 279 | } 280 | break; 281 | 282 | case 'error': 283 | file.message = res; 284 | this.parent.queue.uploadResult('error', file); 285 | console.error('ERROR:', file.message); 286 | 287 | // callback 288 | if (lib.util.checkFunction(this.parent.options.uploadFail)) 289 | { 290 | this.parent.options.uploadFail(file, this.parent); 291 | } 292 | break; 293 | } 294 | 295 | this.readyItems.splice(0, 1); 296 | 297 | // next upload 298 | if (this.readyItems.length) 299 | { 300 | this.play(); 301 | } 302 | else 303 | { 304 | this.uploading = false; 305 | 306 | if (this.parent.options.uploadCompleteAll && typeof this.parent.options.uploadCompleteAll === 'function') 307 | { 308 | this.parent.options.uploadCompleteAll(this.parent); 309 | } 310 | 311 | // send event to plugin 312 | this.parent.eventReceiver('queue.uploadCompleteAll'); 313 | } 314 | }; 315 | 316 | /** 317 | * add upload elements 318 | * 319 | * @param {Object} $el 320 | */ 321 | addUploadElements($el) 322 | { 323 | if (this.$uploadElement && this.$uploadElement.length) 324 | { 325 | this.$uploadElement = this.$uploadElement.add($el); 326 | } 327 | else 328 | { 329 | this.$uploadElement = $el; 330 | } 331 | }; 332 | 333 | /** 334 | * reset event 335 | * 336 | * @param {Object} $el 337 | */ 338 | resetEvent($el) 339 | { 340 | let $inputs = $el || this.$uploadElement; 341 | $inputs.each((k, o) => { 342 | lib.util.inputFileReset(o); 343 | }); 344 | }; 345 | 346 | } 347 | -------------------------------------------------------------------------------- /src/component/index.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import Uploader from './Uploader'; 3 | import Queue from './Queue'; 4 | import Plugin from './Plugin'; 5 | import * as lib from './lib'; 6 | 7 | export default class RG_Uploader { 8 | 9 | /** 10 | * constructor 11 | * 12 | * @param {Object} el HTMLElement 13 | * @param {Object} options 14 | */ 15 | constructor(el, options=null) 16 | { 17 | // check container element 18 | if (!el) return; 19 | 20 | // merge options 21 | this.options = { ...lib.defaultOptions, ...options }; 22 | if (options && options.queue) 23 | { 24 | this.options.queue = { ...lib.defaultOptions.queue, ...options.queue }; 25 | } 26 | 27 | // set container element 28 | this.$container = $(el); 29 | 30 | // init sub modules 31 | this.plugin = new Plugin(this); 32 | this.queue = new Queue(this); 33 | this.uploader = new Uploader(this); 34 | 35 | // init plugin 36 | this.plugin.init(); 37 | 38 | // init queue 39 | this.queue.init(); 40 | 41 | // play init(external) 42 | if (this.options.init && typeof this.options.init === 'function') 43 | { 44 | this.options.init(this); 45 | } 46 | } 47 | 48 | /** 49 | * event receiver 50 | * 51 | * @Param {String} type 52 | * @Param {*} value 53 | */ 54 | eventReceiver(type, value) 55 | { 56 | this.plugin.eventListener(type, value); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/component/lib/defaultOptions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | autoUpload : false, 3 | externalFileForm : null, 4 | allowFileTypes : ['jpeg', 'jpg', 'png', 'gif', 'jpg'], 5 | limitSize : 10000000, 6 | limitSizeTotal : 30000000, 7 | uploadScript : null, 8 | removeScript : null, 9 | uploadHeaders: null, 10 | removeHeaders: null, 11 | eventPrefixName : 'RG-', 12 | srcPrefixName : '', 13 | queue : { 14 | height : 150, 15 | limit : 10, 16 | style : 'list', 17 | buttons : [ 18 | { 19 | name : 'remove queue', 20 | iconName : 'close', 21 | className : 'btn-remove-queue', 22 | action : function(app, file) { 23 | app.queue.removeQueue(file.id, false, true); 24 | } 25 | } 26 | ], 27 | datas : null 28 | }, 29 | plugin : [], 30 | 31 | // upload script url callback 32 | uploadScriptFunc: null, 33 | // remove script url callback 34 | removeScriptFunc: null, 35 | // upload parameters filter `function(res) {}` 36 | uploadParamsFilter: null, 37 | // upload data filtering `function(res) {}` 38 | uploadDataFilter: null, 39 | // remove parameters filter `function(res) {}` 40 | removeParamsFilter: null, 41 | // remove data filtering `function(res) {}` 42 | removeDataFilter: null, 43 | // progress upload `function(response, file) {}` 44 | uploadProgress: null, 45 | // complete upload `function(file) {}` 46 | uploadComplete: null, 47 | // all complete upload `function(app) {}` 48 | uploadCompleteAll: null, 49 | // fail upload `function(file) {}` 50 | uploadFail: null, 51 | // init app `function(app) {}` 52 | init: null 53 | }; 54 | -------------------------------------------------------------------------------- /src/component/lib/fileUpload.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import * as util from './util'; 3 | 4 | /** 5 | * upload progress 6 | * 7 | * @Param {XMLHttpRequestProgressEvent} e 8 | * @Return {object} 9 | */ 10 | function uploadProgress(e) 11 | { 12 | if (e.lengthComputable) 13 | { 14 | return { loaded : e.loaded, total : e.total }; 15 | } 16 | } 17 | 18 | /** 19 | * upload success 20 | * 21 | * @Param {XMLHttpRequestProgressEvent} e 22 | * @Param {File} file 23 | * @Return {Object} 24 | */ 25 | function uploadSuccess(e, file) 26 | { 27 | if (e.readyState === 4) 28 | { 29 | switch (e.status) 30 | { 31 | case 200: 32 | let response = e.responseText; 33 | try { 34 | return JSON.parse(response) || response; 35 | } catch(e) { 36 | return { 37 | state : 'error', 38 | response : { 39 | message : response 40 | } 41 | }; 42 | } 43 | break; 44 | case 404: 45 | return { 46 | state : 'error', 47 | response : { 48 | message : '404 - File not found' 49 | } 50 | }; 51 | break; 52 | case 403: 53 | return { 54 | state : 'error', 55 | response : { 56 | message : '403 - Forbidden file type' 57 | } 58 | }; 59 | break; 60 | default: 61 | return { 62 | state : 'error', 63 | response : { 64 | message : 'Unknown Error' 65 | } 66 | }; 67 | break; 68 | } 69 | } 70 | 71 | return { 72 | state : 'error', 73 | response : { 74 | message : 'Unknown Error' 75 | } 76 | }; 77 | } 78 | 79 | /** 80 | * file upload class 81 | * 82 | * @author : redgoose 83 | * @param {String} action 파일처리 백엔드 url 84 | * @param {File} file 85 | * @param {Object} params 86 | * @param {Object} headers 87 | * @param {Function} filter 88 | * @return {Object} 89 | */ 90 | export default function(action, file, params, headers, filter) 91 | { 92 | const defer = $.Deferred(); 93 | 94 | if (action && typeof action === 'string') 95 | { 96 | // server upload 97 | const xhr = new XMLHttpRequest(); 98 | 99 | if (typeof FormData === 'function' || typeof FormData === 'object') 100 | { 101 | let formData = new FormData(); 102 | // append params 103 | formData.append('file', file); 104 | if (params && (typeof params === 'object')) 105 | { 106 | for (let o in params) 107 | { 108 | formData.append(o, params[o]); 109 | } 110 | } 111 | // open xhr 112 | xhr.open('post', action, true); 113 | // set header 114 | if (headers && typeof headers === 'object') 115 | { 116 | Object.keys(headers).forEach(function(o) { 117 | xhr.setRequestHeader(o, headers[o]); 118 | }); 119 | } 120 | // progress event 121 | xhr.upload.addEventListener('progress', function (e) { 122 | defer.notify(uploadProgress(e), file); 123 | }, false); 124 | // loaded event 125 | xhr.addEventListener('load', function (e) { 126 | let src = uploadSuccess(e.target); 127 | 128 | // filtering response 129 | src = filter(src); 130 | 131 | switch(src.state) { 132 | case 'success': 133 | defer.resolve(src.response, file); 134 | break; 135 | case 'error': 136 | defer.reject(src.message, file); 137 | break; 138 | } 139 | }); 140 | xhr.send(formData); 141 | } 142 | else 143 | { 144 | defer.reject('not support browser', file); 145 | } 146 | } 147 | // local upload 148 | else if (FileReader) 149 | { 150 | let reader = new FileReader(); 151 | reader.onload = (e) => { 152 | defer.resolve({ 153 | src : e.target.result, 154 | isLocalFile : true 155 | }, file); 156 | }; 157 | reader.readAsDataURL(file); 158 | } 159 | else 160 | { 161 | defer.reject('not support browser', file); 162 | } 163 | 164 | return defer.promise(); 165 | }; 166 | -------------------------------------------------------------------------------- /src/component/lib/index.js: -------------------------------------------------------------------------------- 1 | import * as util from './util'; 2 | import fileUpload from './fileUpload'; 3 | import defaultOptions from './defaultOptions'; 4 | import language from './language'; 5 | import keyboardEvent from './keyboardEvent'; 6 | import * as template from './template'; 7 | 8 | 9 | export { 10 | util, 11 | fileUpload, 12 | defaultOptions, 13 | language, 14 | keyboardEvent, 15 | template, 16 | }; -------------------------------------------------------------------------------- /src/component/lib/keyboardEvent.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | 4 | /** 5 | * create key code object 6 | * make keylist to keycode object 7 | * 8 | * @param {Array} list 9 | * @return {Object} 10 | */ 11 | function createKeyCodeObject(list) 12 | { 13 | let result = {}; 14 | list = list || []; 15 | 16 | list.forEach((item) => { 17 | if (!item.key || !item.code) return false; 18 | result[item.key] = item.code; 19 | }); 20 | 21 | return result; 22 | } 23 | 24 | 25 | /** 26 | * Keyboard Event 27 | * 28 | * @param {String} eventPrefix 29 | * @param {Array} keyList 30 | */ 31 | export default function(eventPrefix, keyList) 32 | { 33 | const EVENT_PREFIX = eventPrefix; 34 | const KEY_CODE = createKeyCodeObject(keyList); 35 | 36 | this.pressKeyCode = null; 37 | this.isPressKeyCode = false; 38 | 39 | /** 40 | * key down event 41 | * 42 | * @param {Object} e 43 | */ 44 | const keyDown = (e) => { 45 | this.pressKeyCode = e.keyCode; 46 | this.isPressKeyCode = ((e.keyCode === KEY_CODE.ctrl) || (e.keyCode === KEY_CODE.cmd)); 47 | 48 | // set event 49 | $(window) 50 | .off('keydown.' + EVENT_PREFIX) 51 | .on('keyup.' + EVENT_PREFIX, keyUp); 52 | }; 53 | 54 | /** 55 | * key up event 56 | * 57 | * @param {Object} e 58 | */ 59 | const keyUp = (e) => { 60 | this.pressKeyCode = null; 61 | this.isPressKeyCode = false; 62 | 63 | // set event 64 | $(window) 65 | .off('keyup.' + EVENT_PREFIX) 66 | .on('keydown.' + EVENT_PREFIX, keyDown); 67 | }; 68 | 69 | // init event 70 | $(window).on('keydown.' + EVENT_PREFIX, keyDown); 71 | }; -------------------------------------------------------------------------------- /src/component/lib/language.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | 4 | const dic = { 5 | ko : { 6 | error_add_upload : '업로드가 끝난후에 추가해주세요.', 7 | error_upload_limit : '파일은 총 {0}개까지 업로드할 수 있습니다.', 8 | error_limit_size : '업로드할 수 있는 용량이 초과되었습니다.', 9 | error_limit_size2 : '파일용량을 초과한 파일은 제외됩니다.', 10 | error_file_type : '잘못된 형식의 파일입니다.', 11 | error_check_file : '허용되지 않는 파일은 제외됩니다.', 12 | error_not_upload_file : '업로드할 파일이 없습니다.', 13 | error_remove_error : '파일을 삭제하는중에 오류가 발생했습니다.', 14 | error_import : '데이터 가져오기 실패', 15 | error_now_uploading: '지금 업로드중입니다.', 16 | }, 17 | en : { 18 | error_add_upload : 'Please add after upload.', 19 | error_upload_limit : 'Files can be uploaded to a total of {0}.', 20 | error_limit_size : 'The capacity that can be uploaded has been exceeded.', 21 | error_limit_size2 : 'Files exceeding the file capacity are excluded.', 22 | error_file_type : 'Invalid file.', 23 | error_check_file : 'Do not allow files are excluded.', 24 | error_not_upload_file : 'There are no files to upload.', 25 | error_remove_error : 'Deleting file error.', 26 | error_import : 'Data import failed.', 27 | error_now_uploading: 'Uploading now.', 28 | } 29 | }; 30 | 31 | 32 | /** 33 | * string format 34 | * http://stackoverflow.com/a/4673436 35 | * 36 | * @Param {String} str 37 | * @Param {Array} args 38 | * @Return {String} 39 | */ 40 | function stringFormat(str, args) 41 | { 42 | return str.replace(/{(\d+)}/g, function(match, number) { 43 | return typeof args[number] !== 'undefined' ? args[number] : match; 44 | }); 45 | } 46 | 47 | 48 | // export 49 | export default function language(code, values) 50 | { 51 | let lang = $('html').attr('lang') || 'ko'; 52 | let str = dic[lang][code] || ''; 53 | 54 | // assign values 55 | str = (values && values.length) ? stringFormat(str, values) : str; 56 | 57 | return str; 58 | } 59 | -------------------------------------------------------------------------------- /src/component/lib/template.js: -------------------------------------------------------------------------------- 1 | export const loading = '' + 2 | '
        • ' + 3 | '
          ' + 4 | '
          ' + 5 | '

          ' + 6 | '40%' + 7 | '

          ' + 8 | '
          ' + 9 | '
          ' + 10 | '

          ' + 11 | '30%' + 12 | '

          ' + 13 | '
          ' + 14 | '
          ' + 15 | 'filename.jpg' + 16 | '
          ' + 17 | 'ready' + 18 | '
          ' + 19 | '' + 22 | '
          ' + 23 | '
        • '; 24 | 25 | export const error = '' + 26 | '
        • ' + 27 | '
          ' + 28 | '
          ' + 29 | '
          ' + 30 | 'image/jpg' + 31 | 'filename.jpg' + 32 | '
          ' + 33 | 'upload fail' + 34 | '
          ' + 35 | '
          ' + 36 | '
        • '; 37 | 38 | export const complete = '' + 39 | '
        • ' + 40 | '
          ' + 41 | '
          filename.jpg
          ' + 42 | '
          ' + 43 | 'image/jpg' + 44 | 'filename.jpg' + 45 | '
          ' + 46 | 'uploaded' + 47 | '123.43kb' + 48 | '
          ' + 49 | '' + 50 | '
          ' + 51 | '
        • '; 52 | -------------------------------------------------------------------------------- /src/component/lib/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * byte to size convert 3 | * 4 | * @param {Number} bytes 5 | * @return {String} 6 | */ 7 | export function bytesToSize(bytes) 8 | { 9 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; 10 | if (bytes === 0) return '0'; 11 | const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); 12 | return Math.round(bytes / Math.pow(1024, i), 2) + '' + sizes[i]; 13 | } 14 | 15 | 16 | /** 17 | * find DOM 18 | * 19 | * @param {Object} $con 20 | * @param {String} key 21 | * @param {String} name 22 | * @return {Object} 23 | */ 24 | export function findDOM($con, key, name) 25 | { 26 | if (!($con && $con.length)) return null; 27 | return $con.find(`[data-${key}=${name}]`); 28 | } 29 | 30 | 31 | /** 32 | * get unique number 33 | * 34 | * @param {int} length 35 | * @return {int} 36 | */ 37 | export function getUniqueNumber(length) 38 | { 39 | length = length || 10; 40 | 41 | const timestamp = +new Date; 42 | const _getRandomInt = function( min, max ) 43 | { 44 | return Math.floor( Math.random() * ( max - min + 1 ) ) + min; 45 | }; 46 | 47 | const ts = timestamp.toString(); 48 | const parts = ts.split( "" ).reverse(); 49 | let id = ""; 50 | 51 | for( let i = 0; i < length; ++i ) 52 | { 53 | const index = _getRandomInt( 0, parts.length - 1 ); 54 | id += parts[index]; 55 | } 56 | 57 | return parseInt(id); 58 | } 59 | 60 | 61 | /** 62 | * detect touch event 63 | * 64 | * @return {Boolean} 65 | */ 66 | export function detectTouchEvent() 67 | { 68 | return 'ontouchstart' in document.documentElement; 69 | } 70 | 71 | 72 | /** 73 | * reset input[type=file] 74 | * 75 | * @param {Object} input 76 | */ 77 | export function inputFileReset(input) 78 | { 79 | let win10ie11 = !!navigator.userAgent.match(/Trident.*rv[ :]?[1-9]{2}\./); 80 | let ie = (navigator.appVersion.indexOf("MSIE ") !== -1); 81 | let ie10 = (navigator.appVersion.indexOf("MSIE 10") !== -1); 82 | 83 | if (ie10) 84 | { 85 | // is IE10 86 | input.type = 'radio'; 87 | input.type = 'file'; 88 | } 89 | else if (ie || win10ie11) 90 | { 91 | // is IE 92 | const orgParent = input.parentNode; 93 | const orgNext = input.nextSibling; 94 | 95 | let tmp = document.createElement('form'); 96 | tmp.appendChild(input); 97 | tmp.reset(); 98 | 99 | orgParent.insertBefore(input, orgNext); 100 | } 101 | else 102 | { 103 | // etc 104 | input.value = ''; 105 | } 106 | } 107 | 108 | 109 | /** 110 | * get function return 111 | * 112 | * @param {Function} func 113 | * @param {Object} src 114 | * @param params 115 | * @return {Object} 116 | */ 117 | export function getFunctionReturn(func, src, params) 118 | { 119 | if (!func || !(typeof func === 'function')) return src; 120 | return func(src, params) || src; 121 | } 122 | 123 | /** 124 | * check function 125 | * 126 | * @param {Function} func 127 | * @return {Boolean} 128 | */ 129 | export function checkFunction(func) 130 | { 131 | return func && (typeof func === 'function'); 132 | } 133 | -------------------------------------------------------------------------------- /src/fonts/material-icons/MaterialIcons-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/src/fonts/material-icons/MaterialIcons-Regular.eot -------------------------------------------------------------------------------- /src/fonts/material-icons/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/src/fonts/material-icons/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /src/fonts/material-icons/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/src/fonts/material-icons/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /src/fonts/material-icons/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redgoose-dev/rg-uploader/480a27890fed63cf028950165aee09d169feff88/src/fonts/material-icons/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /src/fonts/material-icons/README.md: -------------------------------------------------------------------------------- 1 | The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts: 2 | 3 | ```html 4 | 6 | ``` 7 | 8 | Read more in our full usage guide: 9 | http://google.github.io/material-design-icons/#icon-font-for-the-web 10 | -------------------------------------------------------------------------------- /src/fonts/material-icons/material-icons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url('MaterialIcons-Regular.eot'); /* For IE6-8 */ 6 | src: local('Material Icons'), 7 | local('MaterialIcons-Regular'), 8 | url('MaterialIcons-Regular.woff2') format('woff2'), 9 | url('MaterialIcons-Regular.woff') format('woff'), 10 | url('MaterialIcons-Regular.ttf') format('truetype'); 11 | } 12 | 13 | .material-icons { 14 | font-family: 'Material Icons'; 15 | font-weight: normal; 16 | font-style: normal; 17 | font-size: 24px; /* Preferred icon size */ 18 | display: inline-block; 19 | line-height: 1; 20 | text-transform: none; 21 | letter-spacing: normal; 22 | word-wrap: normal; 23 | white-space: nowrap; 24 | direction: ltr; 25 | 26 | /* Support for all WebKit browsers. */ 27 | -webkit-font-smoothing: antialiased; 28 | /* Support for Safari and Chrome. */ 29 | text-rendering: optimizeLegibility; 30 | 31 | /* Support for Firefox. */ 32 | -moz-osx-font-smoothing: grayscale; 33 | 34 | /* Support for IE. */ 35 | font-feature-settings: 'liga'; 36 | } 37 | -------------------------------------------------------------------------------- /src/plugins/changeQueue.plugin.js: -------------------------------------------------------------------------------- 1 | function RG_ChangeQueue(options, jquery) 2 | { 3 | this.name = 'Change Queue'; 4 | this.sortable = null; 5 | 6 | var $ = jquery || $ || jQuery; 7 | var self = this; 8 | var app = null; 9 | 10 | if (!$) return; 11 | 12 | /** 13 | * load vendor Sortable 14 | * 15 | * @return {Object|Boolean} 16 | */ 17 | function loadVendorSortable() 18 | { 19 | var defer = $.Deferred(); 20 | 21 | if (self.options.class_sortable && self.options.class_sortable.name === 'Sortable') 22 | { 23 | window.loadedVendorSortable = true; 24 | window.Sortable = self.options.class_sortable; 25 | // 약간의 딜레이가 필요함. 26 | setTimeout(defer.resolve, 100); 27 | return defer.promise(); 28 | } 29 | 30 | // check loaded vendor 31 | if (window.loadedVendorSortable) 32 | { 33 | defer.resolve(); 34 | } 35 | else 36 | { 37 | var head = document.getElementsByTagName('head')[0]; 38 | var scriptElement = document.createElement('script'); 39 | 40 | scriptElement.src = self.options.url_sortable; 41 | 42 | head.appendChild(scriptElement); 43 | 44 | var n = 0; 45 | var interval = setInterval(function(){ 46 | n++; 47 | try { 48 | if (Sortable) 49 | { 50 | clearInterval(interval); 51 | window.loadedVendorSortable = true; 52 | defer.resolve(); 53 | } 54 | } 55 | catch(e) {} 56 | }, 50); 57 | } 58 | 59 | return defer.promise(); 60 | } 61 | 62 | /** 63 | * end change item event 64 | */ 65 | function change() 66 | { 67 | var newIds = []; 68 | var newFiles = []; 69 | var index = app.queue.$queue.children('li').map(function(){ 70 | return app.queue.findItem($(this).data('id')); 71 | }); 72 | 73 | for (var i=0; i' + 25 | '' + o.iconName + '' + 26 | ''); 27 | initButtonsEvent($el); 28 | self.$nav.append($el); 29 | }); 30 | self.$buttons = self.$nav.children('button'); 31 | } 32 | 33 | /** 34 | * init buttons event 35 | * 36 | * @Param {Object} $el 37 | */ 38 | function initButtonsEvent($el) 39 | { 40 | $el.on('click', function(e){ 41 | if ($(this).hasClass('on')) return false; 42 | app.queue.changeStyle($(this).data('style')); 43 | }); 44 | } 45 | 46 | /** 47 | * change active button 48 | */ 49 | function changeActiveButton() 50 | { 51 | self.$buttons.removeClass('on').filter('.style-' + name).addClass('on'); 52 | } 53 | 54 | /** 55 | * init 56 | * 57 | * @Param {Object} parent 58 | */ 59 | this.init = function(parent) 60 | { 61 | app = parent; 62 | 63 | var $body = !!selector ? $(selector) : app.$container.children('header'); 64 | 65 | // append comp 66 | self.$nav = $(''); 67 | $body.append(self.$nav); 68 | 69 | // create buttons 70 | createButtons(); 71 | 72 | // set active button 73 | changeActiveButton(app.queue.style); 74 | }; 75 | 76 | /** 77 | * event listener 78 | * 79 | * @Param {String} type 80 | * @Param {*} value 81 | */ 82 | this.eventListener = function(type, value) 83 | { 84 | switch(type) 85 | { 86 | // change style 87 | case 'queue.changeStyle': 88 | changeActiveButton(value.style); 89 | break; 90 | } 91 | } 92 | } 93 | 94 | export default RG_ChangeQueueStyle; 95 | -------------------------------------------------------------------------------- /src/plugins/dnd.plugin.js: -------------------------------------------------------------------------------- 1 | function RG_DragAndDrop(el, jquery) 2 | { 3 | this.name = 'Drag And Drop'; 4 | this.areaElements = []; 5 | 6 | var $ = jquery || $ || jQuery; 7 | var self = this; 8 | var app = null; 9 | 10 | if (!$) return; 11 | 12 | /** 13 | * init file drag and drop event 14 | * 15 | * @Param {Array} area 16 | * @Return {Object} 17 | */ 18 | var fileDragAndDrop = function(area) 19 | { 20 | if (!window.File || !window.FileList || !window.FileReader || !window.Blob) return false; 21 | if (!area.length) return false; 22 | 23 | var defer = $.Deferred(); 24 | var over = false; 25 | 26 | /** 27 | * dragover handler 28 | * 29 | * @Param {Object} e 30 | */ 31 | var overHandler = function(e) 32 | { 33 | e.stopPropagation(); 34 | e.preventDefault(); 35 | 36 | if (e.type === 'dragover') 37 | { 38 | if (over) return false; 39 | over = true; 40 | if (e.dataTransfer.effectAllowed === 'all') 41 | { 42 | $(e.currentTarget).addClass('drop-mode'); 43 | } 44 | e.dataTransfer.dropEffect = 'copy'; 45 | } 46 | else 47 | { 48 | over = false; 49 | $(e.currentTarget).removeClass('drop-mode'); 50 | } 51 | return false; 52 | }; 53 | 54 | /** 55 | * drop handler 56 | * 57 | * @Param {Object} e 58 | */ 59 | var dropHandler = function(e) 60 | { 61 | e.stopPropagation(); 62 | e.preventDefault(); 63 | 64 | overHandler(e); 65 | 66 | var files = (e.dataTransfer) ? e.dataTransfer.files : null; 67 | if (files && files.length) 68 | { 69 | defer.notify(files); 70 | } 71 | return false; 72 | }; 73 | 74 | // set events 75 | for (var i=0; i' + 66 | '' + 67 | '
          ' + 68 | '
          ' + 69 | '
          ' + 70 | '

          message

          ' + 71 | '' + 75 | '
          ' + 76 | '
          ' + 77 | ''); 78 | self.$el.wrap = self.$el.con.children('.wrap'); 79 | self.$el.bg = self.$el.con.children('.bg'); 80 | self.$el.figure = self.$el.con.find('.img-wrap figure'); 81 | self.$el.meta = self.$el.con.find('.meta > p'); 82 | self.$el.btn_done = self.$el.con.find('.btn-done'); 83 | self.$el.btn_close = self.$el.con.find('.btn-close'); 84 | 85 | // insert element 86 | $('body').append(self.$el.con); 87 | } 88 | 89 | /** 90 | * init events 91 | */ 92 | function initEvents() 93 | { 94 | // close in background 95 | self.$el.bg.on('click', function(){ 96 | self.close(); 97 | }); 98 | 99 | // close in close button 100 | self.$el.btn_close.on('click', function(){ 101 | self.close(); 102 | }); 103 | 104 | // done 105 | self.$el.btn_done.on('click', done); 106 | 107 | // init resize event 108 | $(window).on(RESIZE_EVENT, resize); 109 | } 110 | 111 | /** 112 | * change mobile 113 | */ 114 | function actMobile(pass) 115 | { 116 | if (!pass && isMobile) return false; 117 | 118 | // set isMobile 119 | isMobile = true; 120 | 121 | // change window size 122 | self.$el.wrap 123 | .width('100%').height('100%') 124 | .css({ marginLeft : 0, marginTop : 0, left: 0, top: 0 }); 125 | 126 | // rebuild croppie 127 | if (self.croppie) 128 | { 129 | rebuildCroppie(true); 130 | } 131 | } 132 | 133 | /** 134 | * change desktop 135 | */ 136 | function actDesktop(pass) 137 | { 138 | if (!pass && !isMobile) return false; 139 | 140 | // set isMobile 141 | isMobile = false; 142 | 143 | // change window size 144 | self.$el.wrap 145 | .width(self.options.width).height(self.options.height) 146 | .css({ 147 | marginLeft : (0 - self.options.width * 0.5) + 'px', 148 | marginTop : (0 - self.options.height * 0.5) + 'px', 149 | left: '50%', 150 | top: '50%' 151 | }); 152 | 153 | // rebuild croppie 154 | if (self.croppie) 155 | { 156 | rebuildCroppie(true); 157 | } 158 | } 159 | 160 | /** 161 | * resize event 162 | * 163 | * @param {Object} e 164 | */ 165 | function resize(e) 166 | { 167 | var $win = $(window); 168 | 169 | if (!self.croppie) 170 | { 171 | clearTimeout(resizeInterval); 172 | return false; 173 | } 174 | 175 | if (resizeInterval) 176 | { 177 | clearTimeout(resizeInterval); 178 | } 179 | 180 | resizeInterval = setTimeout(function(){ 181 | // 계속 모바일 사이즈상태일때 실행 182 | if (isMobile && ($win.width() < 640)) 183 | { 184 | actMobile(true); 185 | return false; 186 | } 187 | if ($win.width() < 640) 188 | { 189 | actMobile(true); 190 | } 191 | else if ($win.width() > 640) 192 | { 193 | actDesktop(true); 194 | } 195 | }, 300); 196 | } 197 | 198 | /** 199 | * rebuild croppie 200 | * 201 | * @param {Boolean} isResize 202 | */ 203 | function rebuildCroppie(isResize) 204 | { 205 | // save option 206 | var save = (isResize) ? self.croppie.get() : {}; 207 | 208 | // destroy croppie 209 | destroyCroppie(); 210 | 211 | // build croppie 212 | self.options.croppie.boundary = { 213 | width : (self.options.mobileSize > $(window).width()) ? $(window).width() : self.options.width, 214 | height : ((self.options.mobileSize > $(window).width()) ? $(window).height() : self.options.height)-60 215 | }; 216 | self.croppie = new Croppie(self.$el.figure.get(0), self.options.croppie); 217 | 218 | // bind croppie 219 | if (isResize) 220 | { 221 | self.rebind({ 222 | src : self.file.fullSrc, 223 | points : save.points 224 | }, function(){ 225 | self.croppie.setZoom(save.zoom); 226 | }); 227 | } 228 | } 229 | 230 | /** 231 | * destroy croppie 232 | */ 233 | function destroyCroppie() 234 | { 235 | if (self.croppie) 236 | { 237 | self.croppie.destroy(); 238 | self.croppie = null; 239 | } 240 | } 241 | 242 | /** 243 | * done event 244 | * 245 | * @param {Object} e 246 | */ 247 | function done(e) 248 | { 249 | // result 250 | self.croppie.result(self.options.output).then(function(res){ 251 | if (self.options.uploadScript) 252 | { 253 | $.post( 254 | self.options.uploadScript, 255 | { 256 | name : self.file.name, 257 | image : res, 258 | id : getUniqueNumber() 259 | }, 260 | function(res){ 261 | try { 262 | res = JSON.parse(res); 263 | } catch (e) { 264 | alert('parse error'); 265 | return false; 266 | } 267 | if (res.state === 'error') 268 | { 269 | alert(res.response.message); 270 | return false; 271 | } 272 | 273 | if (self.options.doneCallback) 274 | { 275 | self.options.doneCallback(res.response, app, self.file); 276 | } 277 | }); 278 | } 279 | else 280 | { 281 | if (self.options.doneCallback) 282 | { 283 | self.options.doneCallback({ 284 | id : getUniqueNumber(), 285 | name : 'thumb-' + self.file.name, 286 | src : res, 287 | type : 'image/' + self.options.output.format, 288 | size : 0 289 | }, app, self.file); 290 | } 291 | } 292 | 293 | // close 294 | self.close(); 295 | }, function (error) { 296 | console.log('ERROR', error); 297 | }); 298 | } 299 | 300 | /** 301 | * get unique number 302 | * 303 | * @param {int} length 304 | * @return {int} 305 | */ 306 | function getUniqueNumber(length=undefined) 307 | { 308 | length = length || 10; 309 | 310 | var timestamp = +new Date; 311 | var _getRandomInt = function( min, max ) 312 | { 313 | return Math.floor( Math.random() * ( max - min + 1 ) ) + min; 314 | }; 315 | 316 | var ts = timestamp.toString(); 317 | var parts = ts.split( "" ).reverse(); 318 | var id = ""; 319 | 320 | for( var i = 0; i < length; ++i ) 321 | { 322 | var index = _getRandomInt( 0, parts.length - 1 ); 323 | id += parts[index]; 324 | } 325 | 326 | return parseInt(id); 327 | } 328 | 329 | 330 | /** 331 | * init 332 | * 333 | * @param {Object} parent 334 | */ 335 | this.init = function(parent) 336 | { 337 | app = parent; 338 | 339 | // merge options 340 | this.assignOption(options); 341 | 342 | // load files 343 | loadExternalFiles(); 344 | 345 | // create container 346 | createContainer(); 347 | 348 | // init events 349 | initEvents(); 350 | }; 351 | 352 | /** 353 | * open window 354 | * 355 | * @param {Object} file 356 | * @param {Object} bind 357 | */ 358 | this.open = function(file, bind) 359 | { 360 | bind = bind || {}; 361 | 362 | // show window 363 | this.$el.con.addClass('show'); 364 | $('html').addClass('rg-uploader-popup'); 365 | 366 | // set file value 367 | this.file = file; 368 | 369 | // act pc & mobile 370 | if ($(window).width() < this.options.mobileSize) 371 | { 372 | actMobile(true); 373 | } 374 | else 375 | { 376 | actDesktop(true); 377 | } 378 | 379 | // rebuild croppie 380 | rebuildCroppie(); 381 | // bind image 382 | this.rebind({ 383 | src : this.file.fullSrc, 384 | points : bind.points, 385 | orientation : bind.orientation 386 | }, function(){ 387 | self.croppie.setZoom(bind.zoom || 0.1); 388 | }); 389 | 390 | // input state 391 | this.$el.meta.text('output size: ' + this.options.output.size.width + '*' + this.options.output.size.height); 392 | 393 | // callback open window 394 | if (this.options.openCallback) 395 | { 396 | this.options.openCallback(app); 397 | } 398 | }; 399 | 400 | /** 401 | * close window 402 | */ 403 | this.close = function() 404 | { 405 | destroyCroppie(); 406 | this.file = null; 407 | this.$el.con.removeClass('show'); 408 | $('html').removeClass('rg-uploader-popup'); 409 | 410 | // callback close window 411 | if (this.options.closeCallback) 412 | { 413 | this.options.closeCallback(app); 414 | } 415 | }; 416 | 417 | /** 418 | * rebind 419 | * 420 | * @param {Object} options 421 | * @param {Function} callback 422 | */ 423 | this.rebind = function(options, callback) 424 | { 425 | this.croppie.bind({ 426 | url : options.src, 427 | points : (options.points) ? options.points : [], 428 | orientation : (options.orientation) ? options.orientation : 1 429 | }, function(){ 430 | if (callback) callback(); 431 | }); 432 | }; 433 | 434 | /** 435 | * assignOption 436 | * 437 | * @param {Object} obj 438 | */ 439 | this.assignOption = function(obj) 440 | { 441 | this.options = $.extend(true, this.options, obj); 442 | } 443 | } 444 | 445 | RG_Thumbnail.prototype.options = { 446 | width : 640, 447 | height : 480, 448 | mobileSize : 640, 449 | class_croppie: null, 450 | url_croppieCSS : 'https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.4/croppie.min.css', 451 | url_croppieJS : 'https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.4/croppie.min.js', 452 | uploadScript : '', 453 | output : { 454 | type : 'canvas', 455 | quality : .75, 456 | format : 'jpeg', 457 | size : { width : 150, height : 150 } 458 | }, 459 | croppie : { 460 | enableOrientation: true, 461 | boundary : { width: 640, height: 480-60 }, 462 | viewport : { width: 150, height: 150, type: 'square' } 463 | }, 464 | doneCallback : null, 465 | openCallback : null, 466 | closeCallback : null 467 | }; 468 | 469 | export default RG_Thumbnail; 470 | -------------------------------------------------------------------------------- /src/rg-uploader.js: -------------------------------------------------------------------------------- 1 | import uploader from './component'; 2 | 3 | // stylesheet 4 | import './fonts/material-icons/material-icons.css'; 5 | import './scss/index.scss'; 6 | 7 | // export rg-uploader component 8 | export default uploader; 9 | -------------------------------------------------------------------------------- /src/scss/index.scss: -------------------------------------------------------------------------------- 1 | @import "lib"; 2 | @import "queue-style"; 3 | @import "plugins"; 4 | 5 | // bracket 6 | .rg-uploader .bracket { 7 | &:before {content: '(';} 8 | &:after {content: ')';} 9 | &.large { 10 | &:before {content: '[';} 11 | &:after {content: ']';} 12 | } 13 | } 14 | 15 | // rg-popup 16 | .rg-uploader-popup { 17 | &, body {overflow: hidden;} 18 | } 19 | 20 | // rg-uploader component 21 | .rg-uploader { 22 | border: 1px solid #ccc; 23 | border-radius: $border-radius; 24 | overflow: hidden; 25 | box-sizing: border-box; 26 | em { 27 | font-style: normal; 28 | } 29 | 30 | // queues 31 | .queues { 32 | position: relative; 33 | height: 100%; 34 | box-sizing: border-box; 35 | > ul { 36 | margin: 0; 37 | padding: 0; 38 | height: 100%; 39 | list-style: none; 40 | overflow-x: hidden; 41 | overflow-y: auto; 42 | -webkit-overflow-scrolling: touch; 43 | box-sizing: border-box; 44 | } 45 | nav { 46 | font-size: 0; 47 | box-sizing: border-box; 48 | button { 49 | margin: 0; 50 | padding: 4px; 51 | border: none; 52 | font-size: 0; 53 | background: none; 54 | cursor: pointer; 55 | &.on i { 56 | color: $color-key; 57 | } 58 | &.disabled { 59 | cursor: not-allowed; 60 | i { 61 | color: #b2b5b9; 62 | } 63 | } 64 | } 65 | i { 66 | font-size: 16px; 67 | color: #57595B; 68 | } 69 | } 70 | 71 | @media (max-width: $mobile-max-width) { 72 | overflow: auto; 73 | } 74 | } 75 | } 76 | 77 | // header 78 | .rg-uploader-header { 79 | position: relative; 80 | padding: 10px 130px 10px 10px; 81 | border-bottom: 2px solid #ccc; 82 | h1 { 83 | margin: 0; 84 | font-family: $font-eng-helvetica; 85 | font-size: 18px; 86 | color: #222; 87 | font-weight: normal; 88 | } 89 | p { 90 | margin: 3px 0 0; 91 | font-size: 12px; color: #777; 92 | } 93 | 94 | @media (max-width: $mobile-max-width) { 95 | padding: 10px; 96 | } 97 | } 98 | 99 | // footer 100 | .rg-uploader-footer { 101 | display: flex; 102 | display: -webkit-flex; 103 | align-items: center; 104 | border-top: 2px solid #ccc; 105 | overflow: hidden; 106 | nav { 107 | flex: 1; 108 | font-size: 0; 109 | > * { 110 | display: inline-block; margin: 0; padding: 10px; 111 | vertical-align: middle; font-size: 0; 112 | border: none; border-right: 1px solid #ddd; 113 | overflow: hidden; outline: none; cursor: pointer; background: none; 114 | &:hover {background: #f4f4f4;} 115 | > * {display: inline-block; vertical-align: middle;} 116 | &:first-child {border-bottom-left-radius: $border-radius;} 117 | &.disabled { 118 | span, i {color: #999;} 119 | } 120 | } 121 | span { 122 | margin: 0; 123 | font-family: $font-eng-basic; font-size: 12px; color: #222; 124 | } 125 | i {margin: 0 3px 0 0; font-size: 16px; color: #222;} 126 | .add-file { 127 | input[type=file] { 128 | position: absolute; 129 | visibility: hidden; 130 | } 131 | } 132 | } 133 | 134 | @media (max-width: $mobile-max-width) { 135 | nav { 136 | span {display: none;} 137 | i {margin: 0; font-size: 18px;} 138 | } 139 | } 140 | } 141 | 142 | // body 143 | .rg-uploader-body { 144 | display: flex; 145 | min-height: $queue-height; 146 | > .queues { 147 | flex: 1; 148 | } 149 | 150 | @media (max-width: $mobile-max-width) { 151 | height: auto !important; 152 | min-height: 100px; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/scss/lib.scss: -------------------------------------------------------------------------------- 1 | // color 2 | $color-key: #4A90E2; 3 | $color-error: #CE3E3E; 4 | 5 | // font 6 | $font-eng-helvetica: "Helvetica Neue", Helvetica, Arial, sans-serif; 7 | $font-eng-basic: Arial, sans-serif; 8 | 9 | // size 10 | $queue-height: 150px; 11 | $border-radius: 4px; 12 | $preview-width: $queue-height; 13 | $mobile-max-width: 640px; 14 | 15 | 16 | // mixin 17 | 18 | // flex 19 | @mixin d-flex($middle) { 20 | display: -webkit-flex; 21 | display: flex; 22 | @if ($middle) { 23 | -webkit-align-items: center; 24 | align-items: center; 25 | } 26 | } 27 | 28 | @mixin flex($val:inherit) { 29 | -webkit-flex: $val; 30 | flex: $val; 31 | } 32 | 33 | @mixin drop-area() { 34 | content: ''; 35 | display: block; 36 | position: absolute; 37 | left: 0; 38 | right: 0; 39 | top: 0; 40 | bottom: 0; 41 | z-index: 2; 42 | border: 3px solid $color-key; 43 | pointer-events: none; 44 | } 45 | -------------------------------------------------------------------------------- /src/scss/plugins.scss: -------------------------------------------------------------------------------- 1 | @import "lib"; 2 | 3 | 4 | // plugin - preview 5 | .rg-uploader { 6 | .rg-uploader-body > .preview { 7 | flex: none; 8 | } 9 | .preview { 10 | flex: none; 11 | width: $preview-width; 12 | height: 100%; 13 | overflow: hidden; 14 | &.hide { 15 | display: none; 16 | } 17 | figure { 18 | height: 100%; margin: 0; 19 | background: no-repeat 50% 50%; 20 | background-size: cover; 21 | text-indent: -9999px; 22 | } 23 | .not-image { 24 | position: relative; 25 | background: #7E7F80 !important; 26 | &:after { 27 | content: 'not image'; 28 | position: absolute; 29 | top: 50%; 30 | left: 0; 31 | right: 0; 32 | margin-top: -7px; 33 | text-indent: 0; 34 | font-family: $font-eng-basic; 35 | color: #ccc; 36 | font-size: 11px; 37 | text-align: center; 38 | } 39 | } 40 | } 41 | @media (max-width: $mobile-max-width) { 42 | .preview { 43 | display: none; 44 | } 45 | } 46 | } 47 | 48 | 49 | // plugin - sizeinfo 50 | .rg-uploader { 51 | > footer { 52 | > div { 53 | width: 140px; padding: 0 10px 0 0; 54 | > p { 55 | margin: 0; 56 | font-family: $font-eng-basic; 57 | text-align: right; font-size: 12px; color: #222; 58 | em {font-style: normal;} 59 | } 60 | } 61 | } 62 | } 63 | 64 | 65 | // plugin - changeQueueStyle 66 | .rg-uploader { 67 | > header { 68 | nav { 69 | position: absolute; top: 50%; right: 10px; 70 | height: 28px; margin-top: -14px; 71 | border: 1px solid $color-key; border-radius: 3px; 72 | font-size: 0; overflow: hidden; 73 | button { 74 | display: inline-block; height: 100%; margin: 0; padding: 0 7px; 75 | background: none; cursor: pointer; 76 | border: none; border-left: 1px solid $color-key; 77 | &:first-child {border-left: none;} 78 | i {font-size: 20px; line-height: 22px; color: $color-key;} 79 | &.on { 80 | background: $color-key; 81 | cursor: default; outline: 0; 82 | i {color: #fff;} 83 | } 84 | } 85 | 86 | @media (max-width: $mobile-max-width) { 87 | position: static; margin: 8px 0 0; height: 34px; 88 | display: -webkit-flex; display: flex; 89 | button { 90 | -webkit-flex: 1; flex: 1; 91 | padding: 0; 92 | i {font-size: 20px; line-height: 22px;} 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | 100 | // plugin - drag and drop 101 | .rg-uploader { 102 | .queues.drop-mode { 103 | &:after { 104 | content: ''; position: absolute; display: block; 105 | left: 0; right: 0; top: 0; bottom: 0; 106 | border: 2px dashed $color-key; 107 | pointer-events: none; 108 | } 109 | } 110 | } 111 | 112 | 113 | // plugin - thumbnail 114 | .rg-plugin-thumbnail { 115 | &, * {-webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box;} 116 | position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 99999; 117 | display: none; 118 | &.show {display: block;} 119 | > .bg { 120 | position: absolute; 121 | left: 0; right: 0; top: 0; bottom: 0; 122 | background: rgba(0,0,0,.8); 123 | } 124 | > .wrap { 125 | position: absolute; left: 50%; top: 50%; 126 | background: #fff; 127 | box-shadow: 0 2px 8px rgba(0,0,0,.25); 128 | 129 | .img-wrap { 130 | padding-bottom: 60px; 131 | height: 100%; 132 | figure { 133 | margin: 0; height: 100%; 134 | background: #333; 135 | } 136 | .croppie-container {position: relative; padding: 0;} 137 | .cr-slider-wrap {position: absolute; left: 0; right: 0; bottom: 20px; z-index: 2;} 138 | } 139 | .body { 140 | position: absolute; left: 0; right: 0; bottom: 0; height: 60px; 141 | background: #fff; 142 | display: -webkit-flex; display: flex; 143 | -webkit-align-items: center; align-items: center; 144 | .meta { 145 | -webkit-flex: 1; flex: 1; 146 | padding-left: 10px; padding-right: 20px; 147 | p { 148 | margin: 0; 149 | font-size: 13px; color: #222; 150 | } 151 | } 152 | nav { 153 | padding-right: 10px; text-align: right; 154 | button { 155 | margin: 0; padding: 5px; 156 | border: none; background: none; cursor: pointer; 157 | i { 158 | vertical-align: top; 159 | color: #57595B; 160 | } 161 | & + button {margin-left: 8px;} 162 | } 163 | } 164 | } 165 | } 166 | } 167 | 168 | 169 | // plugin - changeQueue 170 | .rg-plugin-changeQueue { 171 | li {cursor: move;} 172 | .sortable-ghost {opacity: .2;} 173 | } 174 | -------------------------------------------------------------------------------- /src/scss/queue-style.scss: -------------------------------------------------------------------------------- 1 | @import "lib"; 2 | 3 | .rg-uploader { 4 | .queues { 5 | // style - list 6 | > .style-list { 7 | $height: 34px; 8 | 9 | padding: 10px; 10 | li { 11 | position: relative; margin: 3px 0 0; background: #fff; 12 | box-shadow: 0 1px 8px rgba(0,0,0,.15); 13 | &:first-child {margin-top: 0;} 14 | 15 | &.complete { 16 | > div { 17 | display: flex; 18 | align-items: center; 19 | .col { 20 | @include flex(1); 21 | padding: 5px; 22 | } 23 | figure.col {display: none;} 24 | .bd { 25 | margin: 0; padding: 10px 8px 10px 10px; 26 | font-size: 12px; color: #222; word-break: break-all; 27 | hr {display: none;} 28 | .filetype {margin: 0 5px 0 0;} 29 | .state:before {content: ' - ';} 30 | } 31 | nav.col { 32 | @include flex(); 33 | padding-right: 8px; text-align: right; 34 | } 35 | } 36 | &.selected:after { 37 | @include drop-area(); 38 | } 39 | } 40 | &.loading { 41 | > div { 42 | display: flex; 43 | align-items: center; 44 | .col { 45 | @include flex(1); 46 | padding: 5px; 47 | } 48 | figure.col {display: none;} 49 | .bd { 50 | margin: 0; padding: 10px 8px 10px 10px; 51 | font-size: 12px; color: #222; word-break: break-all; 52 | hr {display: none;} 53 | .state:before {content: ' - ';} 54 | } 55 | .bar { 56 | position: absolute; padding: 0; 57 | left: 0; right: 0; top: 0; bottom: 0; 58 | > p { 59 | margin: 0; position: relative; height: 100%; 60 | background: $color-key; 61 | white-space: nowrap; overflow: hidden; 62 | } 63 | span { 64 | position: absolute; display: block; 65 | right: 8px; top: 50%; margin-top: -6px; 66 | font-size: 11px; color: #fff; line-height: normal; 67 | } 68 | } 69 | nav.col { 70 | @include flex(); 71 | padding-right: 8px; text-align: right; 72 | } 73 | } 74 | &.ready { 75 | .bar {display: none;} 76 | .bd {color: #888;} 77 | } 78 | } 79 | &.error { 80 | padding: 10px; background: #CE3E3E; 81 | color: #fff; font-size: 12px; word-break: break-all; 82 | figure.col {display: none;} 83 | .bd { 84 | margin: 0; 85 | .state:before {content: ' - ';} 86 | hr {display: none;} 87 | } 88 | } 89 | } 90 | 91 | @media (max-width: $mobile-max-width) { 92 | li { 93 | &.complete { 94 | > div { 95 | display: block; 96 | p.col { 97 | padding: 10px 10px 0; 98 | br {display: inherit; line-height: 20px;} 99 | .state:before {display: none;} 100 | .state, .size {font-weight: bold;} 101 | } 102 | nav.col { 103 | @include d-flex(true); 104 | margin: 0 10px; padding: 0; 105 | button { 106 | padding: 8px 0 12px; 107 | @include flex(1); 108 | } 109 | } 110 | } 111 | } 112 | &.error { 113 | > div { 114 | p.col { 115 | br {display: inherit; line-height: 20px;} 116 | .state:before {display: none;} 117 | .state, .size {font-weight: bold;} 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | // style - web 126 | > .style-web { 127 | $size: 70px; 128 | $min-size: 34px; 129 | padding: 10px; 130 | li { 131 | position: relative; margin-top: 3px; background: #fff; 132 | box-shadow: 0 1px 8px rgba(0,0,0,.15); 133 | &:first-child { 134 | margin-top: 0; 135 | } 136 | &.complete { 137 | > div { 138 | display: flex; 139 | align-items: center; 140 | .col { 141 | @include flex(1); 142 | padding: 5px; 143 | } 144 | figure.col { 145 | @include flex(inherit); 146 | width: $size; height: $size; 147 | margin: 0; padding: 0; text-indent: -9999px; 148 | background: no-repeat 50% 50%; background-size: cover; 149 | &.not-image { 150 | $length: 30px; 151 | position: relative; background: #999; 152 | &:before, &:after { 153 | content: ''; display: block; position: absolute; 154 | left: 50%; top: 50%; 155 | background: #ddd; 156 | -webkit-transform-origin: 50% 50%; transform-origin: 50% 50%; 157 | -webkit-transform: rotate(45deg); transform: rotate(45deg); 158 | } 159 | &:before { 160 | width: $length; height: 1px; 161 | margin-left: 0 - $length/2; margin-top: -0.5px; 162 | } 163 | &:after { 164 | height: $length; width: 1px; 165 | margin-top: 0 - $length/2; margin-left: -0.5px; 166 | } 167 | } 168 | } 169 | .bd { 170 | margin: 0; padding-left: 10px; 171 | font-family: $font-eng-helvetica; font-size: 12px; color: #222; 172 | word-break: break-all; 173 | hr {border: none; margin: 0; height: 5px; background: none;} 174 | .filetype {display: block; margin: 0 0 3px; font-size: 10px; color: #777;} 175 | .state, .size {font-weight: bold; font-size: 10px;} 176 | } 177 | nav.col { 178 | @include flex(); 179 | text-align: right; 180 | } 181 | } 182 | &.selected:after { 183 | @include drop-area(); 184 | } 185 | } 186 | &.loading { 187 | > div { 188 | @include d-flex(true); 189 | .col { 190 | @include flex(1); 191 | padding: 5px; 192 | } 193 | figure.col {display: none;} 194 | .bar { 195 | position: absolute; padding: 0; 196 | left: 0; right: 0; top: 0; bottom: 0; 197 | > p { 198 | position: relative; margin: 0; height: $min-size; 199 | background: $color-key; 200 | white-space: nowrap; overflow: hidden; 201 | } 202 | span { 203 | position: absolute; display: block; 204 | right: 8px; top: 50%; margin-top: -6px; 205 | font-size: 11px; color: #fff; line-height: normal; 206 | } 207 | } 208 | .bd { 209 | margin: 0; padding: 10px 8px 10px 10px; 210 | font-size: 12px; color: #222; word-break: break-all; 211 | hr {display: none;} 212 | .state:before {content: ' - ';} 213 | } 214 | nav.col { 215 | @include flex(); 216 | text-align: right; 217 | } 218 | } 219 | &.ready { 220 | .bar {display: none;} 221 | .bd {color: #888;} 222 | } 223 | } 224 | &.error { 225 | padding: 10px; 226 | background: #CE3E3E; 227 | color: #fff; 228 | font-size: 12px; 229 | word-break: break-all; 230 | figure.col { 231 | display: none; 232 | } 233 | .bd { 234 | margin: 0; 235 | .state:before {content: ' - ';} 236 | hr {display: none;} 237 | } 238 | } 239 | } 240 | @media (max-width: $mobile-max-width) { 241 | li { 242 | &.complete { 243 | > div { 244 | position: relative; display: block; 245 | padding-left: $size; 246 | figure.col { 247 | position: absolute; left: 0; top: 0; bottom: 0; height: auto; 248 | } 249 | .bd { 250 | padding: 8px 5px 8px 10px; 251 | } 252 | nav.col { 253 | display: flex; 254 | align-items: center; 255 | text-align: center; 256 | margin: 0 10px; 257 | padding: 0; 258 | button { 259 | padding: 8px 0 12px; 260 | flex: 1; 261 | } 262 | } 263 | } 264 | } 265 | } 266 | } 267 | } 268 | 269 | // style - album 270 | > .style-album { 271 | display: -webkit-flex; display: flex; 272 | -webkit-flex-wrap: wrap; flex-wrap: wrap; 273 | padding: 5px; 274 | li { 275 | width: 20%; padding: 5px; 276 | @media (max-width: 1024px) { & {width: 20%;} } 277 | @media (max-width: 768px) { & {width: 25%;} } 278 | @media (max-width: 480px) { & {width: 50%;} } 279 | > div { 280 | position: relative; overflow: hidden; 281 | box-shadow: 0 1px 8px rgba(0,0,0,.15); 282 | figure.col { 283 | margin: 0; min-height: 80px; height: 10vw; max-height: 150px; 284 | background: no-repeat 50% 50%; background-size: cover; 285 | &.not-image { 286 | position: relative; background: #999; 287 | &:after { 288 | content: 'no img'; position: absolute; 289 | left: 0; right: 0; top: 50%; margin-top: -6px; 290 | font-family: $font-eng-basic; color: #ddd; font-size: 11px; 291 | text-align: center; 292 | } 293 | } 294 | } 295 | .bd { 296 | display: block; 297 | margin: 8px; font-family: $font-eng-basic; 298 | word-break: break-all; 299 | font-size: 11px; line-height: 13px; color: #222; 300 | 301 | hr {margin: 0; height: 4px; border: none; background: none;} 302 | .filetype {display: block; margin: 0 0 2px 0; font-size: 10px; color: #888;} 303 | .state, .size {font-weight: bold; font-size: 10px;} 304 | } 305 | nav.col { 306 | @include d-flex(true); 307 | border-top: 1px solid #eee; 308 | margin: 0 0 0; padding: 0; 309 | text-align: center; white-space: nowrap; 310 | button { 311 | padding: 8px 0; 312 | @include flex(1); 313 | } 314 | } 315 | } 316 | &.selected { 317 | > div:after { 318 | @include drop-area(); 319 | } 320 | } 321 | &.complete { 322 | figure.col {text-indent: -9999px;} 323 | } 324 | &.loading { 325 | figure.col { 326 | background: #eee; 327 | p { 328 | margin: 0; position: relative; height: 100%; 329 | background: $color-key; 330 | span { 331 | position: absolute; display: block; 332 | right: 8px; top: 50%; margin-top: -6px; 333 | font-size: 11px; color: #fff; line-height: normal; 334 | } 335 | } 336 | &:after { 337 | display: none; 338 | } 339 | } 340 | .bar { 341 | display: none; 342 | } 343 | &.ready { 344 | .bd { 345 | color: #888; 346 | } 347 | } 348 | } 349 | &.error { 350 | figure.col { 351 | background: $color-error; 352 | } 353 | } 354 | } 355 | } 356 | &.is-large-size { 357 | > .style-album { 358 | > li { 359 | @media (min-width: 1440px) { & {width: 16.6666%;} } 360 | @media (min-width: 1920px) { & {width: 12.5%;} } 361 | @media (min-width: 2400px) { & {width: 10%;} } 362 | } 363 | } 364 | } 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /upload/json/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id" : 8742867877, 4 | "name" : "img-demo-1.jpg", 5 | "size" : 53710, 6 | "src" : "./assets/img-demo-1.jpg", 7 | "type" : "image/jpeg" 8 | }, 9 | { 10 | "id" : 6860860674, 11 | "name" : "img-demo-2.jpg", 12 | "size" : 129454, 13 | "src" : "./assets/img-demo-2.jpg", 14 | "type" : "image/jpeg" 15 | }, 16 | { 17 | "id" : 5860269672, 18 | "name" : "img-demo-3.jpg", 19 | "size" : 103811, 20 | "src" : "./assets/img-demo-3.jpg", 21 | "type" : "image/jpeg" 22 | } 23 | ] -------------------------------------------------------------------------------- /upload/script-node/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const express = require('express'); 3 | const multer = require('multer'); 4 | const detect = require('detect-file-type'); 5 | const queryString = require('query-string'); 6 | const move = require('./move'); 7 | 8 | const dir_tmp = './upload/tmp'; 9 | const dir_dest = './upload/attachments'; 10 | const url_dest = '/attachments'; 11 | 12 | /** 13 | * get file list 14 | * 15 | * @param {Array} files 16 | * @return {Promise} 17 | */ 18 | async function getFileList(files) 19 | { 20 | return new Promise(function(resolve, reject){ 21 | let result = []; 22 | try 23 | { 24 | function push(n) 25 | { 26 | detect.fromFile(`${dir_dest}/${files[n]}`, function(err, type) { 27 | let fileState = fs.statSync(`${dir_dest}/${files[n]}`); 28 | result.push({ 29 | id: getRandomNumber(11111, 90000) + n, 30 | name: files[n], 31 | size: fileState.size, 32 | src: `${url_dest}/${files[n]}`, 33 | type: type ? type.mime : null, 34 | }); 35 | if (files.length - 1 > n) 36 | { 37 | push(n+1); 38 | } 39 | else 40 | { 41 | resolve(result); 42 | } 43 | }); 44 | } 45 | 46 | push(0); 47 | } 48 | catch(e) 49 | { 50 | reject(e); 51 | } 52 | }); 53 | } 54 | 55 | /** 56 | * get random number 57 | * 58 | * @param {Number} min 59 | * @param {Number} max 60 | * @return {Number} 61 | */ 62 | function getRandomNumber(min, max) 63 | { 64 | return Math.floor(Math.random() * (max - min)) + min; 65 | } 66 | 67 | 68 | // routes 69 | module.exports = function(app) 70 | { 71 | // get data 72 | app.get('/data', function(req, res) { 73 | try 74 | { 75 | // make dir 76 | if (!fs.existsSync(`${dir_dest}/`)) 77 | { 78 | fs.mkdirSync(dir_dest); 79 | } 80 | // get files 81 | let files = fs.readdirSync(`${dir_dest}/`); 82 | if (files.length) 83 | { 84 | getFileList(files).then((result) => { 85 | return res.json(result); 86 | }).catch((e) => { 87 | console.error(e); 88 | return res.json([]); 89 | }); 90 | } 91 | else 92 | { 93 | return res.json([]); 94 | } 95 | } 96 | catch(e) 97 | { 98 | console.error(e); 99 | return res.json([]); 100 | } 101 | }); 102 | 103 | // attachment files 104 | app.use('/attachments', express.static('upload/attachments')); 105 | 106 | // upload file 107 | app.post('/upload', multer({ dest: dir_tmp }).single('file'), function(req, res) { 108 | // make dir 109 | if (!fs.existsSync(`${dir_dest}/`)) 110 | { 111 | fs.mkdirSync(dir_dest); 112 | } 113 | 114 | move(req.file.path, `${dir_dest}/${req.file.originalname}`, function(err) { 115 | if (err) 116 | { 117 | return res.json({ state: 'error', message: 'Upload failed', error: err }); 118 | } 119 | return res.json({ 120 | state: 'success', 121 | response: { 122 | src: `${url_dest}/${req.file.originalname}`, 123 | name: req.file.originalname, 124 | } 125 | }); 126 | }); 127 | }); 128 | 129 | // remove file 130 | app.post('/remove', function(req, res) { 131 | let bodyStr = ''; 132 | let body = null; 133 | 134 | req.on('data', function(chunk){ 135 | bodyStr += chunk.toString(); 136 | }); 137 | req.on('end', function(){ 138 | try 139 | { 140 | body = queryString.parse(bodyStr); 141 | fs.unlinkSync(`${dir_dest}/${body.name}`); 142 | res.json({ state: 'success' }); 143 | } 144 | catch(e) 145 | { 146 | console.error(e); 147 | return res.json({ state: 'error' }); 148 | } 149 | }); 150 | }); 151 | }; 152 | -------------------------------------------------------------------------------- /upload/script-node/move.js: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/a/29105404 2 | const fs = require('fs'); 3 | 4 | 5 | module.exports = function move(oldPath, newPath, callback) 6 | { 7 | fs.rename(oldPath, newPath, function (err) { 8 | if (err) 9 | { 10 | if (err.code === 'EXDEV') 11 | { 12 | copy(); 13 | } 14 | else 15 | { 16 | callback(err); 17 | } 18 | return; 19 | } 20 | callback(); 21 | }); 22 | 23 | function copy() { 24 | const readStream = fs.createReadStream(oldPath); 25 | const writeStream = fs.createWriteStream(newPath); 26 | 27 | readStream.on('error', callback); 28 | writeStream.on('error', callback); 29 | 30 | readStream.on('close', function () { 31 | fs.unlink(oldPath, callback); 32 | }); 33 | 34 | readStream.pipe(writeStream); 35 | } 36 | }; -------------------------------------------------------------------------------- /upload/script-php/data.php: -------------------------------------------------------------------------------- 1 | 8742867877, 11 | "name" => "https://unsplash.com/?photo=67t2GJcD5PI", 12 | "size" => 863489, 13 | "src" => "./upload/aaa.jpg", 14 | "type" => "image/jpeg" 15 | ], 16 | [ 17 | "id" => 6860860674, 18 | "name" => "aaaa.jpg", 19 | "size" => 506647, 20 | "src" => "./upload/aaaa.jpg", 21 | "type" => "image/jpeg" 22 | ] 23 | ]; 24 | 25 | // print 26 | echo json_encode($result, JSON_PRETTY_PRINT); -------------------------------------------------------------------------------- /upload/script-php/remove.php: -------------------------------------------------------------------------------- 1 | $type, 18 | 'response' => $response 19 | ], JSON_PRETTY_PRINT); 20 | exit; 21 | } 22 | 23 | 24 | // remove 25 | if (isset($_POST['name']) && file_exists($pwd.'/'.$_POST['name'])) 26 | { 27 | unlink($pwd.'/'.$_POST['name']); 28 | result('success', []); 29 | } 30 | else 31 | { 32 | result('error', []); 33 | } 34 | -------------------------------------------------------------------------------- /upload/script-php/upload.php: -------------------------------------------------------------------------------- 1 | 'not found $_FILES[file]' ]); 18 | } 19 | 20 | 21 | // set file value 22 | $file = $_FILES['file']; 23 | 24 | 25 | // result render 26 | function result($type, $response) 27 | { 28 | echo json_encode([ 29 | 'state' => $type, 30 | 'response' => $response 31 | ], JSON_PRETTY_PRINT); 32 | exit; 33 | } 34 | 35 | // make unique filename 36 | function makeFilename() 37 | { 38 | global $file; 39 | return strtotime(date('Y-m-d H:i:s')).'-'.rand(10000,99999).'.'.pathinfo($file['name'], PATHINFO_EXTENSION); 40 | } 41 | 42 | // detect mobile 43 | function detectMobile() 44 | { 45 | return $isMobile = (bool)preg_match('#\b(ip(hone|od|ad)|android|opera m(ob|in)i|windows (phone|ce)|blackberry|tablet'. 46 | '|s(ymbian|eries60|amsung)|p(laybook|alm|rofile/midp|laystation portable)|nokia|fennec|htc[\-_]'. 47 | '|mobile|up\.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\b#i', $_SERVER['HTTP_USER_AGENT'] ); 48 | } 49 | 50 | 51 | // check directory 52 | if (!is_dir($pwd)) 53 | { 54 | if (is_writable($pwd_root)) 55 | { 56 | $umask = umask(); 57 | umask(000); 58 | mkdir($pwd, 0707); 59 | umask($umask); 60 | } 61 | 62 | if (!is_dir($pwd)) 63 | { 64 | result('error', [ 'message' => 'not exist "'.$pwd.'" directory' ]); 65 | } 66 | } 67 | 68 | 69 | // set filename 70 | if (detectMobile()) 71 | { 72 | $filename = makeFilename(); 73 | } 74 | else 75 | { 76 | $filename = preg_replace("/\s+/", "_", $file['name']); 77 | } 78 | 79 | 80 | // file check 81 | if (file_exists($pwd.'/'.$filename)) 82 | { 83 | result('error', [ 'message' => 'file exists' ]); 84 | } 85 | 86 | // upload file 87 | if (!move_uploaded_file($file['tmp_name'], $pwd.'/'.$filename)) 88 | { 89 | result('error', [ 'message' => 'file upload fail' ]); 90 | } 91 | 92 | // DB UPDATE 93 | 94 | // goal 95 | result('success', [ 96 | 'db_id' => rand(10000,99999), 97 | 'src' => $dir.'/'.$filename, 98 | 'name' => $filename 99 | ]); 100 | -------------------------------------------------------------------------------- /upload/script-php/upload_base64.php: -------------------------------------------------------------------------------- 1 | $type, 20 | 'response' => $response 21 | ], JSON_PRETTY_PRINT); 22 | exit; 23 | } 24 | 25 | // make unique filename 26 | function generateRandomString($length = 10) 27 | { 28 | $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 29 | $charactersLength = strlen($characters); 30 | $randomString = ''; 31 | for ($i = 0; $i < $length; $i++) 32 | { 33 | $randomString .= $characters[rand(0, $charactersLength - 1)]; 34 | } 35 | return $randomString; 36 | } 37 | 38 | 39 | // check post value 40 | if (!$_POST['name'] || !$_POST['image'] || !$_POST['id']) 41 | { 42 | result('error', ['message' => 'not found $_POST']); 43 | } 44 | 45 | 46 | // check directory 47 | if (!is_dir($pwd)) 48 | { 49 | if (is_writable($pwd_root)) 50 | { 51 | $umask = umask(); 52 | umask(000); 53 | mkdir($pwd, 0707); 54 | umask($umask); 55 | } 56 | 57 | if (!is_dir($pwd)) 58 | { 59 | result('error', ['message' => 'not exist "' . $pwd . '" directory']); 60 | } 61 | } 62 | 63 | 64 | // adjust image data 65 | $imgData = str_replace('data:image/jpeg;base64,', '', $_POST['image']); 66 | $imgData = str_replace(' ', '+', $imgData); 67 | // set filename 68 | $filename = $prefix . generateRandomString(15) . '.' . pathinfo($_POST['name'])['extension']; 69 | // set location 70 | $loc = $pwd . '/' . $filename; 71 | 72 | // upload image 73 | $result = file_put_contents($loc, base64_decode($imgData)); 74 | 75 | 76 | // print result 77 | if ($result) 78 | { 79 | result('success', [ 80 | 'id' => (int)$_POST['id'], 81 | 'name' => $filename, 82 | 'src' => $dir . '/' . $filename, 83 | 'size' => filesize($loc), 84 | 'type' => mime_content_type($loc) 85 | ]); 86 | } 87 | else 88 | { 89 | result('error', ['message' => 'upload error']); 90 | } 91 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebPackPlugin = require("html-webpack-plugin"); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const TerserJSPlugin = require('terser-webpack-plugin'); 5 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 6 | 7 | const coreConfig = (env, options) => { 8 | const isDev = options.mode === 'development'; 9 | let config = { 10 | mode: isDev ? options.mode : 'production', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.s?css$/, 15 | use: [ 16 | { 17 | loader: MiniCssExtractPlugin.loader, 18 | options: { 19 | hmr: isDev, 20 | }, 21 | }, 22 | 'css-loader', 23 | 'sass-loader', 24 | ], 25 | }, 26 | { 27 | test: /\.(eot|ttf|woff|woff2)$/, 28 | use: [ 29 | { 30 | loader: 'file-loader', 31 | options: { 32 | publicPath: './', 33 | name: '[name].[ext]', 34 | limit: 10000, 35 | } 36 | } 37 | ] 38 | }, 39 | ], 40 | }, 41 | plugins: [], 42 | optimization: {}, 43 | }; 44 | 45 | if (isDev) 46 | { 47 | /** 48 | * development 49 | */ 50 | config.entry = { 51 | app: ['./dev/index.js'], 52 | }; 53 | config.output = { 54 | publicPath: '/', 55 | filename: '[name].js', 56 | chunkFilename: '[name].js', 57 | }; 58 | config.devtool = 'inline-source-map'; 59 | config.devServer = { 60 | hot: true, 61 | host: '0.0.0.0', 62 | port: options.port || 3000, 63 | contentBase: path.resolve(__dirname, '.cache/dist'), 64 | stats: { 65 | color: true, 66 | }, 67 | before: require('./upload/script-node'), 68 | historyApiFallback: true, 69 | noInfo: true, 70 | }; 71 | config.module.rules = Object.assign([], config.module.rules, { 72 | test: /\.html$/, 73 | use: [ 74 | { 75 | loader: "html-loader", 76 | options: { 77 | minimize: false 78 | } 79 | } 80 | ] 81 | }); 82 | config.plugins = [ 83 | new HtmlWebPackPlugin({ 84 | template: './dev/index.html', 85 | showErrors: true, 86 | }), 87 | new MiniCssExtractPlugin({ filename: 'rg-uploader.css' }), 88 | ]; 89 | } 90 | else 91 | { 92 | /** 93 | * production 94 | */ 95 | config.entry = { 96 | core: './src/rg-uploader.js', 97 | }; 98 | config.output = { 99 | filename: 'rg-uploader.js', 100 | path: path.resolve(__dirname, 'dist'), 101 | publicPath: './', 102 | library: ['RG_Uploader', 'core'], 103 | libraryTarget: 'umd', 104 | libraryExport: 'default', 105 | }; 106 | config.externals = { 107 | jquery: 'jQuery', 108 | }; 109 | config.plugins = [ 110 | new MiniCssExtractPlugin({ filename: 'rg-uploader.css' }), 111 | ]; 112 | config.optimization = { 113 | minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})], 114 | }; 115 | } 116 | 117 | return config; 118 | }; 119 | 120 | const pluginsConfig = () => ({ 121 | mode: 'production', 122 | entry: './src/plugins/index.js', 123 | output: { 124 | filename: 'rg-uploader.plugins.js', 125 | path: path.resolve(__dirname, 'dist'), 126 | publicPath: './', 127 | library: ['RG_Uploader', 'plugins'], 128 | libraryTarget: 'umd', 129 | }, 130 | externals: { 131 | jquery: 'jQuery', 132 | }, 133 | optimization: { 134 | minimizer: [ 135 | new TerserJSPlugin({}), 136 | new OptimizeCSSAssetsPlugin({}), 137 | ], 138 | }, 139 | }); 140 | 141 | 142 | module.exports = [ 143 | coreConfig, 144 | pluginsConfig, 145 | ]; 146 | --------------------------------------------------------------------------------