Skip to content

Commit 8c14c68

Browse files
Add ImagePreviesHalogenHooks (#153)
1 parent 8515c09 commit 8c14c68

File tree

8 files changed

+216
-0
lines changed

8 files changed

+216
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Running a web-compatible recipe:
104104
| :heavy_check_mark: | | [HelloNode](recipes/HelloNode) | Equivalent to `HelloWorldLog` recipe, but just targets node.js environment (not the browser too). For CI testing until another node-only recipe is created. |
105105
| | :heavy_check_mark: | [HelloReactHooks](recipes/HelloReactHooks) | A React port of the ["HTML - Hello" Elm Example](https://elm-lang.org/examples). |
106106
| :heavy_check_mark: | :heavy_check_mark: | [HelloWorldLog](recipes/HelloWorldLog) | This recipe shows how to run a simple "Hello world!" program in either the node.js or web browser console. |
107+
| | :heavy_check_mark: | [ImagePreviewsHalogenHooks](recipes/ImagePreviewsHalogenHooks) | A Halogen port of the ["Files - Drag-and-Drop" Elm Example](https://elm-lang.org/examples/drag-and-drop). |
107108
| | :heavy_check_mark: | [NumbersHalogenHooks](recipes/NumbersHalogenHooks) | A Halogen port of the ["Random - Numbers" Elm Example](https://elm-lang.org/examples). |
108109
| | :heavy_check_mark: | [NumbersReactHooks](recipes/NumbersReactHooks) | A React port of the ["Random - Numbers" Elm Example](https://elm-lang.org/examples/numbers). |
109110
| | :heavy_check_mark: | [PositionsHalogenHooks](recipes/PositionsHalogenHooks) | A Halogen port of the ["Random - Positions" Elm Example](https://elm-lang.org/examples). |
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/bower_components/
2+
/node_modules/
3+
/.pulp-cache/
4+
/output/
5+
/generated-docs/
6+
/.psc-package/
7+
/.psc*
8+
/.purs*
9+
/.psa*
10+
/.spago
11+
/web-dist/
12+
/prod-dist/
13+
/prod/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# ImagePreviewsHalogenHooks
2+
3+
A Halogen port of the ["Files - Drag-and-Drop" Elm Example](https://elm-lang.org/examples/drag-and-drop).
4+
5+
## Expected Behavior:
6+
7+
### Browser
8+
9+
When the user drags a file from their computer on top of the dashed area, the area will change colors, indicating that the file will be uploaded once the user drops it there. The user can also select a list of files by clicking on the button instead. The labels in the example show the names of the last-uploaded files (i.e. `files = `) and whether the user is currently dragging a file over the droppable area (i.e. `hover = `).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{ name = "ImagePreviewsHalogenHooks"
2+
, dependencies =
3+
[ "console"
4+
, "effect"
5+
, "halogen-css"
6+
, "halogen-hooks"
7+
, "halogen-hooks-extra"
8+
, "psci-support"
9+
, "random"
10+
]
11+
, packages = ../../packages.dhall
12+
, sources = [ "recipes/ImagePreviewsHalogenHooks/src/**/*.purs" ]
13+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
module ImagePreviewsHalogenHooks.Main where
2+
3+
import Prelude hiding (top)
4+
5+
import CSS (alignItems, backgroundImage, backgroundPosition, backgroundRepeat, backgroundSize, border, borderRadius, column, contain, dashed, display, displayNone, flex, flexDirection, gray, height, justifyContent, margin, noRepeat, padding, placed, purple, px, sideCenter, solid, url, width)
6+
import CSS as CSS
7+
import CSS.Common (center)
8+
import DOM.HTML.Indexed.InputAcceptType (mediaType)
9+
import DOM.HTML.Indexed.InputType (InputType(..))
10+
import Data.Array (snoc)
11+
import Data.Maybe (Maybe(..), maybe)
12+
import Data.MediaType (MediaType(..))
13+
import Data.Traversable (for, sequence_)
14+
import Data.Tuple.Nested ((/\))
15+
import Effect (Effect)
16+
import Effect.Class (class MonadEffect, liftEffect)
17+
import Effect.Ref (Ref)
18+
import Effect.Ref as Ref
19+
import Halogen (ClassName(..))
20+
import Halogen as H
21+
import Halogen.Aff as HA
22+
import Halogen.HTML as HH
23+
import Halogen.HTML.CSS as HC
24+
import Halogen.HTML.Events as HE
25+
import Halogen.HTML.Properties as HP
26+
import Halogen.Hooks (HookM, StateToken)
27+
import Halogen.Hooks as Hooks
28+
import Halogen.Hooks.Extra.Actions.Events (preventDefault)
29+
import Halogen.VDom.Driver (runUI)
30+
import Web.File.File (File, toBlob)
31+
import Web.File.FileList as FileList
32+
import Web.File.Url (createObjectURL, revokeObjectURL)
33+
import Web.HTML.Event.DataTransfer as DataTransfer
34+
import Web.HTML.Event.DragEvent (dataTransfer)
35+
import Web.HTML.Event.DragEvent as DragEvent
36+
37+
main :: Effect Unit
38+
main =
39+
HA.runHalogenAff do
40+
body <- HA.awaitBody
41+
void $ runUI hookComponent unit body
42+
43+
hookComponent
44+
:: forall unusedQuery unusedInput unusedOutput anyMonad
45+
. MonadEffect anyMonad
46+
=> H.Component HH.HTML unusedQuery unusedInput unusedOutput anyMonad
47+
hookComponent = Hooks.component \_ _ -> Hooks.do
48+
hover /\ hoverIdx <- Hooks.useState false
49+
_ /\ ref <- Hooks.useRef []
50+
urls /\ urlsIdx <- Hooks.useState []
51+
Hooks.pure $
52+
HH.div
53+
[ HC.style do
54+
border dashed (px 6.0) $ if hover then purple else CSS.fromInt 0xcccccc
55+
borderRadius (px 20.0) (px 20.0) (px 20.0) (px 20.0)
56+
width (px 480.0)
57+
height (px 100.0)
58+
margin (px 100.0) (px 100.0) (px 100.0) (px 100.0)
59+
padding (px 40.0) (px 40.0) (px 40.0) (px 40.0)
60+
display flex
61+
flexDirection column
62+
justifyContent center
63+
alignItems center
64+
, HE.onDragEnter \e -> Just do
65+
preventDefault DragEvent.toEvent e
66+
Hooks.put hoverIdx true
67+
, HE.onDragOver \e -> Just do
68+
preventDefault DragEvent.toEvent e
69+
Hooks.put hoverIdx true
70+
, HE.onDragLeave \e -> Just do
71+
preventDefault DragEvent.toEvent e
72+
Hooks.put hoverIdx false
73+
, HE.onDrop \e -> Just do
74+
preventDefault DragEvent.toEvent e
75+
let
76+
mbFileList = DataTransfer.files $ dataTransfer e
77+
fileArray = maybe [] FileList.items mbFileList
78+
putFileUrls ref urlsIdx fileArray
79+
]
80+
-- Note: Elm uses a button that, when clicked, will do the following:
81+
-- 1. create an input element
82+
-- 2. add it to the DOM
83+
-- 3. create a mouse event
84+
-- 4. dispatch the mouse event to the input element
85+
-- 5. (implication) file dialogue appears
86+
-- 6. user selects a file
87+
-- 7. input event handler runs a callback using user's selected file
88+
-- 8. input element is removed from DOM
89+
--
90+
-- The approach used below is based on this SO answer:
91+
-- https://stackoverflow.com/a/47094148
92+
[ HH.label
93+
[ HP.for "file-input" ]
94+
[ HH.div
95+
-- simulate button-like appearance
96+
[ HC.style do
97+
margin (px 4.0) (px 4.0) (px 4.0) (px 4.0)
98+
border solid (px 2.0) gray
99+
borderRadius (px 20.0) (px 20.0) (px 20.0) (px 20.0)
100+
padding (px 20.0) (px 20.0) (px 20.0) (px 20.0)
101+
, HP.class_ $ ClassName "otherCssNotInPurescript-Css"
102+
]
103+
[ HH.text "Upload images" ]
104+
]
105+
, HH.input
106+
[ HP.id_ "file-input"
107+
, HC.style $ display displayNone
108+
, HP.type_ InputFile
109+
, HP.accept $ mediaType $ MediaType "images/*"
110+
, HP.multiple true
111+
, HE.onFileUpload \fileArray -> Just do
112+
putFileUrls ref urlsIdx fileArray
113+
]
114+
, HH.div
115+
[ HC.style do
116+
display flex
117+
alignItems center
118+
height (px 60.0)
119+
padding (px 20.0) (px 20.0) (px 20.0) (px 20.0)
120+
]
121+
(urls <#> \fileUrl ->
122+
HH.div
123+
[ HC.style do
124+
width (px 60.0)
125+
height (px 60.0)
126+
backgroundImage $ url fileUrl
127+
backgroundPosition $ placed sideCenter sideCenter
128+
backgroundRepeat noRepeat
129+
backgroundSize contain
130+
]
131+
[]
132+
)
133+
]
134+
where
135+
putFileUrls
136+
:: Ref (Array (Effect Unit))
137+
-> StateToken (Array String)
138+
-> Array File
139+
-> HookM anyMonad Unit
140+
putFileUrls arrayRef idx files = do
141+
-- revoke all prior object urls
142+
-- by running their effects and ignoring the result
143+
liftEffect do
144+
arrayOfRemovePriorObjectUrls <- Ref.read arrayRef
145+
sequence_ arrayOfRemovePriorObjectUrls
146+
147+
arrayOfUrls <- for files \file -> do
148+
liftEffect do
149+
-- create the object url
150+
urlString <- createObjectURL (toBlob file)
151+
152+
-- create an effect that, when run, will revoke the url
153+
-- and clean up memory
154+
let
155+
revokeUrl :: Effect Unit
156+
revokeUrl = revokeObjectURL urlString
157+
-- add that effect to our mutable reference
158+
Ref.modify_ (\arr -> arr `snoc` revokeUrl) arrayRef
159+
160+
-- now return the original url
161+
pure urlString
162+
Hooks.put idx arrayOfUrls
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.otherCssNotInPurescript-Css {
2+
background-color: lightblue;
3+
cursor: pointer;
4+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>ImagePreviewsHalogenHooks</title>
6+
<link rel="stylesheet" href="./index.css">
7+
</head>
8+
9+
<body>
10+
<script src="./index.js"></script>
11+
</body>
12+
</html>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"use strict";
2+
require("../../../output/ImagePreviewsHalogenHooks.Main/index.js").main();

0 commit comments

Comments
 (0)