@webhandle/tree-file-browser 1.0.0

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 (39) hide show
  1. package/README.md +144 -0
  2. package/client-lib/base-image-name.mjs +41 -0
  3. package/client-lib/data-to-image.mjs +45 -0
  4. package/client-lib/dynamic-load.mjs +21 -0
  5. package/client-lib/file-select-dialog.mjs +70 -0
  6. package/client-lib/form-answer-dialog.mjs +65 -0
  7. package/client-lib/format-bytes.mjs +10 -0
  8. package/client-lib/get-extension-from-mime.mjs +17 -0
  9. package/client-lib/get-file-image-stats.mjs +17 -0
  10. package/client-lib/get-image-stats.mjs +11 -0
  11. package/client-lib/image-browser-view-methods/create-directory.mjs +23 -0
  12. package/client-lib/image-browser-view-methods/delete.mjs +100 -0
  13. package/client-lib/image-browser-view-methods/drag-and-drop.mjs +57 -0
  14. package/client-lib/image-browser-view-methods/file-obj-manipulation.mjs +190 -0
  15. package/client-lib/image-browser-view-methods/sink.mjs +20 -0
  16. package/client-lib/image-browser-view-methods/upload.mjs +159 -0
  17. package/client-lib/image-browser-view-methods/utils.mjs +108 -0
  18. package/client-lib/image-browser-view-methods/view-interactions.mjs +96 -0
  19. package/client-lib/image-browser-view.mjs +227 -0
  20. package/client-lib/image-resize.mjs +32 -0
  21. package/client-lib/info-dialog.mjs +36 -0
  22. package/client-lib/load-styles.mjs +14 -0
  23. package/client-lib/make-image-set.mjs +75 -0
  24. package/client-lib/name-parts.mjs +24 -0
  25. package/client-lib/styles-loaded.mjs +18 -0
  26. package/less/components.less +357 -0
  27. package/package.json +80 -0
  28. package/public/tree-file-browser/resources/css/tree-browser.css +394 -0
  29. package/public/tree-file-browser/resources/css/tree-browser.css.map +1 -0
  30. package/public/tree-file-browser/resources/js/tree-file-browser.js +2 -0
  31. package/public/tree-file-browser/resources/js/tree-file-browser.js.map +1 -0
  32. package/server-lib/initialize-tree-browser-resources.cjs +17 -0
  33. package/server-lib/initialize-tree-browser-resources.mjs +15 -0
  34. package/views/load-browser-views.js +13 -0
  35. package/views/webhandle-tree-image-browser/extension-pill.tri +1 -0
  36. package/views/webhandle-tree-image-browser/guilded-file-upload-form.tri +11 -0
  37. package/views/webhandle-tree-image-browser/guilded-image-upload-form.tri +33 -0
  38. package/views/webhandle-tree-image-browser/image-browser-frame.tri +122 -0
  39. package/views/webhandle-tree-image-browser/variant-choice-box.tri +25 -0
