Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

!feat(components): add support for remote-images #796

Merged
merged 88 commits into from
Jun 7, 2020

Conversation

lessp
Copy link
Member

@lessp lessp commented Mar 22, 2020

A new PR to reflect changes from Skia.

revery-remote-image

Closes #537

BREAKING CHANGE:
When using the <Image ... />-primitive, you now have to pass a variant of either (`Url or `File) to src.

In other words, where you previously passed <Image src="example.png" /> you'll now have to pass

<Image src=`File("example.png") />

or

<Image src=`Url("https://example.com/example.png") />

@Et7f3
Copy link
Member

Et7f3 commented Mar 22, 2020

Oups GHA removed lockdir because esy can't solve dependencies.

@lessp
Copy link
Member Author

lessp commented Mar 22, 2020

There's now a working example pushed, for now I created a getContextRemote for along with getContext for performance-reasons.

I'm guessing we probably want a different API, so that will have to be handled either now or in the future. E.g.

<Image src=Url("..", options) />

@lessp
Copy link
Member Author

lessp commented Mar 23, 2020

Can you see why this fails @Et7f3?

info install 0.6.2 (using package.json)
info checking https://github.com/ocaml/opam-repository for updates...
info checking https://github.com/esy-ocaml/esy-opam-override for updates...
error: File exists
  reading package metadata from github:esy-packages/esy-openssl:package.json#b7eab4ff97f6e61ec4523e771176e3aadc25cdd8
  resolving metadata @opam/conf-libssl@github:esy-packages/esy-openssl:package.json#b7eab4ff97f6e61ec4523e771176e3aadc25cdd8
  resolving @reason-native-web/[email protected]
  resolving @reason-native-web/[email protected]
  resolving fetch-native-lwt@github:lessp/fetch:fetch-native-lwt.json#0774bc989493f78f87e1689731f41f2a83ea0b78
esy: exiting due to errors above

##[error]Bash exited with code '1'.

@lessp
Copy link
Member Author

lessp commented Mar 24, 2020

Ok, so still stuck at the above error.

info install 0.6.2 (using package.json)
info checking https://github.com/ocaml/opam-repository for updates...
info checking https://github.com/esy-ocaml/esy-opam-override for updates...
error: File exists
  reading package metadata from github:esy-packages/esy-openssl:package.json#b7eab4ff97f6e61ec4523e771176e3aadc25cdd8
  resolving metadata @opam/conf-libssl@github:esy-packages/esy-openssl:package.json#b7eab4ff97f6e61ec4523e771176e3aadc25cdd8
  resolving @reason-native-web/[email protected]
  resolving @reason-native-web/[email protected]
  resolving @reason-native-web/[email protected]
  resolving fetch-native-lwt@github:lessp/fetch:fetch-native-lwt.json#0774bc9
esy: exiting due to errors above
##[error]Process completed with exit code 1.

Did you try to run it @Et7f3? (on your Windows-machine, if yes) 🙂

@Et7f3
Copy link
Member

Et7f3 commented Mar 24, 2020

 rd /S/Q esy.lock _esy & esy i
info install 0.6.2 (using package.json)
info checking https://github.com/ocaml/opam-repository for updates...
info checking https://github.com/esy-ocaml/esy-opam-override for updates...
info resolving esy packages: done
info solving esy constraints: done
info resolving npm packages: done
.... fetching @opam/conf-libssl@archive:https://github.com/openssl/openssl/archive/OpenSSL_1_1_1d.tar.gz#sha1:df0ee4811c87c209ebadb4e6                                                                                                                                      info fetching: done
.... installing @opam/conf-libssl@archive:https://github.com/openssl/openssl/archive/OpenSSL_1_1_1d.tar.gz#sha1:df0ee4811c87c209ebadb4                                                                                                                                      info installing: done

Work ok on my computer it manage to solve. I try to build.

