Skip to content

Instantly share code, notes, and snippets.

@fuzzyweapon
Last active May 7, 2018 15:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fuzzyweapon/00469c3b02c9424b397deeb14044af17 to your computer and use it in GitHub Desktop.
Save fuzzyweapon/00469c3b02c9424b397deeb14044af17 to your computer and use it in GitHub Desktop.
Partial
/*
* Copyright 2018 Nicholas Bilyk
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.acornui.js.file
import com.acornui.async.Promise
import com.acornui.async.launch
import com.acornui.file.FileIoManager
import com.acornui.file.FileReader
import com.acornui.io.NativeBuffer
import com.acornui.js.time.setTimeout
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.Node
import org.w3c.dom.css.CSSStyleDeclaration
import org.w3c.dom.events.Event
import org.w3c.files.File
import org.w3c.files.get
import org.w3c.files.FileReader as JsFileApiReader
import kotlin.browser.document
import kotlin.browser.window
class JsFileIoManager : FileIoManager {
// TODO: Test to see if we can lateinit this
private var filePicker: HTMLInputElement? = null
private var files: List<FileReader>? = null
private val clickHandler = { _: Event ->
val fileList = filePicker?.files
files = if (fileList == null || fileList.length == 0) {
null
} else {
var tempList = mutableListOf<FileReader>()
for (i in 0..fileList.length - 1) {
tempList.add(JsFileReader(fileList[i] ?: continue))
}
tempList
}
}
private fun createFilePicker(multipleFiles: Boolean): HTMLInputElement {
val newFilePicker = document.createElement("input") as HTMLInputElement
newFilePicker.type = "file"
// Required for iOS Safari
newFilePicker.setAttribute("style", "width: 0px; height: 0px; overflow: hidden;")
newFilePicker.style.visibility = "hidden"
newFilePicker.multiple = multipleFiles
newFilePicker.id = "test"
newFilePicker.onclick = null
newFilePicker.onchange = clickHandler
return newFilePicker
}
private fun getFileReaders(extensions: List<String>, defaultPath: String, multipleFiles: Boolean = false): List<FileReader>? {
// TODO: Check to see if extensions limits what types of files can be pulled in or if it filters to that + all and mimic functionality
// Ensure there is a body to append to in order to preserve browser support when using contains()
if (document.body == null) {
document.createElement("body").also { document.appendChild(it) }
}
val body = document.body as HTMLElement
filePicker = createFilePicker(multipleFiles).also { body.appendChild(it) }
filePicker?.click()
body.removeChild(filePicker!!)
return files
}
override fun pickFileForOpen(extensions: List<String>, defaultPath: String): FileReader? {
return getFileReaders(extensions, defaultPath)?.get(0)
}
override fun pickFilesForOpen(extensions: List<String>, defaultPath: String): List<FileReader>? {
return getFileReaders(extensions, defaultPath, true)
}
}
class JsFileReader(private val file: File) : FileReader {
override val name: String = file.name
override val size: Int = file.size
override val lastModified: Int = file.lastModified
private val reader: JsFileApiReader = JsFileApiReader()
private var contents: Promise<String> = getContentsPromise()
get() = getContentsPromise() // Just to compile for now.
private fun getContentsPromise() =
object : Promise<String>() {
init {
launch {
reader.onload = { _: Event ->
success(reader.result as String)
}
reader.onerror = { _: Event ->
val error: dynamic = reader.error
var msg: String = when(error.code) {
error.ENCODING_ERR -> "Encoding error"
error.NOT_FOUND_ERR -> "File not found"
error.NOT_READABLE_ERR -> "File could not be read"
error.SECURITY_ERR -> "File has a security issue"
else -> "File cannot be opened due to the following error"
}
msg = "$msg: ${error.code}"
fail(Exception(msg))
}
}
}
}
override suspend fun readAsString(): String? {
reader.readAsText(file)
return contents.await()
}
override suspend fun readAsBinary(): NativeBuffer<Byte>? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override suspend fun saveToFileAsString(extension: String, defaultPath: String, value: String): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override suspend fun saveToFileAsBinary(extension: String, defaultPath: String, value: NativeBuffer<Byte>): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
@nbilyk
Copy link

nbilyk commented Apr 26, 2018

if (document.getElementById("filePicker") == null)
JsFileIoManager(canvas) -- That looks scary.

@nbilyk
Copy link

nbilyk commented Apr 26, 2018

21 - rename to canvasClickHandler

@nbilyk
Copy link

nbilyk commented Apr 26, 2018

32 - canvas.onmousedown Never use the on___ setters for elements you didn't create. Always use addEventListener. E.g. canvas.addEventListener("mousedown", canvasMouseDownHandler)

@nbilyk
Copy link

nbilyk commented Apr 26, 2018

16 - why stop propagation?

@fuzzyweapon
Copy link
Author

Yeah, I'm going to remove that self init. That's something I don't like. Removes explicit control if something goes wrong.
21 - will do
32 - will do
16 - I just didn't think there was any need to continue propagating (but this very well could be totally ignorant)

@nbilyk
Copy link

nbilyk commented Apr 26, 2018

30 - rename to pickFileForRead
add to interface
return a FileReader interface (when you get to it)

@nbilyk
Copy link

nbilyk commented Apr 26, 2018

3 - don't use lateinit in that way. lateinit is only for if there's an initialization step separate from the constructor.

@fuzzyweapon
Copy link
Author

gist updated

@fuzzyweapon
Copy link
Author

updated - major changes, now the promise object created more closely resembles the kind of interface it is fulfilling (which is better for picking multiple files) and gets rid of the .getornull call on picking one file. Also, files is initiated with a function which we use also as its getter (so we aren't baking in copy/pasting that code in two places). This fixes the multiple picking bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment