designer 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +2 -0
  3. data/.gitignore +4 -0
  4. data/.travis.yml +17 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +142 -0
  7. data/LICENSE +21 -0
  8. data/README.md +102 -0
  9. data/Rakefile +27 -0
  10. data/TODO.md +6 -0
  11. data/app/assets/stylesheets/designer.scss +189 -0
  12. data/app/assets/stylesheets/designer/_texy.scss +311 -0
  13. data/app/controllers/designer/application_controller.rb +33 -0
  14. data/app/controllers/designer/editor_controller.rb +50 -0
  15. data/app/controllers/designer/images_controller.rb +66 -0
  16. data/app/helpers/designer/designer_helper.rb +95 -0
  17. data/app/javascript/designer/DefaultInput.vue +82 -0
  18. data/app/javascript/designer/components/DefaultForm.vue +65 -0
  19. data/app/javascript/designer/components/DefaultInput.vue +82 -0
  20. data/app/javascript/designer/components/MediaGallery.vue +564 -0
  21. data/app/javascript/designer/components/SortableInputArray.vue +74 -0
  22. data/app/javascript/designer/editor.js +65 -0
  23. data/app/javascript/designer/helpers.js +73 -0
  24. data/app/javascript/designer/index.js +136 -0
  25. data/app/javascript/packs/designer.js +2 -0
  26. data/app/views/designer/application/notifications.js.erb +13 -0
  27. data/app/views/designer/editor/show.html.slim +88 -0
  28. data/app/views/designer/elements/_image.html.slim +11 -0
  29. data/app/views/designer/elements/_quote.html.slim +7 -0
  30. data/app/views/designer/elements/_separator.html.slim +1 -0
  31. data/app/views/designer/elements/_text.html.slim +5 -0
  32. data/app/views/layouts/designer/application.html.slim +9 -0
  33. data/bin/test +6 -0
  34. data/bin/webpack +19 -0
  35. data/bin/webpack-dev-server +19 -0
  36. data/config/routes.rb +6 -0
  37. data/designer.gemspec +29 -0
  38. data/lib/designer.rb +10 -0
  39. data/lib/designer/attribute.rb +15 -0
  40. data/lib/designer/configuration.rb +6 -0
  41. data/lib/designer/engine.rb +35 -0
  42. data/lib/designer/version.rb +5 -0
  43. data/lib/tasks/designer.rake +20 -0
  44. data/lib/templates/dev_installer.rb +13 -0
  45. data/lib/templates/installer.rb +11 -0
  46. data/package.json +33 -0
  47. data/yarn.lock +288 -0
  48. metadata +160 -0
