Signed-off-by: Julien Eluard <julien.eluard@gmail.com>
This commit is contained in:
Julien Eluard 2018-10-15 14:55:01 +02:00
parent a6de279660
commit 5eeda7aa0b
No known key found for this signature in database
GPG Key ID: 6FD7DB5437FCBEF6
13 changed files with 235 additions and 63 deletions

View File

@ -88,9 +88,9 @@
(dissoc :font)
(assoc style-key (merge style font)))))
(defn transform-to-uppercase [{:keys [uppercase? force-uppercase?] :as opts} ts]
(defn transform-to-uppercase [{:keys [uppercase? force-uppercase?]} ts]
(if (or force-uppercase? (and uppercase? platform/android?))
(vec (map string/upper-case ts))
(vec (map #(when % (string/upper-case %)) ts))
ts))
(defn text

View File

@ -130,24 +130,27 @@
(def command-hook
"Hook for extensions"
{:properties
{:scope #{:personal-chats :public-chats}
{:description? :string
:scope #{:personal-chats :public-chats}
:short-preview :view
:preview :view
:on-send? :event
:on-receive? :event
:parameters [{:id :keyword
:type {:one-of #{:text :phone :password :number}}
:placeholder :string
:suggestions :view}]}
:hook
(reify hooks/Hook
(hook-in [_ id {:keys [description scope parameters preview short-preview]} cofx]
(hook-in [_ id {:keys [description scope parameters preview short-preview on-send on-receive]} cofx]
(let [new-command (reify protocol/Command
(id [_] (name id))
(scope [_] scope)
(description [_] description)
(parameters [_] parameters)
(validate [_ _ _])
(on-send [_ _ _])
(on-receive [_ _ _])
(on-send [_ _ _] {:dispatch on-send})
(on-receive [_ _ _] {:dispatch on-receive})
(short-preview [_ props] (short-preview props))
(preview [_ props] (preview props)))]
(load-commands cofx [new-command])))

View File

@ -131,7 +131,7 @@
(defn choose-nft-token-suggestion [selected-event-creator]
[choose-nft-token selected-event-creator])
(defview nft-token [{:keys [name image_url] :as token}]
(defview nft-token [{{:keys [name image_url]} :token}]
[react/view {:flex-direction :column
:align-items :center}
[svgimage/svgimage {:style {:width 100
@ -200,6 +200,9 @@
tx-exists? :status-pending
:else :status-tx-not-found))]]]]))
(defn transaction-status [{:keys [tx-hash outgoing]}]
[send-status tx-hash outgoing])
(defview send-preview
[{:keys [content timestamp-str outgoing group-chat]}]
(letsubs [network [:network-name]]

View File

@ -33,7 +33,7 @@
"Function which can provide any extra effects to be produced in addition to
normal message effects which happen whenever message is sent")
(on-receive [this command-message cofx]
"Function which can provide any extre effects to be produced in addition to
"Function which can provide any extra effects to be produced in addition to
normal message effects which happen when particular command message is received")
(short-preview [this command-message]
"Function rendering the short-preview of the command message, used when

View File

@ -286,7 +286,7 @@
:chat-parameter-box
:<- [:get-current-chat]
:<- [:selected-chat-command]
(fn [[current-chat {:keys [current-param-position params]}]]
(fn [[_ {:keys [current-param-position params]}]]
(when (and params current-param-position)
(get-in params [current-param-position :suggestions]))))

View File

@ -1,36 +1,162 @@
(ns status-im.extensions.core
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[pluto.reader :as reader]
[pluto.registry :as registry]
[pluto.storages :as storages]
[status-im.chat.commands.core :as commands]
[status-im.chat.commands.impl.transactions :as transactions]
[status-im.ui.components.react :as react]
[status-im.ui.components.button.view :as button]
[status-im.utils.handlers :as handlers]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.fx :as fx]))
(def components
{'view {:value react/view}
'text {:value react/text}
'nft-token {:value transactions/nft-token}
'send-status {:value transactions/send-status}
'asset-selector {:value transactions/choose-nft-asset-suggestion}
'token-selector {:value transactions/choose-nft-token-suggestion}})
(re-frame/reg-fx
::alert
(fn [value] (js/alert value)))
(def app-hooks #{commands/command-hook})
(re-frame/reg-event-fx
:alert
(fn [_ [_ {:keys [value]}]]
{::alert value}))
(re-frame/reg-fx
::log
(fn [value] (js/console.log value)))
(re-frame/reg-event-fx
:log
(fn [_ [_ {:keys [value]}]]
{::log value}))
(re-frame/reg-sub
:store/get
(fn [db [_ {:keys [key]}]]
(get-in db [:extensions-store :collectible key])))
(handlers/register-handler-fx
:store/put
(fn [{:keys [db]} [_ {:keys [key value]}]]
{:db (assoc-in db [:extensions-store :collectible key] value)}))
(defn- append [acc k v]
(let [o (get acc k)]
(assoc acc k (conj (if (vector? o) o (vector o)) v))))
(handlers/register-handler-fx
:store/append
(fn [{:keys [db]} [_ {:keys [key value]}]]
{:db (update-in db [:extensions-store :collectible] append key value)}))
(handlers/register-handler-fx
:store/clear
(fn [{:keys [db]} [_ {:keys [key]}]]
{:db (update-in db [:extensions-store :collectible] dissoc key)}))
(re-frame/reg-event-fx
:http/get
(fn [_ [_ {:keys [url on-success on-failure timeout]}]]
{:http-get (merge {:url url
:success-event-creator (fn [o] (into on-success (vector o)))}
(when on-failure
{:failure-event-creator (fn [o] (into on-failure (vector o)))})
(when timeout
{:timeout-ms timeout}))}))
(defn button [{:keys [on-click]} label]
[button/secondary-button {:on-press #(re-frame/dispatch on-click)} label])
(defn input [{:keys [on-change placeholder]}]
[react/text-input {:on-change-text #(re-frame/dispatch on-change) :placeholder placeholder}])
(def capacities
(reduce (fn [capacities hook]
(assoc-in capacities [:hooks :commands] hook))
{:components components
:queries {'get-collectible-token {:value :get-collectible-token}}
:events {}}
app-hooks))
{:components {'view {:value react/view}
'text {:value react/text}
'input {:value input :properties {:on-change :event :placeholder :string}}
'button {:value button :properties {:on-click :event}}
'nft-token-viewer {:value transactions/nft-token :properties {:token :string}}
'transaction-status {:value transactions/transaction-status :properties {:outgoing :string :tx-hash :string}}
'asset-selector {:value transactions/choose-nft-asset-suggestion}
'token-selector {:value transactions/choose-nft-token-suggestion}}
:queries {'store/get {:value :store/get :arguments {:key :string}}
'get-collectible-token {:value :get-collectible-token :arguments {:token :string :symbol :string}}}
:events {'alert
{:permissions [:read]
:value :alert
:arguments {:value :string}}
'log
{:permissions [:read]
:value :log
:arguments {:value :string}}
'store/put
{:permissions [:read]
:value :store/put
:arguments {:key :string :value :string}}
'store/append
{:permissions [:read]
:value :store/append
:arguments {:key :string :value :string}}
'store/clear
{:permissions [:read]
:value :store/put
:arguments {:key :string}}
'http/get
{:permissions [:read]
:value :http/get
:arguments {:url :string
:timeout? :string
:on-success :event
:on-failure? :event}}
'browser/open {:value :browser/open :arguments {:url :string}}
'chat/open {:value :chat/open :arguments {:url :string}}
'ethereum/sign
{:arguments
{:account :string
:message :string
:on-result :event}}
'ethereum/send-raw-transaction
{:arguments {:data :string}}
'ethereum/send-transaction
{:arguments
{:from :string
:to :string
:gas? :string
:gas-price? :string
:value? :string
:data? :string
:nonce? :string}}
'ethereum/new-contract
{:arguments
{:from :string
:gas? :string
:gas-price? :string
:value? :string
:data? :string
:nonce? :string}}
'ethereum/call
{:arguments
{:from? :string
:to :string
:gas? :string
:gas-price? :string
:value? :string
:data? :string
:block :string}}
'ethereum/logs
{:arguments
{:from? :string
:to :string
:address :string
:topics :string
:blockhash :string}}}
:hooks {:commands commands/command-hook}})
(defn read-extension [o]
(-> o :value first :content reader/read))
(defn read-extension [{:keys [value]}]
(when (seq value)
(let [{:keys [content]} (first value)]
(reader/read content))))
(defn parse [{:keys [data] :as m}]
(defn parse [{:keys [data]}]
(try
(let [{:keys [errors] :as extension-data} (reader/parse {:capacities capacities} data)]
(when errors
@ -38,9 +164,16 @@
extension-data)
(catch :default e (println "EXC" e))))
(def uri-prefix "https://get.status.im/extension/")
(defn valid-uri? [s]
(boolean
(when s
(re-matches (re-pattern (str "^" uri-prefix "\\w+@\\w+")) (string/trim s)))))
(defn url->uri [s]
(when s
(string/replace s "https://get.status.im/extension/" "")))
(string/replace s uri-prefix "")))
(defn load-from [url f]
(when-let [uri (url->uri url)]

View File

@ -1,3 +0,0 @@
(ns status-im.ui.components.text
(:require [status-im.ui.components.react :as react]
[status-im.utils.platform :as platform]))

View File

@ -160,6 +160,7 @@
(spec/def ::extension-url (spec/nilable string?))
(spec/def ::staged-extension (spec/nilable any?))
(spec/def ::extensions-store (spec/nilable any?))
;;;;NODE
@ -235,6 +236,7 @@
:desktop/desktop
:dimensions/window
:dapps/permissions]
:opt-un
[::current-public-key
::modal
@ -308,4 +310,5 @@
::collectibles
::extension-url
::staged-extension
::extensions-store
:registry/registry]))

View File

@ -38,7 +38,7 @@
(defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}]
(let [on-success #(re-frame/dispatch (success-event-creator %))
on-error #(re-frame/dispatch (failure-event-creator %))
on-error (when failure-event-creator #(re-frame/dispatch (failure-event-creator %)))
opts {:valid-response? response-validator
:timeout-ms timeout-ms}]
(http/get url on-success on-error opts)))

View File

@ -2,8 +2,11 @@
(:require-macros [status-im.utils.views :as views])
(:require [re-frame.core :as re-frame]
[clojure.string :as string]
[status-im.extensions.core :as extensions]
[status-im.i18n :as i18n]
[status-im.ui.components.react :as react]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.common.common :as components.common]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.react :as react]
@ -13,49 +16,66 @@
[status-im.ui.components.text-input.view :as text-input]
[status-im.ui.screens.extensions.add.styles :as styles]))
(defn cartouche [{:keys [header]} content]
(defn cartouche [{:keys [header]} content]
[react/view {:style styles/cartouche-container}
[react/text {:style styles/cartouche-header}
header]
[react/view {:style styles/cartouche-content-wrapper}
[react/view {:flex 1}
[react/text {:style styles/text}
content]]]])
content]]])
(defn hooks [{:keys [hooks]}]
(mapcat (fn [[hook-id values]]
(map (fn [[id]]
(symbol "hook" (str (name hook-id) "." (name id))))
(str (name hook-id) "." (name id)))
values))
hooks))
(views/defview show-extension []
(views/letsubs [{:keys [data errors]} [:get-staged-extension]]
[react/view styles/screen
[status-bar/status-bar]
[react/keyboard-avoiding-view components.styles/flex
[toolbar/simple-toolbar (i18n/label :t/extension)]
[react/scroll-view {:keyboard-should-persist-taps :handled}
[react/view styles/wrapper
[cartouche {:header (i18n/label :t/identifier)}
(str (get-in data ['meta :name]))]
[cartouche {:header (i18n/label :t/name)}
(str (get-in data ['meta :name]))]
[cartouche {:header (i18n/label :t/description)}
(str (get-in data ['meta :description]))]
[cartouche {:header (i18n/label :t/hooks)}
(string/join " " (hooks data))]
[cartouche {:header (i18n/label :t/permissions)}
(i18n/label :t/none)]
[cartouche {:header (i18n/label :t/errors)}
(i18n/label :t/none)]]]
[react/view styles/bottom-container
[react/view components.styles/flex]
[components.common/bottom-button
{:forward? true
:label (i18n/label :t/install)
:disabled? (not (empty? errors))
:on-press #(re-frame/dispatch [:extension/install data])}]]]]))
(if data
[react/view styles/screen
[status-bar/status-bar]
[react/keyboard-avoiding-view components.styles/flex
[toolbar/simple-toolbar (i18n/label :t/extension)]
[react/scroll-view {:keyboard-should-persist-taps :handled}
[react/view styles/wrapper
[cartouche {:header (i18n/label :t/identifier)}
[react/text {:style styles/text}
(str (get-in data ['meta :name]))]]
[cartouche {:header (i18n/label :t/name)}
[react/text {:style styles/text}
(str (get-in data ['meta :name]))]]
[cartouche {:header (i18n/label :t/description)}
[react/text {:style styles/text}
(str (get-in data ['meta :description]))]]
[cartouche {:header (i18n/label :t/hooks)}
(into [react/view] (for [hook (hooks data)]
[react/text {:style styles/text}
(str hook)]))]
[cartouche {:header (i18n/label :t/permissions)}
[react/text {:style styles/text}
(i18n/label :t/none)]]
[cartouche {:header (i18n/label :t/errors)}
(if errors
(into [react/view] (for [error errors]
[react/text {:style styles/text}
(str (name (:pluto.reader.errors/type error)) " " (str (:pluto.reader.errors/value error)))]))
[react/text {:style styles/text}
(i18n/label :t/none)])]]]
[react/view styles/bottom-container
[react/view components.styles/flex]
[components.common/bottom-button
{:forward? true
:label (i18n/label :t/install)
:disabled? (not (empty? errors))
:on-press #(re-frame/dispatch [:extension/install data])}]]]]
[react/view styles/screen
[status-bar/status-bar]
[react/view {:flex 1}
[toolbar/simple-toolbar (i18n/label :t/extension)]
[react/view {:style {:flex 1 :justify-content :center :align-items :center}}
[react/text (i18n/label :t/invalid-extension)]]]])))
(def qr-code
[react/touchable-highlight {:on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed
@ -86,5 +106,5 @@
[components.common/bottom-button
{:forward? true
:label (i18n/label :t/find)
:disabled? (string/blank? extension-url)
:on-press #(re-frame/dispatch [:extension/show extension-url])}]]]]))
:disabled? (not (extensions/valid-uri? extension-url))
:on-press #(re-frame/dispatch [:extension/show (string/trim extension-url)])}]]]]))

View File

@ -0,0 +1,10 @@
(ns status-im.test.extensions.core
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.extensions.core :as extensions]))
(deftest valid-uri?
(is (= false (extensions/valid-uri? nil)))
(is (= false (extensions/valid-uri? "http://get.status.im/extension/ipfs")))
(is (= false (extensions/valid-uri? " http://get.status.im/extension/ipfs@QmWKqSanV4M4zrd55QMkvDMZEQyuHzCMHpX1Fs3dZeSExv ")))
(is (= true (extensions/valid-uri? " https://get.status.im/extension/ipfs@QmWKqSanV4M4zrd55QMkvDMZEQyuHzCMHpX1Fs3dZeSExv ")))
(is (= true (extensions/valid-uri? "https://get.status.im/extension/ipfs@QmWKqSanV4M4zrd55QMkvDMZEQyuHzCMHpX1Fs3dZeSExv"))))

View File

@ -3,6 +3,7 @@
[status-im.test.contacts.subs]
[status-im.test.data-store.chats]
[status-im.test.data-store.realm.core]
[status-im.test.extensions.core]
[status-im.test.browser.core]
[status-im.test.browser.permissions]
[status-im.test.wallet.subs]
@ -72,6 +73,7 @@
'status-im.test.init.core
'status-im.test.data-store.chats
'status-im.test.data-store.realm.core
'status-im.test.extensions.core
'status-im.test.mailserver.core
'status-im.test.group-chats.core
'status-im.test.node.core

View File

@ -385,6 +385,7 @@
"enter-dapp-url": "Enter a ÐApp URL",
"wallet-transaction-total-fee": "Total Fee",
"extension": "Extension",
"invalid-extension": "Invalid extension URI",
"datetime-day": {
"one": "day",
"other": "days"