@lessp lessp changed the title feat: add support for remote-images feat(components): add support for remote-images Jun 5, 2020
@lessp lessp changed the title feat(components): add support for remote-images !feat(components): add support for remote-images Jun 5, 2020
src/Core/Environment.rei Outdated Show resolved Hide resolved
Comment on lines 197 to 212
LetOperators.(
{
let.map image = ImageRenderer.fromUrl(url);

switch (image) {
| None => ()
| Some(img) =>
Canvas.drawImageRect(
v.canvas,
img,
None,
Rect.makeLtrb(x, y, x +. width, y +. height),
paint,
)
};
}
Copy link
Member

@glennsl glennsl Jun 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like the wrong place to do I/O, and asynchronous I/O in particular. How would you recover from error, provide fallback using a local file and such, for example, with this logic being here? This ought to be moved up to the component, I think.

node#setStyle(styles);
node#setResizeMode(resizeMode);
Obj.magic(node);
module Asset = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree with this. Other props could be passed through the variant as well, if needed, e.g. Remote({url, timeout}).

Comment on lines 1 to 16
let (let.map) = (promise, fn) => Lwt.map(fn, promise);
let (let.mapOk) = (promise, fn) =>
Lwt.map(
fun
| Ok(response) => fn(response)
| Error(e) => e,
promise,
);

let (let.flatMapOk) = (promise, fn) =>
Lwt.bind(
promise,
fun
| Ok(response) => fn(response)
| Error(e) => Lwt.return(Error(e)),
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ought to be moved to Utility I think.

Comment on lines 22 to 41
let download = url => {
Fetch.(
{
Log.info("Fetching image: " ++ url);

let.flatMapOk {Response.body, headers, _} = get(url);

let data = Body.toString(body);
let mediaType =
headers
|> List.find_opt(((k, _v)) =>
String.lowercase_ascii(k) == "content-type"
)
|> Option.map(((_k, v)) => v)
|> Option.value(~default="");

Lwt.return(Ok({data, mediaType}));
}
);
};
Copy link
Member

@glennsl glennsl Jun 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also the wrong place to do I/O I think. A renderer should render, not do all kinds of other things as well. I can see how the earlier decision to have fromAssetPath here lead to this, but adding network I/O and asynchrony makes the spaghetti bowl significantly deeper. I think this needs a proper refactoring to separate file I/O, asset handling, network access and rendering.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that probably makes sense! Any pointers on where you'd start? drawImage takes a Skia.Image.t and that we handle IO in the component? Would that mean that we need to create a wrapper-%component for the Image-primitive perhaps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... or would ImageNode work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe to begin with, I could start to put all the IO-specific things in a separate module and let drawImage take:

let drawImage = (~x, ~y, ~width, ~height, ~paint=?, data: Skia.Image.t, v: t) => {
  Canvas.drawImageRect(
    v.canvas,
    data,
    None,
    Rect.makeLtrb(x, y, x +. width, y +. height),
    paint,
  );
};

Copy link
Member

@glennsl glennsl Jun 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that seems like a good start. Not sure what you mean with the wrapped component, but I don't think the component API needs or ought to change.

Copy link
Member Author

@lessp lessp Jun 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed some progress, let me know if you think this is the right direction and what else we could do to tidy things up. Really appreciate the feedback @glennsl! 👌

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! I think this already addresses the bulk of my concerns. It would be nice to have the asset handling and networking separated out and to have proper interface files for the new modules. I'm also not sure about the idea of Image living in IO, but that's more of a cognitive dissonance issue than an actual problem I think.

Good job and thanks for doing this!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I'll give another round soon just to tie things up. Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some interface-files and left a note that the Image-module residing in IO could be moved and/or have another API-surface. Agree with you on that!

After your feedback, I think this at least leaves it in a little better state than previously while still having room for improvements!

As for this PR to not have to compete against time and getting stale, I'm thinking we can remove the use of it in the examples (as well as the call to startEventLoop), in order to enable Timber again, but still get the functionality in. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that's a good idea if this can't be solved now. If possible, it might be a good idea to try to run Lwt the same way we run Luv in Oni2, polling on every tick so we're still in full control of the event loop:

https://github.com/onivim/oni2/blob/f5094e83c944108bd8fea9d0a7744ff3e5a33912/src/bin_editor/Oni2_editor.re#L208-L228

I added some interface-files and left a note that the Image-module residing in IO could be moved and/or have another API-surface. Agree with you on that!

Good call. Thanks for adding the interfaces and comments! I find them very helpful.

package.json Outdated Show resolved Hide resolved
src/IO/Image.rei Outdated
Comment on lines 1 to 10
/**
* fromUrl
*
* Given a network file-path this returns a promise,
* holding either a Ok(option(Skia.Image.t)) or an Error(string).
*
* Examples:
* let result = Image.fromUrl("https://example.com/hello.png");
*/
let fromUrl: string => Lwt.t(result(option(Skia.Image.t), string));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why an option inside a result? What does Ok(None) mean?

Copy link
Member Author

@lessp lessp Jun 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Changed in: 07edf87

@glennsl glennsl merged commit f2dce86 into revery-ui:master Jun 7, 2020
@lessp
Copy link
Member Author

lessp commented Jun 7, 2020

Before bringing this in, as to not bring another breaking change too soon. The two things I thought about were:

  • Control over caching etc

Images downloaded from online would probably have an option for folder to cache in, delete/keep files after download, caching etc while these would not apply for images loaded from disk since they're already there. I don't think settings like these make a lot of sense as props, but better put in a record.

If so, we can leave as is and the breaking change would be to go from:

`Url(string)

to something like:

`Url({ path: string, ...options })

Which might be OK as a breaking change for the future? Unless we want to take pre-caution and let \Url` take a record from the beginning.

  • Component while loading/error etc

Does images over network and file-images take the same props, I think they could, right?

<Image 
  src=`Url({ ...Image.Url.defaults, url: "https://example.com/example.png" }) 
  // src=`File("example.png") 
  onLoading={<View />} 
  onError={<View />}  
/>

If so, we can leave as is because it'll just be additional niceties/features in the future.

@glennsl @zbaylin @Et7f3

@lessp
Copy link
Member Author

lessp commented Jun 7, 2020

I saw that you merged it @glennsl, but I think it's fine, the conclusions I came to are above 🙂

tl;dr I think the breaking change might go from Url(string) -> Url(Image.Url.t), rest of the props can be shared

@lessp lessp deleted the remote-images branch June 7, 2020 10:14
@glennsl
Copy link
Member

glennsl commented Jun 7, 2020

Ah, sorry, that was terrible timing.

Images downloaded from online would probably have an option for folder to cache in, delete/keep files after download, caching etc while these would not apply for images loaded from disk since they're already there. I don't think settings like these make a lot of sense as props, but better put in a record.

This seems to me like an application-wide setting, not one that would need to be different for each component instance. Or to have the flexibility of having some instances with different settings, you could provide a functor interface for example.

Does images over network and file-images take the same props, I think they could, right?

For any optional props that aren't outright contradictory for file-images, I think that's absolutely fine.

@lessp
Copy link
Member Author

lessp commented Jun 7, 2020

No worries at all, just hit me while discussing a bit with Zach!

This seems to me like an application-wide setting, not one that would need to be different for each component instance. Or to have the flexibility of having some instances with different settings, you could provide a functor interface for example.

Yep, that's true!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Load image from absolute path/url
3 participants