@@ -0,0 +1,95 @@
1
+ module Designer::DesignerHelper
2
+
3
+ def designer_embed element_id
4
+ element = designer_resource.elements.find{|x| x['id'] == element_id }
5
+ return "Cannot find element `#{element_id}`" unless element
6
+
7
+ designer_render element
8
+ end
9
+
10
+ def designer_render element
11
+ element = element.symbolize_keys
12
+ template_path = designer_option(:elements_template_path)
13
+ if lookup_context.exists?(element[:template], designer_option(:elements_template_path), true)
14
+ render "#{template_path}/#{element[:template]}", designer_element_options(element)
15
+ elsif lookup_context.exists?(element[:template], 'designer/elements', true)
16
+ render "designer/elements/#{element[:template]}", designer_element_options(element)
17
+ else
18
+ raise "Missing designer template `#{element[:template]}`"
19
+ end
20
+ end
21
+
22
+ def designer_render_resource resource
23
+ return unless resource&.elements
24
+ designer_set_resource resource
25
+ resource.elements.each_with_object('') do |element, html|
26
+ next if element['hidden'] || element[:hidden]
27
+ result = designer_render element
28
+ html << result if result
29
+ end.html_safe
30
+ end
31
+
32
+ def designer_element_options element
33
+ element.symbolize_keys!
34
+ options = {}
35
+ options.merge!(element[:values]) if element[:values]
36
+ options.symbolize_keys
37
+ end
38
+
39
+ def designer_attachment image_key
40
+ ActiveStorage::Attachment.joins(:blob).where(active_storage_blobs: {key: image_key}).first
41
+ end
42
+
43
+ # Render input text embeds and ERB
44
+ def designer_content text, context={}
45
+ return if text.blank?
46
+ ApplicationController.render inline: text,
47
+ assigns: { _resource: designer_resource }.merge(context)
48
+ end
49
+
50
+ # Render input text as markdown
51
+ def designer_markdown text, options = {}
52
+ return if text.blank?
53
+ Kramdown::Document.new(text, {
54
+ syntax_highlighter_opts: {
55
+ line_numbers: nil
56
+ }
57
+ }.merge(options)).to_html
58
+ end
59
+
60
+ def designer_preview_path
61
+ path = designer_option(:preview_path) || ':resource_name/:id'
62
+ path_parts = path.split('/')
63
+ path_parts.map! do |part|
64
+ if part[0] == ':'
65
+ param = part[1..-1]
66
+ if params[param]
67
+ params[param]
68
+ else
69
+ designer_resource.send(param)
70
+ end
71
+ else
72
+ part
73
+ end
74
+ end
75
+ path_parts.join('/')
76
+ end
77
+
78
+ def designer_option key
79
+ Designer.configuration[designer_resource_name][key]
80
+ rescue
81
+ raise "Mismatch designer option `#{val}` for `#{designer_resource_name}`"
82
+ end
83
+
84
+ def designer_resource_name
85
+ designer_resource&.model_name&.route_key || params[:resource_name]
86
+ end
87
+
88
+ def designer_set_resource resource
89
+ @_resource = resource
90
+ end
91
+
92
+ def designer_resource
93
+ @_resource || @resource
94
+ end
95
+ end
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <div class="w-100">
3
+ <div v-if="spec.type == 'string' && spec.multiline" class="form-group">
4
+ <a href="#" @click="openMarkdownEditor('#textarea-' + object.id)" class="float-right"><i class="fa fa-edit"></i></a>
5
+ <label :for="'textarea-' + object.id" class="control-label">{{ spec.label || name | titleize }}</label>
6
+ <textarea :id="'textarea-' + object.id" v-model="object[name]" :placeholder="spec.placeholder" @change="$emit('change')" rows="5" class="form-control"></textarea>
7
+
8
+ <!-- <div class="col-sm-3 col-form-label">
9
+ {{ spec.label || name | titleize }}
10
+ <small><a href="#" @click="openMarkdownEditor('#textarea-' + object.id)">Editor</a></small>
11
+ </div>
12
+ <div class="col-sm-9">
13
+ <textarea :id="'textarea-' + object.id" :type="spec.type" v-model="object[name]" :placeholder="spec.placeholder" @change="$emit('change')" class="form-control"></textarea>
14
+ </div> -->
15
+ </div>
16
+ <div v-else-if="spec.type == 'array'" class="form-group form-row">
17
+ <label class="col-sm-3 col-form-label">{{ spec.label || name | titleize }}</label>
18
+ <div class="col-sm-9">
19
+ <tags-input element-id="tags"
20
+ v-model="object[name]"
21
+ :existing-tags="tagArrayToObject(spec.enum)"
22
+ :typeahead="true"
23
+ :typeahead-activation-threshold="0"
24
+ :only-existing-tags="!spec.custom"></tags-input>
25
+ </div>
26
+ </div>
27
+ <div v-else-if="spec.enum" class="form-group form-row">
28
+ <label class="col-sm-3 col-form-label">{{ spec.label || name | titleize }}</label>
29
+ <div class="col-sm-9">
30
+ <select v-model="object[name]" class="form-control">
31
+ <option value="">Please select one</option>
32
+ <option v-for="(value, index) in spec.enum">{{ value }}</option>
33
+ </select>
34
+ </div>
35
+ </div>
36
+ <div v-else-if="spec.type == 'boolean'" class="form-check">
37
+ <input type="checkbox" value="true" :id="name" v-model="object[name]" @change="$emit('change')" class="form-check-input">
38
+ <label class="form-check-label" :for="name">{{ spec.label || name | titleize }}</label>
39
+ </div>
40
+ <div v-else class="form-group form-row">
41
+ <label class="col-sm-3 col-form-label">{{ spec.label || name | titleize }}</label>
42
+ <div class="col-sm-9">
43
+ <input :type="spec.type" v-model="object[name]" :placeholder="spec.placeholder" class="form-control">
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </template>
48
+
49
+ <script>
50
+ import Vue from 'vue/dist/vue.esm.js'
51
+ import VoerroTagsInput from '@voerro/vue-tagsinput'
52
+ // import Helpers from '../helpers'
53
+
54
+ Vue.component('tags-input', VoerroTagsInput)
55
+
56
+ export default {
57
+ props: ['spec', 'name', 'item'],
58
+ components: {
59
+ VoerroTagsInput
60
+ },
61
+ data() {
62
+ return {
63
+ object: this.item || {}
64
+ }
65
+ },
66
+ // beforeMount() {
67
+ // // console.log('DefaultInput', this.object, this.spec.default)
68
+ // // this.object[this.name] = this.object[this.name] || this.spec.default
69
+ // },
70
+ // created() {
71
+ // console.log('DefaultInput', this, this.spec, this.name, this.item)
72
+ // },
73
+ methods: {
74
+ tagArrayToObject(tags) {
75
+ return tags.reduce((acc, cur, i) => {
76
+ acc[cur] = cur
77
+ return acc
78
+ }, {})
79
+ }
80
+ }
81
+ }
82
+ </script>
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <form v-bind:id="item.id" v-on:dragstart="onDragElement">
3
+ <div class="card">
4
+ <div class="card-header d-flex align-items-center drag-handle">
5
+ <a data-toggle="collapse" href="#" v-bind:data-target="'#collapse-' + item.id" class="title px-0 flex-fill text-left">{{ item.title || spec.label }}</a>
6
+ <input type="checkbox" class="mr-3" :checked="!item.hidden" @change="item.hidden = !item.hidden">
7
+ <div class="dropdown">
8
+ <button data-toggle="dropdown" type="button" class="btn btn-sm btn-secondary dropdown-toggle"></button>
9
+ <div class="dropdown-menu">
10
+ <a href="#" class="dropdown-item" @click="copyToClipboard(getEmbedCode(item.id))">Copy embed code</a>
11
+ <a href="#" class="dropdown-item" @click="$emit('remove')">Delete</a>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ <div v-bind:id="'collapse-' + item.id" class="collapse">
16
+ <div class="card-body" v-for="(property, name) in spec.properties" :key="property.name">
17
+ <!--
18
+ <br>property: -------------------------
19
+ {{property}}
20
+ <br>item: >>>
21
+ <br>{{item}}
22
+ <br>object: >>>
23
+ <br>{{object}} -->
24
+ <SortableInputArray
25
+ v-if="property.type == 'array' && property.properties"
26
+ v-bind:item="object"
27
+ v-bind:spec="property">
28
+ </SortableInputArray>
29
+ <DefaultInput
30
+ v-else
31
+ v-bind:name="name"
32
+ v-bind:item="object"
33
+ v-bind:spec="property"></DefaultInput>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </form>
38
+ </template>
39
+
40
+ <script>
41
+ import DefaultInput from './DefaultInput.vue'
42
+ import SortableInputArray from './SortableInputArray.vue'
43
+
44
+ export default {
45
+ props: ['item', 'spec'],
46
+ components: {
47
+ DefaultInput, SortableInputArray
48
+ },
49
+ data() {
50
+ return {
51
+ object: this.item && this.item.values ? this.item.values : {}
52
+ }
53
+ },
54
+ created() {
55
+ console.log('DefaultForm', this, this.item, this.spec)
56
+ },
57
+ methods: {
58
+ onDragElement(event) {
59
+ console.log('onDragElement', event, this)
60
+ event.dataTransfer.setData('id', this.item.id)
61
+ event.dataTransfer.setData('embed', this.getEmbedCode(this.item.id))
62
+ }
63
+ }
64
+ }
65
+ </script>
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <div class="w-100">
3
+ <div v-if="spec.type == 'string' && spec.multiline" class="form-group">
4
+ <a href="#" @click="openMarkdownEditor('#textarea-' + object.id)" class="float-right"><i class="fa fa-edit"></i></a>
5
+ <label :for="'textarea-' + object.id" class="control-label">{{ spec.label || name | titleize }}</label>
6
+ <textarea :id="'textarea-' + object.id" v-model="object[name]" :placeholder="spec.placeholder" @change="$emit('change')" rows="5" class="form-control"></textarea>
7
+
8
+ <!-- <div class="col-sm-3 col-form-label">
9
+ {{ spec.label || name | titleize }}
10
+ <small><a href="#" @click="openMarkdownEditor('#textarea-' + object.id)">Editor</a></small>
11
+ </div>
12
+ <div class="col-sm-9">
13
+ <textarea :id="'textarea-' + object.id" :type="spec.type" v-model="object[name]" :placeholder="spec.placeholder" @change="$emit('change')" class="form-control"></textarea>
14
+ </div> -->
15
+ </div>
16
+ <div v-else-if="spec.type == 'array'" class="form-group form-row">
17
+ <label class="col-sm-3 col-form-label">{{ spec.label || name | titleize }}</label>
18
+ <div class="col-sm-9">
19
+ <tags-input element-id="tags"
20
+ v-model="object[name]"
21
+ :existing-tags="tagArrayToObject(spec.enum)"
22
+ :typeahead="true"
23
+ :typeahead-activation-threshold="0"
24
+ :only-existing-tags="!spec.custom"></tags-input>
25
+ </div>
26
+ </div>
27
+ <div v-else-if="spec.enum" class="form-group form-row">
28
+ <label class="col-sm-3 col-form-label">{{ spec.label || name | titleize }}</label>
29
+ <div class="col-sm-9">
30
+ <select v-model="object[name]" class="form-control">
31
+ <option value="">Please select one</option>
32
+ <option v-for="(value, index) in spec.enum">{{ value }}</option>
33
+ </select>
34
+ </div>
35
+ </div>
36
+ <div v-else-if="spec.type == 'boolean'" class="form-check">
37
+ <input type="checkbox" value="true" :id="name" v-model="object[name]" @change="$emit('change')" class="form-check-input">
38
+ <label class="form-check-label" :for="name">{{ spec.label || name | titleize }}</label>
39
+ </div>
40
+ <div v-else class="form-group form-row">
41
+ <label class="col-sm-3 col-form-label">{{ spec.label || name | titleize }}</label>
42
+ <div class="col-sm-9">
43
+ <input :type="spec.type" v-model="object[name]" :placeholder="spec.placeholder" class="form-control">
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </template>
48
+
49
+ <script>
50
+ import Vue from 'vue/dist/vue.esm.js'
51
+ import VoerroTagsInput from '@voerro/vue-tagsinput'
52
+ // import Helpers from '../helpers'
53
+
54
+ Vue.component('tags-input', VoerroTagsInput)
55
+
56
+ export default {
57
+ props: ['spec', 'name', 'item'],
58
+ components: {
59
+ VoerroTagsInput
60
+ },
61
+ data() {
62
+ return {
63
+ object: this.item || {}
64
+ }
65
+ },
66
+ // beforeMount() {
67
+ // // console.log('DefaultInput', this.object, this.spec.default)
68
+ // // this.object[this.name] = this.object[this.name] || this.spec.default
69
+ // },
70
+ // created() {
71
+ // console.log('DefaultInput', this, this.spec, this.name, this.item)
72
+ // },
73
+ methods: {
74
+ tagArrayToObject(tags) {
75
+ return tags.reduce((acc, cur, i) => {
76
+ acc[cur] = cur
77
+ return acc
78
+ }, {})
79
+ }
80
+ }
81
+ }
82
+ </script>
@@ -0,0 +1,564 @@
1
+ <template>
2
+ <div class="image-uploader">
3
+ <!--
4
+ <button type="button" class="btn btn-danger float-right btn-is-option" @click.prevent="showOptions = !showOptions">
5
+ <i class="fa fa-cog" aria-hidden="true"></i>
6
+ Options
7
+ </button>
8
+ <h1 id="example-title" class="example-title">Full Example</h1>
9
+ -->
10
+
11
+ <div v-show="$refs.upload && $refs.upload.dropActive" class="drop-active">
12
+ <h3>Drop files to upload</h3>
13
+ </div>
14
+ <div class="upload">
15
+ <!-- v-show="!showOptions" -->
16
+ <div class="table-responsive">
17
+ <table class="table table-hover">
18
+ <thead>
19
+ <tr>
20
+ <!-- <th>#</th> -->
21
+ <th></th>
22
+ <th>Name</th>
23
+ <th>Size</th>
24
+ <!-- <th>Speed</th> -->
25
+ <th>Status</th>
26
+ <th></th>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ <tr v-if="!files.length">
31
+ <td colspan="7">
32
+ <div class="text-center p-5">
33
+ <h4>Drop files anywhere to upload<br/>or</h4>
34
+ <label :for="name" class="btn btn-lg btn-success">Select Files</label>
35
+ </div>
36
+ </td>
37
+ </tr>
38
+ <tr v-for="(file, index) in files" :key="file.id">
39
+ <!-- <td>{{index}}</td> -->
40
+ <td>
41
+ <img v-if="file.thumbnail || file.thumbnail_url" :src="file.thumbnail || file.thumbnail_url" width="50" height="auto" />
42
+ <span v-else>No Image</span>
43
+ </td>
44
+ <td>
45
+ <div class="filename">
46
+ {{file.name}}<br>
47
+ <em>{{file.kind}}</em>
48
+ </div>
49
+ <div class="progress" v-if="file.active || file.progress && file.progress !== '0.00'">
50
+ <div :class="{'progress-bar': true, 'progress-bar-striped': true, 'bg-danger': file.error, 'progress-bar-animated': file.active}" role="progressbar" :style="{width: file.progress + '%'}">{{file.progress}}%</div>
51
+ </div>
52
+ </td>
53
+ <td>{{file.size | formatSize}}</td>
54
+
55
+ <!--
56
+ <td v-if="file.speed">{{file.speed | formatSize}}</td>
57
+ <td v-else></td>
58
+ -->
59
+
60
+ <td v-if="file.error">{{file.error}}</td>
61
+ <td v-else-if="file.persisted">persisted</td>
62
+ <td v-else-if="file.success">success</td>
63
+ <td v-else-if="file.active">active</td>
64
+ <td v-else></td>
65
+ <td align="right">
66
+ <div class="btn-group btn-group-sm">
67
+ <button class="btn btn-secondary btn-sm dropdown-toggle" data-toggle="dropdown" type="button"></button>
68
+ <div class="dropdown-menu" v-if="!file.persisted && !file.success">
69
+ <!-- <a :class="{'dropdown-item': true, disabled: file.active || file.success || file.error === 'compressing'}" href="#" @click.prevent="file.active || file.success || file.error === 'compressing' ? false : onEditFileShow(file)">Edit</a> -->
70
+ <a :class="{'dropdown-item': true, disabled: !file.active}" href="#" @click.prevent="file.active ? $refs.upload.update(file, {error: 'cancel'}) : false">Cancel</a>
71
+
72
+ <a class="dropdown-item" href="#" v-if="file.active" @click.prevent="$refs.upload.update(file, {active: false})">Abort</a>
73
+ <a class="dropdown-item" href="#" v-else-if="file.error && file.error !== 'compressing' && $refs.upload.features.html5" @click.prevent="$refs.upload.update(file, {active: true, error: '', progress: '0.00'})">Retry upload</a>
74
+ <a :class="{'dropdown-item': true, disabled: file.success || file.error === 'compressing'}" href="#" v-else @click.prevent="file.success || file.error === 'compressing' ? false : $refs.upload.update(file, {active: true})">Upload</a>
75
+
76
+ <div class="dropdown-divider"></div>
77
+ <a class="dropdown-item" href="#" @click.prevent="removeFile(index)">Remove</a>
78
+ </div>
79
+ <div class="dropdown-menu" v-else>
80
+ <a class="dropdown-item" href="#" @click.prevent="copyToClipboard(file.key)">Copy key</a>
81
+
82
+ <div class="dropdown-divider"></div>
83
+ <a class="dropdown-item" href="#" @click.prevent="removeFile(index)">Remove</a>
84
+ </div>
85
+ </div>
86
+ </td>
87
+ </tr>
88
+ </tbody>
89
+ </table>
90
+ </div>
91
+ <div class="gallery-foorer mx-2">
92
+ <div class="btn-group btn-group-sm">
93
+ <file-upload
94
+ class="btn btn-success dropdown-toggle"
95
+ :post-action="postAction"
96
+ :put-action="putAction"
97
+ :extensions="extensions"
98
+ :accept="accept"
99
+ :multiple="multiple"
100
+ :directory="directory"
101
+ :size="size || 0"
102
+ :thread="thread < 1 ? 1 : (thread > 5 ? 5 : thread)"
103
+ :headers="headers"
104
+ :data="data"
105
+ :drop="drop"
106
+ :drop-directory="dropDirectory"
107
+ :add-index="addIndex"
108
+ v-model="files"
109
+ @input-filter="inputFilter"
110
+ @input-file="inputFile"
111
+ ref="upload">
112
+ <!-- <i class="fa fa-plus"></i> -->
113
+ Select
114
+ </file-upload>
115
+ <button type="button" class="btn btn-info" v-if="!$refs.upload || !$refs.upload.active" @click.prevent="$refs.upload.active = true">
116
+ <i class="fa fa-arrow-up" aria-hidden="true"></i>
117
+ Start Upload
118
+ </button>
119
+ <button type="button" class="btn btn-danger" v-else @click.prevent="$refs.upload.active = false">
120
+ <i class="fa fa-stop" aria-hidden="true"></i>
121
+ Stop Upload
122
+ </button>
123
+ </div>
124
+
125
+ <!--
126
+ <div class="footer-status">
127
+ Drop: {{$refs.upload ? $refs.upload.drop : false}},
128
+ Active: {{$refs.upload ? $refs.upload.active : false}},
129
+ Uploaded: {{$refs.upload ? $refs.upload.uploaded : true}},
130
+ Drop active: {{$refs.upload ? $refs.upload.dropActive : false}}
131
+ </div>
132
+ -->
133
+
134
+ </div>
135
+ </div>
136
+
137
+
138
+ <!--
139
+ <div class="option" v-show="showOptions">
140
+ <div class="form-group">
141
+ <label for="accept">Accept:</label>
142
+ <input type="text" id="accept" class="form-control" v-model="accept">
143
+ <small class="form-text text-muted">Allow upload mime type</small>
144
+ </div>
145
+ <div class="form-group">
146
+ <label for="extensions">Extensions:</label>
147
+ <input type="text" id="extensions" class="form-control" v-model="extensions">
148
+ <small class="form-text text-muted">Allow upload file extension</small>
149
+ </div>
150
+ <div class="form-group">
151
+ <label>PUT Upload:</label>
152
+ <div class="form-check">
153
+ <label class="form-check-label">
154
+ <input class="form-check-input" type="radio" name="put-action" id="put-action" value="" v-model="putAction"> Off
155
+ </label>
156
+ </div>
157
+ <div class="form-check">
158
+ <label class="form-check-label">
159
+ <input class="form-check-input" type="radio" name="put-action" id="put-action" value="/upload/put" v-model="putAction"> On
160
+ </label>
161
+ </div>
162
+ <small class="form-text text-muted">After the shutdown, use the POST method to upload</small>
163
+ </div>
164
+ <div class="form-group">
165
+ <label for="thread">Thread:</label>
166
+ <input type="number" max="5" min="1" id="thread" class="form-control" v-model.number="thread">
167
+ <small class="form-text text-muted">Also upload the number of files at the same time (number of threads)</small>
168
+ </div>
169
+ <div class="form-group">
170
+ <label for="size">Max size:</label>
171
+ <input type="number" min="0" id="size" class="form-control" v-model.number="size">
172
+ </div>
173
+ <div class="form-group">
174
+ <label for="minSize">Min size:</label>
175
+ <input type="number" min="0" id="minSize" class="form-control" v-model.number="minSize">
176
+ </div>
177
+ <div class="form-group">
178
+ <label for="autoCompress">Automatically compress:</label>
179
+ <input type="number" min="0" id="autoCompress" class="form-control" v-model.number="autoCompress">
180
+ <small class="form-text text-muted" v-if="autoCompress > 0">More than {{autoCompress | formatSize}} files are automatically compressed</small>
181
+ <small class="form-text text-muted" v-else>Set up automatic compression</small>
182
+ </div>
183
+
184
+ <div class="form-group">
185
+ <div class="form-check">
186
+ <label class="form-check-label">
187
+ <input type="checkbox" id="add-index" class="form-check-input" v-model="addIndex"> Start position to add
188
+ </label>
189
+ </div>
190
+ <small class="form-text text-muted">Add a file list to start the location to add</small>
191
+ </div>
192
+
193
+ <div class="form-group">
194
+ <div class="form-check">
195
+ <label class="form-check-label">
196
+ <input type="checkbox" id="drop" class="form-check-input" v-model="drop"> Drop
197
+ </label>
198
+ </div>
199
+ <small class="form-text text-muted">Drag and drop upload</small>
200
+ </div>
201
+ <div class="form-group">
202
+ <div class="form-check">
203
+ <label class="form-check-label">
204
+ <input type="checkbox" id="drop-directory" class="form-check-input" v-model="dropDirectory"> Drop directory
205
+ </label>
206
+ </div>
207
+ <small class="form-text text-muted">Not checked, filter the dragged folder</small>
208
+ </div>
209
+ <div class="form-group">
210
+ <div class="form-check">
211
+ <label class="form-check-label">
212
+ <input type="checkbox" id="upload-auto" class="form-check-input" v-model="uploadAuto"> Auto start
213
+ </label>
214
+ </div>
215
+ <small class="form-text text-muted">Automatically activate upload</small>
216
+ </div>
217
+ <div class="form-group">
218
+ <button type="button" class="btn btn-success btn-lg btn-block" @click.prevent="showOptions = !showOptions">Confirm</button>
219
+ </div>
220
+ </div>
221
+
222
+ <div :class="{'modal-backdrop': true, 'fade': true, show: addData.show}"></div>
223
+ <div :class="{modal: true, fade: true, show: addData.show}" id="modal-add-data" tabindex="-1" role="dialog">
224
+ <div class="modal-dialog" role="document">
225
+ <div class="modal-content">
226
+ <div class="modal-header">
227
+ <h5 class="modal-title">Add data</h5>
228
+ <button type="button" class="close" @click.prevent="addData.show = false">
229
+ <span>&times;</span>
230
+ </button>
231
+ </div>
232
+ <form @submit.prevent="onAddData">
233
+ <div class="modal-body">
234
+ <div class="form-group">
235
+ <label for="name">Name:</label>
236
+ <input type="text" class="form-control" required id="name" placeholder="Please enter a file name" v-model="addData.name">
237
+ <small class="form-text text-muted">Such as <code>filename.txt</code></small>
238
+ </div>
239
+ <div class="form-group">
240
+ <label for="type">Type:</label>
241
+ <input type="text" class="form-control" required id="type" placeholder="Please enter the MIME type" v-model="addData.type">
242
+ <small class="form-text text-muted">Such as <code>text/plain</code></small>
243
+ </div>
244
+ <div class="form-group">
245
+ <label for="content">Content:</label>
246
+ <textarea class="form-control" required id="content" rows="3" placeholder="Please enter the file contents" v-model="addData.content"></textarea>
247
+ </div>
248
+ </div>
249
+ <div class="modal-footer">
250
+ <button type="button" class="btn btn-secondary" @click.prevent="addData.show = false">Close</button>
251
+ <button type="submit" class="btn btn-success">Save</button>
252
+ </div>
253
+ </form>
254
+ </div>
255
+ </div>
256
+ </div>
257
+
258
+
259
+ <div :class="{'modal-backdrop': true, 'fade': true, show: editFile.show}"></div>
260
+ <div :class="{modal: true, fade: true, show: editFile.show}" id="modal-edit-file" tabindex="-1" role="dialog">
261
+ <div class="modal-dialog modal-lg" role="document">
262
+ <div class="modal-content">
263
+ <div class="modal-header">
264
+ <h5 class="modal-title">Edit file</h5>
265
+ <button type="button" class="close" @click.prevent="editFile.show = false">
266
+ <span>&times;</span>
267
+ </button>
268
+ </div>
269
+ <form @submit.prevent="onEditorFile">
270
+ <div class="modal-body">
271
+ <div class="form-group">
272
+ <label for="name">Name:</label>
273
+ <input type="text" class="form-control" required id="name" placeholder="Please enter a file name" v-model="editFile.name">
274
+ </div>
275
+ <div class="form-group" v-if="editFile.show && editFile.blob && editFile.type && editFile.type.substr(0, 6) === 'image/'">
276
+ <label>Image: </label>
277
+ <div class="edit-image">
278
+ <img :src="editFile.blob" ref="editImage" />
279
+ </div>
280
+
281
+ <div class="edit-image-tool">
282
+ <div class="btn-group" role="group">
283
+ <button type="button" class="btn btn-success" @click="editFile.cropper.rotate(-90)" title="cropper.rotate(-90)"><i class="fa fa-undo" aria-hidden="true"></i></button>
284
+ <button type="button" class="btn btn-success" @click="editFile.cropper.rotate(90)" title="cropper.rotate(90)"><i class="fa fa-repeat" aria-hidden="true"></i></button>
285
+ </div>
286
+ <div class="btn-group" role="group">
287
+ <button type="button" class="btn btn-success" @click="editFile.cropper.crop()" title="cropper.crop()"><i class="fa fa-check" aria-hidden="true"></i></button>
288
+ <button type="button" class="btn btn-success" @click="editFile.cropper.clear()" title="cropper.clear()"><i class="fa fa-remove" aria-hidden="true"></i></button>
289
+ </div>
290
+ </div>
291
+ </div>
292
+ </div>
293
+ <div class="modal-footer">
294
+ <button type="button" class="btn btn-secondary" @click.prevent="editFile.show = false">Close</button>
295
+ <button type="submit" class="btn btn-success">Save</button>
296
+ </div>
297
+ </form>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ -->
302
+ </div>
303
+ </template>
304
+
305
+ <script>
306
+ // import Cropper from 'cropperjs'
307
+ // import ImageCompressor from '@xkeshi/image-compressor'
308
+ import FileUpload from 'vue-upload-component'
309
+ export default {
310
+ name: 'MediaGallery',
311
+ components: {
312
+ FileUpload,
313
+ },
314
+ props: ['uploads_path'],
315
+ data() {
316
+ return {
317
+ files: [],
318
+ accept: 'image/png,image/gif,image/jpeg,image/webp',
319
+ extensions: 'gif,jpg,jpeg,png,webp',
320
+ // extensions: ['gif', 'jpg', 'jpeg','png', 'webp'],
321
+ // extensions: /\.(gif|jpe?g|png|webp)$/i,
322
+ minSize: 1024,
323
+ size: 1024 * 1024 * 10,
324
+ multiple: true,
325
+ directory: false,
326
+ drop: true,
327
+ dropDirectory: true,
328
+ addIndex: false,
329
+ thread: 3,
330
+ name: 'file',
331
+ postAction: this.uploads_path, //'/upload/post',
332
+ putAction: null, //this.uploads_path, //'/upload/put',
333
+ headers: {
334
+ 'X-Csrf-Token': 'xxxx',
335
+ },
336
+ data: {
337
+ '_csrf_token': 'xxxxxx',
338
+ },
339
+ // autoCompress: 1024 * 1024,
340
+ // uploadAuto: false,
341
+ // showOptions: false,
342
+ // addData: {
343
+ // show: false,
344
+ // name: '',
345
+ // type: '',
346
+ // content: '',
347
+ // },
348
+ // editFile: {
349
+ // show: false,
350
+ // name: '',
351
+ // }
352
+ }
353
+ },
354
+ created() {
355
+ let self = this
356
+ $.ajax({
357
+ url: this.uploads_path,
358
+ dataType: 'json'
359
+ }).done(function (result) {
360
+ console.log('files', result)
361
+ result.forEach(function(file) {
362
+ file.persisted = true
363
+ self.files.push(file)
364
+ })
365
+ })
366
+ },
367
+ // watch: {
368
+ // 'editFile.show'(newValue, oldValue) {
369
+ // if (!newValue && oldValue) {
370
+ // this.$refs.upload.update(this.editFile.id, { error: this.editFile.error || '' })
371
+ // }
372
+ // if (newValue) {
373
+ // this.$nextTick(function () {
374
+ // if (!this.$refs.editImage) {
375
+ // return
376
+ // }
377
+ // let cropper = new Cropper(this.$refs.editImage, {
378
+ // autoCrop: false,
379
+ // })
380
+ // this.editFile = {
381
+ // ...this.editFile,
382
+ // cropper
383
+ // }
384
+ // })
385
+ // }
386
+ // },
387
+ // 'addData.show'(show) {
388
+ // if (show) {
389
+ // this.addData.name = ''
390
+ // this.addData.type = ''
391
+ // this.addData.content = ''
392
+ // }
393
+ // },
394
+ // },
395
+ methods: {
396
+ removeFile(index) {
397
+ const file = this.files.splice(index, 1)[0]
398
+ console.log('removing', file)
399
+
400
+ if (file.delete_url) {
401
+ $.ajax({
402
+ type: 'DELETE',
403
+ url: file.delete_url,
404
+ // url: this.uploads_path,
405
+ data: {
406
+ key: file.key
407
+ }
408
+ })
409
+ }
410
+ this.$refs.upload.remove(file)
411
+ },
412
+ inputFilter(newFile, oldFile, prevent) {
413
+ console.log('input filter', newFile, oldFile)
414
+ if (newFile && !oldFile) {
415
+ // Before adding a file
416
+ // Filter system files or hide files
417
+ if (/(\/|^)(Thumbs\.db|desktop\.ini|\..+)$/.test(newFile.name)) {
418
+ return prevent()
419
+ }
420
+ // Filter php html js file
421
+ if (/\.(php5?|html?|jsx?)$/i.test(newFile.name)) {
422
+ return prevent()
423
+ }
424
+ // Automatic compression
425
+ // if (newFile.file && newFile.type.substr(0, 6) === 'image/' && this.autoCompress > 0 && this.autoCompress < newFile.size) {
426
+ // newFile.error = 'compressing'
427
+ // const imageCompressor = new ImageCompressor(null, {
428
+ // convertSize: Infinity,
429
+ // maxWidth: 512,
430
+ // maxHeight: 512,
431
+ // })
432
+ // imageCompressor.compress(newFile.file)
433
+ // .then((file) => {
434
+ // this.$refs.upload.update(newFile, { error: '', file, size: file.size, type: file.type })
435
+ // })
436
+ // .catch((err) => {
437
+ // this.$refs.upload.update(newFile, { error: err.message || 'compress' })
438
+ // })
439
+ // }
440
+ }
441
+ if (newFile && (!oldFile || newFile.file !== oldFile.file)) {
442
+ // Create a blob field
443
+ newFile.blob = ''
444
+ let URL = window.URL || window.webkitURL
445
+ if (URL && URL.createObjectURL) {
446
+ newFile.blob = URL.createObjectURL(newFile.file)
447
+ }
448
+ // Thumbnails
449
+ newFile.thumbnail = ''
450
+ if (newFile.blob && newFile.type.substr(0, 6) === 'image/') {
451
+ newFile.thumbnail = newFile.blob
452
+ }
453
+ }
454
+ },
455
+ // add, update, remove File Event
456
+ inputFile(newFile, oldFile) {
457
+ console.log('input file', newFile, oldFile)
458
+ if (newFile && oldFile) {
459
+ // update
460
+ if (newFile.active && !oldFile.active) {
461
+ // beforeSend
462
+ // min size
463
+ if (newFile.size >= 0 && this.minSize > 0 && newFile.size < this.minSize) {
464
+ this.$refs.upload.update(newFile, { error: 'size' })
465
+ }
466
+ }
467
+ if (newFile.progress !== oldFile.progress) {
468
+ // progress
469
+ }
470
+ if (newFile.error && !oldFile.error) {
471
+ // error
472
+ }
473
+ if (newFile.success && !oldFile.success) {
474
+ // success
475
+ newFile.persisted = true
476
+ newFile.key = newFile.response[newFile.response.length - 1].key
477
+ }
478
+ }
479
+ if (!newFile && oldFile) {
480
+ // remove
481
+ // if (oldFile.success && oldFile.response.id) {
482
+ // // $.ajax({
483
+ // // type: 'DELETE',
484
+ // // url: '/upload/delete?id=' + oldFile.response.id,
485
+ // // })
486
+ // }
487
+ }
488
+ // Automatically activate upload
489
+ if (Boolean(newFile) !== Boolean(oldFile) || oldFile.error !== newFile.error) {
490
+ if (this.uploadAuto && !this.$refs.upload.active) {
491
+ this.$refs.upload.active = true
492
+ }
493
+ }
494
+ },
495
+ copyToClipboard(text) {
496
+ const dummy = document.createElement('input')
497
+ document.body.appendChild(dummy)
498
+ dummy.value = text
499
+ dummy.select()
500
+ document.execCommand('copy', false)
501
+ dummy.remove()
502
+ toastr.info("Copied!")
503
+ console.log('copied', text)
504
+ },
505
+ alert(message) {
506
+ alert(message)
507
+ },
508
+ // onEditFileShow(file) {
509
+ // this.editFile = { ...file, show: true }
510
+ // this.$refs.upload.update(file, { error: 'edit' })
511
+ // },
512
+ // onEditorFile() {
513
+ // if (!this.$refs.upload.features.html5) {
514
+ // this.alert('Your browser does not support')
515
+ // this.editFile.show = false
516
+ // return
517
+ // }
518
+ // let data = {
519
+ // name: this.editFile.name,
520
+ // }
521
+ // if (this.editFile.cropper) {
522
+ // let binStr = atob(this.editFile.cropper.getCroppedCanvas().toDataURL(this.editFile.type).split(',')[1])
523
+ // let arr = new Uint8Array(binStr.length)
524
+ // for (let i = 0; i < binStr.length; i++) {
525
+ // arr[i] = binStr.charCodeAt(i)
526
+ // }
527
+ // data.file = new File([arr], data.name, { type: this.editFile.type })
528
+ // data.size = data.file.size
529
+ // }
530
+ // this.$refs.upload.update(this.editFile.id, data)
531
+ // this.editFile.error = ''
532
+ // this.editFile.show = false
533
+ // },
534
+ // onAddFolder() {
535
+ // if (!this.$refs.upload.features.directory) {
536
+ // this.alert('Your browser does not support')
537
+ // return
538
+ // }
539
+ // let input = this.$refs.upload.$el.querySelector('input')
540
+ // input.directory = true
541
+ // input.webkitdirectory = true
542
+ // this.directory = true
543
+ // input.onclick = null
544
+ // input.click()
545
+ // input.onclick = (e) => {
546
+ // this.directory = false
547
+ // input.directory = false
548
+ // input.webkitdirectory = false
549
+ // }
550
+ // },
551
+ // onAddData() {
552
+ // this.addData.show = false
553
+ // if (!this.$refs.upload.features.html5) {
554
+ // this.alert('Your browser does not support')
555
+ // return
556
+ // }
557
+ // let file = new window.File([this.addData.content], this.addData.name, {
558
+ // type: this.addData.type,
559
+ // })
560
+ // this.$refs.upload.add(file)
561
+ // }
562
+ }
563
+ }
564
+ </script>