parent
a6de279660
commit
5eeda7aa0b
|
@ -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
|
||||
|
|
|
@ -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])))
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]))))
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
(ns status-im.ui.components.text
|
||||
(:require [status-im.ui.components.react :as react]
|
||||
[status-im.utils.platform :as platform]))
|
|
@ -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]))
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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)])}]]]]))
|
||||
|
|
|
@ -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"))))
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue