Skip to content

Add useQRCode hook #134

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

Merged
merged 2 commits into from
Mar 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const componentv2Loaders = [
"ButtonGroup",
"Clip",
"Link",
"QRCode",
"Slat"
].map(fromComponentPathv2);

Expand Down
75 changes: 75 additions & 0 deletions docs/Examples2/QRCode.example.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
module Lumi.Components2.Examples.QRCode where

import Prelude
import Data.Foldable (traverse_)
import Data.Maybe (Maybe(..), fromMaybe)
import Effect.Unsafe (unsafePerformEffect)
import Lumi.Components (lumiElement)
import Lumi.Components.Example (example)
import Lumi.Components.Input as Input
import Lumi.Components.Spacing (Space(..), vspace)
import Lumi.Components.Text (subsectionHeader_)
import Lumi.Components2.Box (box)
import Lumi.Components2.Link (link)
import Lumi.Components2.QRCode (ErrorCorrectLevel(..), useQRCode)
import Lumi.Styles as S
import Lumi.Styles.Border as Border
import Lumi.Styles.Box (FlexAlign(..))
import Lumi.Styles.Box as Box
import React.Basic.DOM.Events (capture, targetValue)
import React.Basic.Hooks (JSX, ReactComponent, component, element, useState, (/\))
import React.Basic.Hooks as React
import Web.HTML.History (URL(..))

docs :: JSX
docs =
flip element {}
$ unsafePerformEffect do
component "QRCodeExample" \_ -> React.do
value /\ setValue <- useState "https://www.lumi.com"
pure
$ lumiElement box
_
{ content =
[ Input.input
Input.text_
{ value = value
, onChange = capture targetValue $ traverse_ (const >>> setValue)
}
, vspace S24
, example
$ element qrcodeExample { value }
]
}

qrcodeExample :: ReactComponent { value :: String }
qrcodeExample =
unsafePerformEffect do
component "QRCode" \props -> React.do
{ qrcode, url } <- useQRCode ECLLow props.value
pure
$ lumiElement box
<<< Box._align Center
$ _
{ content =
[ lumiElement qrcode
<<< Border.border
>>> Border._round
>>> S.styleModifier_
( S.css
{ padding: S.int 16
, width: S.int 140
}
)
$ identity
, vspace S8
, lumiElement link
_
{ href = fromMaybe (URL "") url
, download = Just "qrcode.svg"
, content =
[ subsectionHeader_ "Download SVG"
]
}
]
}
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"big-integer": "^1.6.44",
"jss": "^10.0.0-alpha.16",
"jss-preset-default": "^10.0.0-alpha.16",
"qrcode.react": "^1.0.0",
"react": "^16.6.1",
"react-dnd": "^2.6.0",
"react-dnd-html5-backend": "^2.6.0",
Expand Down
35 changes: 17 additions & 18 deletions src/Lumi/Components2/Link.purs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,12 @@ type LinkProps
, navigate :: Maybe (Effect Unit)
, tabIndex :: Int
, target :: Maybe String
, download :: Maybe String
, content :: Array JSX
, className :: String
)