@@ -0,0 +1,227 @@
1
+ import { View } from '@webhandle/backbone-view'
2
+ import { imageBrowserFrame, variantChoiceBox } from '../views/load-browser-views.js'
3
+ import KalpaTreeOnPage from 'kalpa-tree-on-page'
4
+ import formatBytes from './format-bytes.mjs'
5
+
6
+ import Emitter from '@webhandle/minimal-browser-event-emitter'
7
+
8
+ // method imports
9
+ import { deleteFile, deleteDirectory } from './image-browser-view-methods/delete.mjs'
10
+ import {
11
+ _join, _determineParentPath, _fileToKalpaNode, _determineExtensions,
12
+ _determineSizes, _sortFiles, _compareVariants, sanitizeFileName, _isImageFile
13
+ } from './image-browser-view-methods/utils.mjs'
14
+ import { changeFilesView, applyFilter, clearFilter, selectVariant, showVariantDetails } from './image-browser-view-methods/view-interactions.mjs'
15
+ import { getDropCoverSelector, handleDrop, isFileTypeDrag, dragEnter, dragLeave, dragOver, _cleanupDropDone } from './image-browser-view-methods/drag-and-drop.mjs'
16
+ import { createDirectory } from './image-browser-view-methods/create-directory.mjs'
17
+ import { createVariantValues, _getFilesFromEvent, _getAssociatedRealFiles, _createAccessUrl,
18
+ getSelectedFiles, _transformRelativeUrlToPublic, getSelectedUrl } from './image-browser-view-methods/file-obj-manipulation.mjs'
19
+ import { _uploadGuidedImageFile, _uploadGuidedFile, _uploadAutomaticImageFile, uploadFiles, _uploadFileButton, _uploadFile } from './image-browser-view-methods/upload.mjs'
20
+ import { _uploadData, findDirectories } from './image-browser-view-methods/sink.mjs'
21
+
22
+
23
+ export default class ImageBrowserView extends View {
24
+ /**
25
+ * Construct a new file browser
26
+ * @param {object} options
27
+ * @param {FileSink} options.sink The file to use as a file source
28
+ * @param {boolean} [options.imagesOnly] Set to true if you would like to display only images
29
+ * @param {boolean} [options.allowFileSelection] Set to true so that selected files are marked
30
+ * @param {EventNotificationPanel} [options.eventNotificationPanel] The panel which status messages will be added to.
31
+ * @param {string} [options.startingDirectory] Opens to that directory path if it exists
32
+ * @param {boolean} [options.deleteWithoutConfirm] False by default
33
+ * @param {boolean} [options.ignoreGlobalEvents] False by default, if true it will not listen to events like paste or keypresses
34
+ * which occur on the document
35
+ * @param {Emitter} [options.emitter] Emitter for various file events
36
+ */
37
+ constructor(options) {
38
+ super(options)
39
+ }
40
+ preinitialize() {
41
+ this.className = 'image-browser'
42
+ this.idInd = 1
43
+ this.nodes = {}
44
+ this.events = {
45
+ 'click .create-directory': 'createDirectory'
46
+ , 'click .upload-file': '_uploadFileButton'
47
+ , 'change [name="fileUpload"]': '_uploadFile'
48
+ , 'click .delete-file': 'deleteFile'
49
+ , 'click .delete-directory': 'deleteDirectory'
50
+ , 'click .variant-choice-box .details': 'showVariantDetails'
51
+ , 'dblclick .variant-choice-box': 'showVariantDetails'
52
+ , 'click .variant-choice-box': 'selectVariant'
53
+ , 'click .view-icons button': 'changeFilesView'
54
+ , 'keyup [name="filter"]': 'applyFilter'
55
+ , 'change [name="filter"]': 'applyFilter'
56
+ , 'click .clear-filter': 'clearFilter'
57
+ , 'dragenter .': 'dragEnter'
58
+ , 'dragleave .': 'dragLeave'
59
+ , 'dragover .': 'dragOver'
60
+ , 'drop .': 'handleDrop'
61
+ }
62
+ this.overCount = 0
63
+
64
+ if (!this.emitter) {
65
+ this.emitter = new Emitter()
66
+ }
67
+
68
+ this.fileUploadSelector = 'input[name="fileUpload"]'
69
+
70
+
71
+ document.addEventListener('paste', this.handlePaste.bind(this))
72
+ }
73
+
74
+ async handlePaste(evt) {
75
+ if(this.ignoreGlobalEvents) {
76
+ return
77
+ }
78
+ evt.preventDefault()
79
+ if(evt.clipboardData && evt.clipboardData.files && evt.clipboardData.files.length > 0) {
80
+ this.uploadFiles(evt.clipboardData.files, { uploadType: 'guided' })
81
+ }
82
+ }
83
+
84
+
85
+ _addPending(file) {
86
+ let note
87
+ if (this.eventNotificationPanel) {
88
+ note = this.eventNotificationPanel.addNotification({
89
+ model: {
90
+ status: 'pending',
91
+ headline: `uploading ${file.name}`
92
+ }
93
+ })
94
+ }
95
+ return note
96
+ }
97
+
98
+ async render() {
99
+ this.el.innerHTML = imageBrowserFrame(this.model)
100
+ this.data = []
101
+
102
+ this.rootDirectory = await this.sink.getFullFileInfo('')
103
+ this.rootDirectory.name = "Files"
104
+ let rootNode = this.rootNode = this._fileToKalpaNode(this.rootDirectory)
105
+ this.data.push(rootNode)
106
+
107
+ let directories = await this.findDirectories()
108
+ this._sortFiles(directories)
109
+
110
+ this.data.push(...directories.map(this._fileToKalpaNode.bind(this)))
111
+ KalpaTreeOnPage({
112
+ treeContainerSelector: `#${this.id} .treebox`
113
+ , data: this.data
114
+ }).then(tree => {
115
+ this.tree = tree
116
+ tree.on('select', (node) => {
117
+ this.setCurrentNode(node)
118
+ })
119
+ tree.on('selected', (node) => {
120
+ // There's a bug, either in the browser or kalpa tree that causes it
121
+ // not to examine if a scroll bar is needed for the tree if the content
122
+ // area changes in a big way. Part of this bug may be that it's being
123
+ // used in a grid which has some weird width/height effects
124
+ // Anyway, we need to make sure the browser knows to examine the tree so
125
+ // we change the height then change it back.
126
+ // This event is triggered when kalpa-tree thinks it's done with transitions
127
+
128
+ let tree = this.el.querySelector('.tree')
129
+ tree.style.height = '99.99999%'
130
+ setTimeout(() => {
131
+ tree.style.height = '100%'
132
+ }, 100)
133
+ })
134
+ if (this.startingDirectory) {
135
+ for (let node of Object.values(this.tree.nodes)) {
136
+ if (node.file && node.file.relPath && node.file.relPath == this.startingDirectory) {
137
+ tree.select(node.id)
138
+ break
139
+ }
140
+ }
141
+ }
142
+ else {
143
+ tree.select(1)
144
+ }
145
+ })
146
+ }
147
+
148
+ async setCurrentNode(node) {
149
+ this.currentNode = node
150
+ let info = await this.sink.getFullFileInfo(node.file.relPath)
151
+ let variantValues = this.createVariantValues(info)
152
+
153
+
154
+ let content = ''
155
+ for (let child of variantValues) {
156
+ content += variantChoiceBox(child)
157
+ }
158
+
159
+
160
+ let choicesBoxes = this.el.querySelector('.choice-boxes')
161
+ choicesBoxes.innerHTML = ''
162
+ choicesBoxes.insertAdjacentHTML('beforeend', content)
163
+
164
+ for (let i = 0; i < choicesBoxes.children.length; i++) {
165
+ choicesBoxes.children[i].variant = variantValues[i]
166
+ }
167
+ this.el.querySelector('.box-holder').scrollTop = 0
168
+ this.applyFilter()
169
+ }
170
+
171
+ // uploads
172
+ _uploadGuidedImageFile = _uploadGuidedImageFile
173
+ _uploadGuidedFile = _uploadGuidedFile
174
+ _uploadAutomaticImageFile = _uploadAutomaticImageFile
175
+ uploadFiles = uploadFiles
176
+ _uploadFileButton = _uploadFileButton
177
+ _uploadFile = _uploadFile
178
+
179
+ // file-obj-manipulation
180
+ createVariantValues = createVariantValues
181
+ _getFilesFromEvent = _getFilesFromEvent
182
+ _getAssociatedRealFiles = _getAssociatedRealFiles
183
+ _createAccessUrl = _createAccessUrl
184
+ getSelectedFiles = getSelectedFiles
185
+ _transformRelativeUrlToPublic = _transformRelativeUrlToPublic
186
+ getSelectedUrl = getSelectedUrl
187
+
188
+ // create-directory
189
+ createDirectory = createDirectory
190
+
191
+ // drag-and-drop
192
+ getDropCoverSelector = getDropCoverSelector
193
+ handleDrop = handleDrop
194
+ isFileTypeDrag = isFileTypeDrag
195
+ dragEnter = dragEnter
196
+ dragLeave = dragLeave
197
+ dragOver = dragOver
198
+ _cleanupDropDone = _cleanupDropDone
199
+
200
+ // view-interactions
201
+ changeFilesView = changeFilesView
202
+ applyFilter = applyFilter
203
+ clearFilter = clearFilter
204
+ selectVariant = selectVariant
205
+ showVariantDetails = showVariantDetails
206
+
207
+ // utils
208
+ sanitizeFileName = sanitizeFileName
209
+ _sortFiles = _sortFiles
210
+ _compareVariants = _compareVariants
211
+ _determineExtensions = _determineExtensions
212
+ _determineSizes = _determineSizes
213
+ _join = _join
214
+ _determineParentPath = _determineParentPath
215
+ _fileToKalpaNode = _fileToKalpaNode
216
+ _formatBytes = formatBytes
217
+ _isImageFile = _isImageFile
218
+
219
+ // delete
220
+ deleteFile = deleteFile
221
+ deleteDirectory = deleteDirectory
222
+
223
+ // sink
224
+ _uploadData = _uploadData
225
+ findDirectories = findDirectories
226
+
227
+ }
@@ -0,0 +1,32 @@
1
+ import dataToImage from "./data-to-image.mjs"
2
+
3
+ /*image, base64 encoded data of image, blob, file, or arraybuffer */
4
+ export default async function resizeImage(imageData,
5
+ { maxWidth = 1920, quality = .7, outputFormat = "image/png" } = {}) {
6
+ return new Promise(async (resolve, reject) => {
7
+
8
+ try {
9
+ let resizeImg = await dataToImage(imageData)
10
+ let ratio = resizeImg.naturalWidth / maxWidth
11
+ let maxHeight = resizeImg.naturalHeight / ratio
12
+
13
+ var resizeCanvas = document.createElement('canvas');
14
+ resizeCanvas.width = maxWidth;
15
+ resizeCanvas.height = maxHeight;
16
+
17
+ var ctx = resizeCanvas.getContext('2d');
18
+ ctx.clearRect(0, 0, resizeCanvas.width, resizeCanvas.height);
19
+ ctx.drawImage(resizeImg, 0, 0, resizeCanvas.width, resizeCanvas.height);
20
+
21
+
22
+ resizeCanvas.toBlob((blob) => {
23
+ resolve(blob)
24
+ }, outputFormat, quality);
25
+ } catch (e) {
26
+ if (console && console.log) {
27
+ console.log('error resizing image: ' + e);
28
+ }
29
+ reject(e)
30
+ }
31
+ })
32
+ }
@@ -0,0 +1,36 @@
1
+ import Dialog from 'ei-dialog'
2
+
3
+ export class InfoDialog extends Dialog {
4
+ constructor(options) {
5
+ super(Object.assign({}, options,
6
+ {
7
+ on: {
8
+ '.btn-ok': () => {
9
+ this.resolve()
10
+ return true
11
+ },
12
+ '.mask': () => {
13
+ this.resolve()
14
+ return true
15
+ },
16
+ '.btn-cancel': () => {
17
+ this.resolve()
18
+ return true
19
+ }
20
+ }
21
+
22
+ }
23
+ ))
24
+ }
25
+
26
+ async open() {
27
+ this.promise = new Promise((resolve, reject) => {
28
+ this.resolve = resolve
29
+ this.reject = reject
30
+ })
31
+ super.open()
32
+
33
+ return this.promise
34
+ }
35
+
36
+ }
@@ -0,0 +1,14 @@
1
+ import areStylesLoaded from "./styles-loaded.mjs";
2
+ import ensureIcons from '@dankolz/webhandle-admin-icons/client-js/ensure-styles-are-loaded.js'
3
+
4
+ export default function loadStyles() {
5
+ if(!areStylesLoaded()) {
6
+ let link = document.createElement('link')
7
+ link.href = '/@webhandle/tree-file-browser/resources/css/tree-browser.css'
8
+ link.rel = 'stylesheet'
9
+ document.head.appendChild(link)
10
+
11
+ }
12
+ ensureIcons()
13
+
14
+ }
@@ -0,0 +1,75 @@
1
+ import resizeImage from '../client-lib/image-resize.mjs'
2
+ import dataToImage from '../client-lib/data-to-image.mjs'
3
+ import getImageStats from '../client-lib/get-image-stats.mjs'
4
+ import getExtension from './get-extension-from-mime.mjs'
5
+
6
+ let webpMime = 'image/webp'
7
+
8
+ export default async function makeImageSet(data,
9
+ {
10
+ singleDensityWidth = null
11
+ , quality = .7
12
+ , outputFormat = "image/png"
13
+ , doubleDensityInput = true
14
+ , baseFileName
15
+ , altText
16
+ } = {}
17
+ ) {
18
+ let source = await dataToImage(data)
19
+ let stats = await getImageStats(source)
20
+ let ratio = stats.width / stats.height
21
+
22
+ if (!baseFileName) {
23
+ baseFileName = "" + (new Date().getTime())
24
+ }
25
+ if (!singleDensityWidth) {
26
+ if (doubleDensityInput) {
27
+ singleDensityWidth = stats.width / 2
28
+ }
29
+ else {
30
+ singleDensityWidth = stats.width
31
+ }
32
+ }
33
+ let doubleDensityWidth = singleDensityWidth * 2
34
+
35
+ let suffixes = {
36
+ '-2x': doubleDensityWidth
37
+ , '': singleDensityWidth
38
+ , '-half': Math.floor(singleDensityWidth / 2)
39
+ , '-quarter': Math.floor(singleDensityWidth / 4)
40
+ }
41
+
42
+ let files = {}
43
+
44
+ for (let key of Object.keys(suffixes)) {
45
+ let width = suffixes[key]
46
+ let data = await resizeImage(source, {
47
+ maxWidth: width
48
+ , quality: quality
49
+ , outputFormat: outputFormat
50
+ })
51
+ files[baseFileName + key + '.' + getExtension(outputFormat)] = data
52
+
53
+ data = await resizeImage(source, {
54
+ maxWidth: width
55
+ , quality: quality
56
+ , outputFormat: webpMime
57
+ })
58
+ files[baseFileName + key + '.' + getExtension(webpMime)] = data
59
+ }
60
+
61
+
62
+ let info = {
63
+ "name": baseFileName,
64
+ "size": doubleDensityWidth + "x" + (doubleDensityWidth / ratio),
65
+ "displaySize": singleDensityWidth + "x" + (singleDensityWidth / ratio),
66
+ "fallback": getExtension(outputFormat),
67
+ "altText": altText || baseFileName
68
+ }
69
+
70
+
71
+ files[baseFileName + '.json'] = JSON.stringify(info, null, '\t')
72
+
73
+ return files
74
+
75
+ }
@@ -0,0 +1,24 @@
1
+ import baseImageName from "./base-image-name.mjs"
2
+
3
+ /**
4
+ *
5
+ * @param {File,string} file
6
+ */
7
+ export default function nameParts(file) {
8
+ let name
9
+ if(typeof file === 'string') {
10
+ name = file
11
+ }
12
+ else if(file instanceof File) {
13
+ name = file.name
14
+ }
15
+
16
+ let result = [baseImageName(name)]
17
+
18
+ let parts = name.split('.')
19
+ if(parts.length > 1) {
20
+ result.push(parts.pop())
21
+ }
22
+
23
+ return result
24
+ }
@@ -0,0 +1,18 @@
1
+
2
+ /**
3
+ * Returns true if the styles for the image browser have been loaded
4
+ */
5
+ export default function areStylesLoaded() {
6
+ let d = document.createElement('div')
7
+ d.classList.add('webhandle-file-tree-image-browser-test')
8
+
9
+ d.style.position = 'absolute'
10
+ d.style.left = '-10000px'
11
+
12
+
13
+ document.body.appendChild(d)
14
+ let color = window.getComputedStyle(d)['background-color']
15
+ d.remove()
16
+
17
+ return color === 'rgb(101, 105, 99)'
18
+ }