GenieFramework / StippleDemos

Demo apps for Stipple
49 stars 29 forks source link

update image via js_methods #7

Closed yakir12 closed 3 years ago

yakir12 commented 3 years ago

In the WebCam example, we should replace

https://github.com/GenieFramework/StippleDemos/blob/cd49df10b9bfd8e4e441da2adad7668fcc42bb44/WebCam.jl#L88

with the Stipple.js_methods(::WebCam) implementation.

Perhaps coupling the repeating updates to the image with the state of the camera via the model's cameraon parameter (there is no need for the client to repeatedly connect to a new image if the camera is off.

hhaensel commented 3 years ago

Please go ahead and test wheather it works. If yes, please submit a PR. I would personally rather try to get the Webchannel solution running.

yakir12 commented 3 years ago

Sorry for not being clear: I had already tried that with simply:

Stipple.js_methods(model::WebCam) = 
            """
            setInterval(function() {
            var img = document.getElementById("frame");
            img.src = "frame/" + new Date().getTime();
            }, 200);
            """

which didn't work (the frame never got updated). The only way the app shows the camera is by including that as a script.

I would personally rather try to get the Webchannel solution running.

I agree that the webchannel solution would be more inline with the way things are done here. I think however that the webchannel way involves extra communications that are not necessary in this case. It would be good to allow for a mechanism where the client updates the frame on basis of some local state (a timer like in this case, or perhaps as a function of displaying the last frame (so fetch a new frame only when the last frame finished displaying)) without the need to talk to the server about it.

hhaensel commented 3 years ago

The reason is, because the script is never called. You need to trigger the function, e.g. by a watcher on the cameraon.

Why do you prefer to have the loop running on the client?

hhaensel commented 3 years ago

Try something like this

Base.@kwdef mutable struct WebCam <: ReactiveModel
    cameraon::R{Bool} = true
    imageurl::R{String} = IMGPATH
end

Stipple.js_watch(model::WebCam) = """
    cameraon: function (newval, oldval) { 
        if (this.camera == undefined) { this.camera = 0 };
        if (this.camera) { clearInterval(this.camera) };
        if (newval) {
            this.camera = setInterval(function() {
                WebCam.imageurl = "frame/" + new Date().getTime();
            }, 200);
        }
    }
"""

function ui()
    m = dashboard(vm(model), [      
        heading("WebCam"),
        row(cell(class="st-module", [ # using <img/> instead of quasar's becuase of the `img id = "frame"` that is used by the JS above to update the `src` from the client side
            quasar(:img, "", src=:imageurl, :basic, style="height: 140px; max-width: 150px")
        ])),
        row(cell(class="st-module", [
            p(toggle("Camera on", fieldname = :cameraon)),
        ]))
    ], title = "WebCam")

    return html(m)
end

You need to include the imageurl in order to install the model property where the image url is stored. It probably doesn't need to be reactive...

hhaensel commented 3 years ago

The WebCam.jl on StippleDemo has been updated. Feel free to close this, if you think this is solved. The same looping could, of course, be done from the server, without changing the rest of the code.

yakir12 commented 3 years ago

This looks fantastic. I'll close this.

Two follow up question:

  1. Here, you register cameraon and supply the method in the same place. I assume the better way would be to register the function (newval, oldval) in js_methods as, say, loopframe:
    Stipple.js_methods(model::WebCam) = """
    loopframe (newval, oldval) { 
        if (this.camera == undefined) { this.camera = 0 };
        if (this.camera) { clearInterval(this.camera) };
        if (newval) {
            this.camera = setInterval(function() {
                WebCam.imageurl = "frame/" + new Date().getTime();
            }, 200);
        }
    }
    """

    and then:

    Stipple.js_watch(model::WebCam) = """
    cameraon: {handler: 'loopframe'}
    """
  2. Any ideas on how to register multiple parameters to be watched? I want to have more than just cameraon watched, but also other model parameters (e.g. msg invoking badtoml). And consequently, how do I register multiple methods in js_methods...?
hhaensel commented 3 years ago

you are absolutely right, and I had done these changes already but had not pushed them, because they are waiting for the release of the new Stipple version. (@essenciary can I go with the proposed changes? 😉)

But now that you ask, I already pushed them, have a look.

  1. And consequently, how do I register multiple methods in js_methods...?

Just separate them by comma, see the new version of WebCam.jl

Stipple.js_methods(model::WebCam) = """
    updateimage: function () { 
        this.imageurl = "frame/" + new Date().getTime();
    },
    startcamera: function () { 
        this.cameratimer = setInterval(this.updateimage, 1000/$FPS_CLIENT);
    },
    stopcamera: function () { 
        clearInterval(this.cameratimer);
    }
"""

The same for js_watch. And I used js_created for the new version of WebCam, which will only work with the new Stipple version.

yakir12 commented 3 years ago

I used js_created for the new version of WebCam, which will only work with the new Stipple version.

Hmmm, I guess you mean you have local changes that you haven't pushed to a PR to Stipple? Cause I can't find any mention of js_created anywhere in GenieFramework apart from the WebCam.jl example in StippleDemo of course (so not Stipple#master but not in any open PR either). Eager to try this out :smile:

hhaensel commented 3 years ago

Hmmm, I guess you mean you have local changes that you haven't pushed to a PR to Stipple?

Exactly, but you can easily define js_created yourself:

function js_created(m::Any) "" end
js_created(app::M) where {M<:ReactiveModel} = js_created(M)

and in Stipple.render

js_created(app)  != "" && push!(vue, :created    => JSONText("function () { $(js_created(app)) }"))
hhaensel commented 3 years ago

Stipple#master contains the new stuff!

yakir12 commented 3 years ago

All this is super awesome (and works very well). I owe you so many beers by now, I hope we never meet...