Skip to content

Commit

Permalink
File uploader API
Browse files Browse the repository at this point in the history
  • Loading branch information
essenciary committed Jan 5, 2024
1 parent 721ff97 commit 8f30da6
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 6 deletions.
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "StippleUI"
uuid = "a3c5d34a-b254-4859-a8fa-b86abb7e84a3"
authors = ["Adrian Salceanu <[email protected]>"]
version = "0.22.13"
version = "0.22.14"

[deps]
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
Expand All @@ -22,10 +22,10 @@ StippleUIDataFramesExt = "DataFrames"
Colors = "0.12"
DataFrames = "1"
Dates = "1.6"
Genie = "5"
Genie = "5.23.7"
OrderedCollections = "1"
PrecompileTools = "1"
Stipple = "0.27.15"
Stipple = "0.27.27"
Tables = "1"
julia = "1.6"

Expand Down
5 changes: 5 additions & 0 deletions demos/uploader/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[deps]
GenieAutoReload = "c6228e60-a24f-11e9-1776-c313472ebacc"
GenieFramework = "a59fdf5c-6bf0-4f5d-949c-a137c9e2f353"
Stipple = "4acbeb90-81a0-11ea-1966-bdaff8155998"
StippleUI = "a3c5d34a-b254-4859-a8fa-b86abb7e84a3"
59 changes: 59 additions & 0 deletions demos/uploader/app.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module App

using GenieFramework
@genietools

@app begin
@event rejected begin
@info "rejected"
# @info params(:payload)["event"]
end

# @event added begin
# @info "added"
# # @info params(:payload)["event"]
# end

# @event removed begin
# @info "removed"
# # @info params(:payload)["event"]
# end

# @event started begin
# @info "started"
# # @info params(:payload)["event"]
# end

# @event uploading begin
# @info "uploading"
# # @info params(:payload)["event"]
# end

# @event uploaded begin
# @info "uploaded"
# # @info params(:payload)["event"]
# end

# @event finished begin
# @info "finished"
# # @info params(:payload)["event"]
# end

# @event failed begin
# @info "failed"
# # @info params(:payload)["event"]
# end

@onchange fileuploads begin
if ! isempty(fileuploads)
@info "File was uploaded: " fileuploads
fileuploads = Dict{AbstractString,AbstractString}()
end

@info "All good"
end
end

@page("/", "ui.jl")

end
24 changes: 24 additions & 0 deletions demos/uploader/ui.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
heading("File uploader demo")
uploader( multiple = true,
accept = ".jpg, .jpeg, .pdf, image/*",
maxfilesize = 102400000000,
maxfiles = 10,
autoupload = true,
hideuploadbtn = true,
method = "POST",
label = "Upload Images",
nothumbnails = false,
dark = false,
square = false,
flat = false,
bordered = false,

# @on("rejected", :rejected),
# @on("added", :added),
# @on("removed", :removed),
# @on("uploaded", :uploaded),
# @on("uploading", :uploading),
# @on("start", :started),
# @on("finish", :finished),
# @on("failed", :failed)
)
73 changes: 70 additions & 3 deletions src/Uploaders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,72 @@ export uploader

register_normal_element("q__uploader", context = @__MODULE__)

@kwdef mutable struct UploadedFile
tmppath::String
name::String
channel::String
end

# handlers receive an UploadedFile and return an UploadedFile
const upload_handlers = Function[]

function __init__()
Genie.config.cors_headers["Access-Control-Allow-Origin"] = "*"
Genie.config.cors_headers["Access-Control-Allow-Headers"] = "Content-Type"
Genie.config.cors_headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,OPTIONS"
Genie.config.cors_allowed_origins = ["*"]

route("/____/upload/:channel", method = POST) do
for f in Genie.Requests.filespayload()
uf = UploadedFile(tempname(), f[2].name, params(:channel))

try
write(uf.tmppath, f[2].data)
catch e
@error "Error saving uploaded file: $e"
rethrow(e)
end

push_uploaded_files(uf)

for h in upload_handlers
try
uf = uf |> h
catch e
@error "Error in upload handler: $e"
if Genie.Configuration.isdev()
rethrow(e)
end
end
end

end

"OK"
end
end


function push_uploaded_files(uf::UploadedFile)
Stipple._push!(
Pair(:fileuploads,
Dict{AbstractString,AbstractString}("name" => uf.name,
"channel" => uf.channel,
"path" => uf.tmppath)
),
uf.channel
)

# # this won't be broadcasted back to the server so we need to do it manually
Stipple.WEB_TRANSPORT[].broadcast(
Genie.WebChannels.tagbase64encode(""">eval:Genie.WebChannels.sendMessageTo(window.CHANNEL, 'watchers', {'payload': {'field':'fileuploads', 'newval': {'name':'$(uf.name)', 'channel':'$(uf.channel)', 'path':'$(uf.tmppath)'}, 'oldval': {}, 'sesstoken': document.querySelector("meta[name='sesstoken']")?.getAttribute('content')}})"""),
channels = uf.channel
)

nothing
end


"""
uploader(args...; kwargs...)
Expand Down Expand Up @@ -50,7 +116,6 @@ julia> uploader(label="Upload Image", autoupload=true, multiple=true, method="PO
* `flat::Bool` - Applies a flat design (no default shadow)
* `bordered::Bool` - Applies a default border to the component
5. Upload
* `factory::String` - Function which should return an Object or a Promise resolving with an Object; For best performance, reference it from your scope and do not define it inline Function form. (files) => Object, Promise
* `url::String` - URL or path to the server which handles the upload. Takes String or factory function, which returns String. Function is called right before upload; If using a function then for best performance, reference it from your scope and do not define it inline ex. `"files => `https://example.com?count=\${files.length}`"` `https://example.com/path`
* `method::String` - HTTP method to use for upload; Takes String or factory function which returns a String; Function is called right before upload; If using a function then for best performance, reference it from your scope and do not define it inline default. `POST` ex. `POST` `PUT`
* `fieldname::String` - Field name for each file upload; This goes into the following header: 'Content-Disposition: form-data; name="__HERE__"; filename="somefile.png"; If using a function then for best performance, reference it from your scope and do not define it inline default value. `(file) => file.name` ex. `backgroundFile` `fieldname!="(file) => 'background' + file.name"`
Expand All @@ -60,8 +125,10 @@ julia> uploader(label="Upload Image", autoupload=true, multiple=true, method="PO
* `sendraw::Union{Bool, String}` - Send raw files without wrapping into a Form(); Takes boolean or factory function for Boolean; Function is called right before upload; If using a function then for best performance, reference it from your scope and do not define it inline ex. `sendraw` `sendraw!="files => ...."`
* `batch::Union{Bool, String}` - Upload files in batch (in one XHR request); Takes boolean or factory function for Boolean; Function is called right before upload; If using a function then for best performance, reference it from your scope and do not define it inline ex. `"files => files.length > 10"`
"""
function uploader(args...; kwargs...)
q__uploader(args...; kw(kwargs)...)
function uploader(url::Union{String,Nothing} = nothing, args...; kwargs...)
url === nothing && (url = "'/____/upload/' + channel_")

q__uploader(url! = url, args...; kw(kwargs)...)
end

end

0 comments on commit 8f30da6

Please sign in to comment.