designer 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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>