link ::
LumiComponent
( className :: String
, content :: Array JSX
, href :: URL
, navigate :: Maybe (Effect Unit)
, target :: Maybe String
)
link :: LumiComponent LinkProps
link =
unsafePerformEffect do
let
Expand All @@ -44,7 +39,9 @@ link =
{ className: ""
, href: URL ""
, navigate: Nothing
, tabIndex: 0
, target: Nothing
, download: Nothing
, content: []
}
lumiComponent "Link" defaults \props@{ className } -> React.do
Expand All @@ -56,15 +53,17 @@ link =
, className
, href: un URL props.href
, onClick:
handler (merge { button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent }) \{ button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent } -> do
case props.navigate, button, metaKey, altKey, ctrlKey, shiftKey of
Just n', Just 0, Just false, Just false, Just false, Just false ->
runEffectFn1
(handler (stopPropagation <<< preventDefault) $ const n')
syntheticEvent
_, _, _, _, _, _ ->
runEffectFn1
(handler stopPropagation mempty)
syntheticEvent
handler (merge { button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent }) \{ button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent } -> do
case props.navigate, button, metaKey, altKey, ctrlKey, shiftKey of
Just n', Just 0, Just false, Just false, Just false, Just false ->
runEffectFn1
(handler (stopPropagation <<< preventDefault) $ const n')
syntheticEvent
_, _, _, _, _, _ ->
runEffectFn1
(handler stopPropagation mempty)
syntheticEvent
, target: toNullable props.target
, tabIndex: props.tabIndex
, download: toNullable props.download
}
20 changes: 20 additions & 0 deletions src/Lumi/Components2/QRCode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use strict";

exports.qrcode_ = require("qrcode.react");

exports.generateSVGUrl = ref => () => {
const containerNode = ref.current;
if (containerNode == null) {
throw new Error("Cannot save the contents of an empty ref");
}
const svgNode = containerNode.querySelector("svg");
if (svgNode == null) {
throw new Error("Inner SVG node not found");
}

const data = new XMLSerializer().serializeToString(svgNode);
const svg = new Blob([data], { type: "image/svg+xml;charset=utf-8" });
const url = URL.createObjectURL(svg);
const dispose = () => URL.revokeObjectURL(url);
return { url, dispose };
};
95 changes: 95 additions & 0 deletions src/Lumi/Components2/QRCode.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
module Lumi.Components2.QRCode where

import Prelude
import Data.Maybe (Maybe(..))
import Data.Newtype (class Newtype)
import Data.Nullable as Nullable
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import Lumi.Components (LumiComponent, lumiComponent)
import Lumi.Styles (toCSS)
import Lumi.Styles.QRCode as Styles.QRCode
import Lumi.Styles.Theme (useTheme)
import React.Basic.DOM as R
import React.Basic.Emotion as E
import React.Basic.Hooks (type (/\), Hook, ReactComponent, Ref, UnsafeReference(..), UseEffect, UseMemo, UseRef, UseState, coerceHook, element, useEffect, useMemo, useRef, useState, (/\))
import React.Basic.Hooks as React
import Web.DOM (Node)
import Web.HTML.History (URL(..))

newtype UseQRCode hooks
= UseQRCode
( UseEffect
(UnsafeReference (LumiComponent ()))
( UseState
(Maybe URL)
( UseMemo
(String /\ ErrorCorrectLevel)
(LumiComponent ())
(UseRef (Nullable.Nullable Node) hooks)
)
)
)

derive instance ntUseQRCode :: Newtype (UseQRCode hooks) _

data ErrorCorrectLevel
= ECLLow
| ECLMedium
| ECLQuality
| ECLHigh

derive instance eqErrorCorrectLevel :: Eq ErrorCorrectLevel

errorCorrectLevelToString :: ErrorCorrectLevel -> String
errorCorrectLevelToString = case _ of
ECLLow -> "L"
ECLMedium -> "M"
ECLQuality -> "Q"
ECLHigh -> "H"

useQRCode :: ErrorCorrectLevel -> String -> Hook UseQRCode { qrcode :: LumiComponent (), url :: Maybe URL }
useQRCode level value =
coerceHook React.do
ref <- useRef Nullable.null
qrcode <-
useMemo (value /\ level) \_ ->
unsafePerformEffect do
lumiComponent "QRCode" {} \props -> React.do
theme <- useTheme
pure
$ E.element R.div'
{ children:
[ element qrcode_
{ value
, level: errorCorrectLevelToString level
, renderAs: "svg"
, xmlns: "http://www.w3.org/2000/svg"
, size: Nullable.null
, bgColor: "rgba(255,255,255,0.0)"
, fgColor: "rgba(0,0,0,1.0)"
}
]
, ref
, className: props.className
, css: toCSS theme props Styles.QRCode.qrcode
}
url /\ setUrl <- useState Nothing
useEffect (UnsafeReference qrcode) do
svgUrl <- generateSVGUrl ref
setUrl \_ -> Just $ URL svgUrl.url
pure svgUrl.dispose
pure { qrcode, url }

foreign import qrcode_ ::
ReactComponent
{ value :: String
, level :: String
, renderAs :: String
, xmlns :: String
, size :: Nullable.Nullable Int
, bgColor :: String
, fgColor :: String
}

foreign import generateSVGUrl :: Ref (Nullable.Nullable Node) -> Effect { url :: String, dispose :: Effect Unit }
Loading