Skip to content

Commit

Permalink
Support Viewing Coin Trending in Twitter (Trade Plugin) (#1458)
Browse files Browse the repository at this point in the history
* chore: define the trader plugin

* chore: TypedMessage & TypedMessageCashTrending

+ chore: add TypedMessageAnchor
+ chore: parse tweet into TypedMessage
+ chore: add TypedMessageCashTrending
+ chore: add PostDummy
+ chore: render TypedMessageCashTrending
+ fix: renderCompoundMessage

* chore(ui): add TrendingView

* chore: integrate with APIs

* chore: support WholePostVisibility

* chore: inject page inspector

* chore: add UI components

+ chore: add PriceChangedTable
+ chore: add TickersTable
+ chore: add MarketCapRank
+ chore: add PriceChart
+ chore: add Skeleton & PriceChartDaysControl

* chore: integrate with CMC apis

* refactor: better UX

* fix: build error

* chore: inject post dummy according to the WholePostVisibility settings

* chore: add MAX options into PriceChart

* chore: support WholePostVisibility

* chore: check availability before displaying TrendingView

* refactor: use URLSearchParams

* refactor: resolve reviews
  • Loading branch information
guanbinrui authored Sep 4, 2020
1 parent be30f62 commit d2179ec
Show file tree
Hide file tree
Showing 67 changed files with 3,367 additions and 63 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@servie/events": "^1.0.0",
"@types/bip39": "^3.0.0",
"@types/classnames": "^2.2.10",
"@types/d3": "^5.7.2",
"@types/elliptic": "^6.4.12",
"@types/gun": "^0.9.2",
"@types/json-stable-stringify": "^1.0.32",
Expand All @@ -81,6 +82,7 @@
"bip39": "^3.0.2",
"classnames": "^2.2.6",
"clipboard-polyfill": "^3.0.1",
"d3": "^5.16.0",
"elliptic": "^6.5.3",
"eth-contract-metadata": "^1.15.0",
"fuse.js": "^6.4.1",
Expand Down
20 changes: 18 additions & 2 deletions src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@
"import_your_persona": "Import Your Persona",
"internal_id": "Internal ID",
"keywords": "Keywords",
"language_en": "English",
"language_zh": "中文",
"language_ja": "日本語",
"mnemonic_words": "Mnemonic Words",
"my_personas": "My Personas",
"my_wallets": "My Wallets",
Expand Down Expand Up @@ -240,7 +243,12 @@
"settings_appearance_dark": "Dark",
"settings_appearance_light": "Light",
"settings_language": "Language",
"settings_choose_eth_network": "Choose Ethereum network",
"settings_choose_eth_network": "Choose Ethereum Network",
"settings_post_replace_all_posts": "All Posts",
"settings_post_replace_enhanced_posts": "Enhanced Posts",
"settings_post_replace_encrypted_posts": "Encrypted Posts",
"settings_post_replacement_scope": "Post Replacement Scope",
"settings_post_replacement_scope_desc": "Maskbook will render a new post to replace the original one to provide some enhancement.",
"skip": "Skip",
"share": "Share",
"share_to": "Share to…",
Expand Down Expand Up @@ -284,5 +292,13 @@
"plugin_file_service_drop_here": "Drop a file here to upload",
"plugin_file_service_error_101": "The input is not a single file.",
"plugin_file_service_error_102": "The file is too large; limit is {{limit}}.",
"plugin_file_service_on_change_file": "Change File"
"plugin_file_service_on_change_file": "Change File",
"plugin_trader_no_data": "No Data",
"plugin_trader_tab_price": "Price",
"plugin_trader_tab_exchange": "Exchange",
"plugin_trader_tab_switch_data_source": "Switch Data Source: ",
"plugin_trader_table_exchange": "Exchange",
"plugin_trader_table_pair": "Pair",
"plugin_trader_table_price": "Price",
"plugin_trader_table_volume": "Volume (24h)"
}
18 changes: 17 additions & 1 deletion src/_locales/ja/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@
"import_your_persona": "人格をインポート",
"internal_id": "内部 ID",
"keywords": "キーワード",
"language_en": "English",
"language_zh": "中文",
"language_ja": "日本語",
"mnemonic_words": "パスフレーズ",
"my_personas": "私の人格アカウント",
"my_wallets": "私のウォレット",
Expand Down Expand Up @@ -241,6 +244,11 @@
"settings_appearance_light": "ライトモード",
"settings_language": "言語を設定",
"settings_choose_eth_network": "Ethereum ネットワークを選択してください",
"settings_post_replace_all_posts": "すべての投稿",
"settings_post_replace_enhanced_posts": "強化された投稿",
"settings_post_replace_encrypted_posts": "暗号化された投稿",
"settings_post_replacement_scope": "ポスト交換範囲",
"settings_post_replacement_scope_desc": "Maskbook は、新しい投稿をレンダリングして元の投稿を置き換え、いくつかの拡張機能を提供します。",
"skip": "スキップ",
"share": "共有",
"share_to": "共有先",
Expand Down Expand Up @@ -284,5 +292,13 @@
"plugin_file_service_drop_here": "ファイルをここにドラッグ&ドロップ",
"plugin_file_service_error_101": "一つのファイルだけにしてください!",
"plugin_file_service_error_102": "ファイルが大きすぎます!最大容量は{{limit}}です。",
"plugin_file_service_on_change_file": "ファイルの変更"
"plugin_file_service_on_change_file": "ファイルの変更",
"plugin_trader_no_data": "データなし",
"plugin_trader_tab_price": "価格",
"plugin_trader_tab_exchange": "取引所",
"plugin_trader_tab_switch_data_source": "データソースの切り替え: ",
"plugin_trader_table_exchange": "取引所",
"plugin_trader_table_pair": "通貨ペア",
"plugin_trader_table_price": "価格",
"plugin_trader_table_volume": "取引高"
}
18 changes: 17 additions & 1 deletion src/_locales/zh/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@
"import_your_persona": "導入角色",
"internal_id": "內部ID",
"keywords": "關鍵字",
"language_en": "English",
"language_zh": "中文",
"language_ja": "日本語",
"mnemonic_words": "助記詞",
"my_personas": "我的角色",
"my_wallets": "我的錢包",
Expand Down Expand Up @@ -241,6 +244,11 @@
"settings_appearance_light": "淺色",
"settings_language": "語言",
"settings_choose_eth_network": "選擇以太坊網絡",
"settings_post_replace_all_posts": "所有帖子",
"settings_post_replace_enhanced_posts": "僅增強的帖子",
"settings_post_replace_encrypted_posts": "僅加密的帖子",
"settings_post_replacement_scope": "帖子替換範圍",
"settings_post_replacement_scope_desc": "Maskbook 將呈現一個新帖子以替換原始帖子,以提供一些增強功能。",
"skip": "跳過",
"share": "分享",
"share_to": "分享给…",
Expand Down Expand Up @@ -284,5 +292,13 @@
"plugin_file_service_drop_here": "拖入文件來上傳",
"plugin_file_service_error_101": "只能選擇單個文檔",
"plugin_file_service_error_102": "文檔尺寸過大。請不要超過 {{limit}}。",
"plugin_file_service_on_change_file": "選擇其他文檔"
"plugin_file_service_on_change_file": "選擇其他文檔",
"plugin_trader_no_data": "暫無數據",
"plugin_trader_tab_price": "價格",
"plugin_trader_tab_exchange": "交易標的",
"plugin_trader_tab_switch_data_source": "切換數據來源: ",
"plugin_trader_table_exchange": "交易標的",
"plugin_trader_table_pair": "",
"plugin_trader_table_price": "價格",
"plugin_trader_table_volume": "交易量 (24小時)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const useStyles = makeStyles((theme: Theme) => ({
root: { boxSizing: 'border-box', width: '100%', backgroundColor: 'transparent', borderColor: 'transparent' },
title: { display: 'flex', alignItems: 'center' },
icon: { paddingRight: theme.spacing(0.75), display: 'flex', width: 20, height: 20 },
content: { margin: theme.spacing(1, 0), padding: 0 },
content: { margin: theme.spacing(1, 0), padding: 0, overflowWrap: 'break-word' },
rightIcon: { paddingLeft: theme.spacing(0.75) },
}))

Expand Down
20 changes: 20 additions & 0 deletions src/components/InjectedComponents/PageInspector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import { PluginUI, PluginConfig } from '../../plugins/plugin'

export interface PageInspectorProps {}

export function PageInspector(props: PageInspectorProps) {
return (
<>
{[...PluginUI.values()].map((x) => (
<PluginPageInspectorForEach key={x.identifier} config={x} />
))}
</>
)
}

function PluginPageInspectorForEach({ config }: { config: PluginConfig }) {
const F = config.pageInspector
if (typeof F === 'function') return <F />
return null
}
57 changes: 57 additions & 0 deletions src/components/InjectedComponents/PostReplacer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { useEffect, useMemo } from 'react'
import { usePostInfoDetails } from '../DataSource/usePostInfo'
import { DefaultTypedMessageRenderer } from './TypedMessageRenderer'
import { PluginUI } from '../../plugins/plugin'
import { makeTypedMessageCompound, isTypedMessageSuspended, isTypedMessageKnown } from '../../protocols/typed-message'
import { useValueRef } from '../../utils/hooks/useValueRef'
import { currentPostReplacementScopeSettings, PostReplacementScope } from '../../settings/settings'
import { makeStyles, Theme } from '@material-ui/core'

const useStlyes = makeStyles((theme: Theme) => ({
root: {
overflowWrap: 'break-word',
},
}))

export interface PostReplacerProps {
zip?: () => void
unzip?: () => void
}

export function PostReplacer(props: PostReplacerProps) {
const classes = useStlyes()
const postContent = usePostInfoDetails('postContent')
const postMessage = usePostInfoDetails('postMessage')
const postPayload = usePostInfoDetails('postPayload')
const postRepalcementScope = useValueRef(currentPostReplacementScopeSettings)

const plugins = [...PluginUI.values()]
const processedPostMessage = useMemo(
() => plugins.reduce((x, plugin) => plugin.messageProcessor?.(x) ?? x, postMessage),
[plugins.map((x) => x.identifier).join(), postContent],
)
const shouldReplacePost =
// replace all posts
postRepalcementScope === PostReplacementScope.all ||
// replace posts which enhanced by plugins
(postRepalcementScope === PostReplacementScope.enhancedOnly &&
processedPostMessage.items.some((x) => !isTypedMessageKnown(x))) ||
// replace posts which encrypted by maskbook
(postRepalcementScope === PostReplacementScope.encryptedOnly && postPayload.ok)

// zip/unzip original post
useEffect(() => {
if (shouldReplacePost) props.zip?.()
else props.unzip?.()
}, [shouldReplacePost])

return shouldReplacePost ? (
<span className={classes.root}>
<DefaultTypedMessageRenderer
message={makeTypedMessageCompound(
processedPostMessage.items.filter((x) => !isTypedMessageSuspended(x)),
)}
/>
</span>
) : null
}
48 changes: 44 additions & 4 deletions src/components/InjectedComponents/TypedMessageRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import anchorme from 'anchorme'
import {
TypedMessage,
TypedMessageText,
TypedMessageAnchor,
TypedMessageImage,
TypedMessageCompound,
TypedMessageUnknown,
TypedMessageSuspended,
registerTypedMessageRenderer,
TypedMessageEmpty,
makeTypedMessageText,
} from '../../protocols/typed-message'
import { Image } from '../shared/Image'
import { useAsync } from 'react-use'
Expand Down Expand Up @@ -43,7 +46,7 @@ export const DefaultTypedMessageTextRenderer = React.memo(function DefaultTypedM
) {
return renderWithMetadata(
props,
<Typography variant="body1" style={{ lineBreak: 'anywhere' }} data-testid="text_payload">
<Typography component="span" color="textPrimary" variant="body1" data-testid="text_payload">
<RenderText text={props.message.content}></RenderText>
</Typography>,
)
Expand All @@ -54,13 +57,32 @@ registerTypedMessageRenderer('text', {
priority: 0,
})

export const DefaultTypedMessageAnchorRenderer = React.memo(function DefaultTypedMessageAnchorRenderer(
props: TypedMessageRendererProps<TypedMessageAnchor>,
) {
const { content, href } = props.message
return renderWithMetadata(
props,
<Typography component="span" variant="body1" data-testid="anchor_payload">
<Link color="primary" target="_blank" rel="noopener noreferrer" href={href}>
{content}
</Link>
</Typography>,
)
})
registerTypedMessageRenderer('anchor', {
component: DefaultTypedMessageAnchorRenderer,
id: 'maskbook.anchor',
priority: 0,
})

export const DefaultTypedMessageImageRenderer = React.memo(function DefaultTypedMessageImageRenderer(
props: TypedMessageRendererProps<TypedMessageImage>,
) {
const { image, width, height } = props.message
return renderWithMetadata(
props,
<Typography variant="body1" style={{ lineBreak: 'anywhere' }} data-testid="text_payload">
<Typography variant="body1" data-testid="image_payload">
<Image src={image} width={width} height={height} />
</Typography>,
)
Expand Down Expand Up @@ -99,10 +121,21 @@ registerTypedMessageRenderer('compound', {
priority: 0,
})

export const DefaultTypedMessageEmptyRenderer = React.memo(function DefaultTypedMessageEmptyRenderer(
props: TypedMessageRendererProps<TypedMessageEmpty>,
) {
return renderWithMetadata(props, null)
})
registerTypedMessageRenderer('empty', {
component: DefaultTypedMessageEmptyRenderer,
id: 'maskbook.empty',
priority: 0,
})

export const DefaultTypedMessageUnknownRenderer = React.memo(function DefaultTypedMessageUnknownRenderer(
props: TypedMessageRendererProps<TypedMessageUnknown>,
) {
return renderWithMetadata(props, <Typography>Unknown message</Typography>)
return renderWithMetadata(props, <Typography color="textPrimary">Unknown message</Typography>)
})
registerTypedMessageRenderer('unknown', {
component: DefaultTypedMessageUnknownRenderer,
Expand All @@ -115,9 +148,16 @@ export const DefaultTypedMessageSuspendedRenderer = React.memo(function DefaultT
) {
const { promise } = props.message
const { loading, error, value } = useAsync(() => promise, [promise])

return renderWithMetadata(
props,
loading ? 'Loading...' : error ? 'Error' : <DefaultTypedMessageRenderer {...props} message={value!} />,
loading ? (
<DefaultTypedMessageTextRenderer {...props} message={makeTypedMessageText('Loading...')} />
) : error ? (
<DefaultTypedMessageTextRenderer {...props} message={makeTypedMessageText('Error!')} />
) : (
<DefaultTypedMessageRenderer {...props} message={value!} />
),
)
})
registerTypedMessageRenderer('suspended', {
Expand Down
7 changes: 2 additions & 5 deletions src/components/shared-settings/useSettingsUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '@material-ui/core'
import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos'
import { useStylesExtends } from '../custom-ui-helper'
import { getEnumAsArray } from '../../utils/enum'

const useStyles = makeStyles((theme) =>
createStyles({
Expand Down Expand Up @@ -153,16 +154,12 @@ export function useSettingsUI(ref: ValueRef<boolean>) {
function useEnumSettings<Q extends object>(
...[ref, enumObject, getText, selectProps]: useEnumSettingsParams<Q>
): HookedUI<Q[keyof Q]> {
const enum_ = Object.keys(enumObject)
// Leave only key of enum
.filter((x) => Number.isNaN(parseInt(x)))
.map((key) => ({ key, value: enumObject[key as keyof Q] }))
const enum_ = getEnumAsArray(enumObject)
const change = (value: any) => {
if (!Number.isNaN(parseInt(value))) {
value = parseInt(value)
}
if (!enum_.some((x) => x.value === value)) {
console.log(value)
throw new Error('Invalid state')
}
ref.value = value
Expand Down
2 changes: 2 additions & 0 deletions src/extension/background-script/PluginService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as RedPacket from '../../plugins/RedPacket/state-machine'
import * as Wallet from '../../plugins/Wallet/wallet'
import * as Gitcoin from '../../plugins/Gitcoin/service'
import * as FileService from '../../plugins/FileService/service'
import * as Trader from '../../plugins/Trader/services'
import type { ERC20TokenRecord, ManagedWalletRecord, ExoticWalletRecord } from '../../plugins/Wallet/database/types'
import { EthereumNetwork } from '../../plugins/Wallet/database/types'
import { getWalletProvider, web3 } from '../../plugins/Wallet/web3'
Expand All @@ -11,6 +12,7 @@ const Plugins = {
'maskbook.red_packet': RedPacket,
'maskbook.wallet': Wallet,
'maskbook.fileservice': FileService,
'maskbook.trader': Trader,
'co.gitcoin': Gitcoin,
} as const
type Plugins = typeof Plugins
Expand Down
Loading

0 comments on commit d2179ec

Please sign in to comment.