Compare commits

..
Author SHA1 Message Date
John Huang 68ae1788c8 PR 2024-02-20 15:33:26 -08:00
John Huang 54d5b9fc1d MOB-193 PR 2024-02-20 15:30:40 -08:00
John Huang 614f92def8 Merge branch 'main' into feature/MOB-193-install-app-dlg
# Conflicts:
#	package.json
#	pnpm-lock.yaml
2024-02-20 15:18:37 -08:00
John Huang 14c155ce31 Rename URL variable 2024-02-15 10:11:51 -08:00
John Huang 8f92972c47 Comments 2024-02-15 10:03:19 -08:00
John Huang 2b53b6b8d7 Update localization 2024-02-15 09:57:05 -08:00
John Huang 0391cefebe Merge branch 'main' into feature/MOB-193-install-app-dlg 2024-02-15 09:42:24 -08:00
John Huang 5d7863348a Clean up 2024-02-15 09:42:04 -08:00
John Huang 027c1ef8ec getOS and get meta from header 2024-02-14 15:34:16 -08:00
9 changed files with 190 additions and 41 deletions
+1 -1
View File
@@ -40,7 +40,7 @@
"@cosmjs/proto-signing": "^0.32.1",
"@cosmjs/stargate": "^0.32.1",
"@cosmjs/tendermint-rpc": "^0.32.1",
"@dydxprotocol/v4-abacus": "^1.4.9",
"@dydxprotocol/v4-abacus": "^1.4.6",
"@dydxprotocol/v4-client-js": "^1.0.20",
"@dydxprotocol/v4-localization": "^1.1.34",
"@ethersproject/providers": "^5.7.2",
+4 -4
View File
@@ -30,8 +30,8 @@ dependencies:
specifier: ^0.32.1
version: 0.32.2
'@dydxprotocol/v4-abacus':
specifier: ^1.4.9
version: 1.4.9
specifier: ^1.4.6
version: 1.4.6
'@dydxprotocol/v4-client-js':
specifier: ^1.0.20
version: 1.0.20
@@ -1290,8 +1290,8 @@ packages:
resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==}
dev: false
/@dydxprotocol/v4-abacus@1.4.9:
resolution: {integrity: sha512-OnrD5Db7AGSxyQE+gWRmkBuXbkZMfSEDB+v2s+ft0zgd4D5j/WTZkk1RG0fn8ISV4AffoN5T24jBWxLfqVlmZw==}
/@dydxprotocol/v4-abacus@1.4.6:
resolution: {integrity: sha512-qYq+4TizcMMxYVXckn0LCucWBe5N9ZNtD1XnowCAuBUUifHSgMGvao5OeZIKMgNM/udSKOXLss4zLy6dH/G2SA==}
dev: false
/@dydxprotocol/v4-client-js@1.0.20:
+1
View File
@@ -11,6 +11,7 @@ export enum DialogTypes {
ExternalNavKeplr = 'ExternalNavKeplr',
MnemonicExport = 'MnemonicExport',
MobileSignIn = 'MobileSignIn',
MobileDownload = 'MobileDownload',
Onboarding = 'Onboarding',
OrderDetails = 'OrderDetails',
Preferences = 'Preferences',
+26 -30
View File
@@ -9,16 +9,14 @@ import type { ChartLine, TvWidget } from '@/constants/tvchart';
import { useStringGetter } from '@/hooks';
import {
getCurrentMarketOrders,
getCurrentMarketPositionData,
getIsAccountConnected,
} from '@/state/accountSelectors';
import { getCurrentMarketOrders, getCurrentMarketPositionData } from '@/state/accountSelectors';
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
import { MustBigNumber } from '@/lib/numbers';
import { getChartLineColors } from '@/lib/tradingView/utils';
let chartLines: Record<string, ChartLine> = {};
/**
* @description Hook to handle drawing chart lines
*/
@@ -27,47 +25,43 @@ export const useChartLines = ({
tvWidget,
displayButton,
isChartReady,
chartLinesRef,
}: {
tvWidget: TvWidget | null;
displayButton: HTMLElement | null;
isChartReady: boolean;
chartLinesRef: React.MutableRefObject<Record<string, ChartLine>>;
isChartReady?: boolean;
}) => {
const [showOrderLines, setShowOrderLines] = useState(true);
const [showOrderLines, setShowOrderLines] = useState(false);
const stringGetter = useStringGetter();
const appTheme = useSelector(getAppTheme);
const appColorMode = useSelector(getAppColorMode);
const isAccountConnected = useSelector(getIsAccountConnected);
const currentMarketPositionData = useSelector(getCurrentMarketPositionData, shallowEqual);
const currentMarketOrders: SubaccountOrder[] = useSelector(getCurrentMarketOrders, shallowEqual);
useEffect(() => {
if (isChartReady && displayButton) {
displayButton.onclick = () => setShowOrderLines(!showOrderLines);
displayButton.onclick = () => {
const newShowOrderLinesState = !showOrderLines;
if (newShowOrderLinesState) {
displayButton?.classList?.add('order-lines-active');
} else {
displayButton?.classList?.remove('order-lines-active');
}
setShowOrderLines(newShowOrderLinesState);
};
}
}, [isChartReady, showOrderLines]);
useEffect(() => {
if (!isAccountConnected) {
deleteChartLines();
}
}, [isAccountConnected]);
useEffect(() => {
if (isChartReady && tvWidget) {
if (tvWidget && isChartReady) {
tvWidget.onChartReady(() => {
tvWidget.chart().dataReady(() => {
if (showOrderLines) {
displayButton?.classList?.add('order-lines-active');
drawOrderLines();
drawPositionLine();
} else {
displayButton?.classList?.remove('order-lines-active');
deleteChartLines();
}
});
@@ -84,13 +78,13 @@ export const useChartLines = ({
const key = currentMarketPositionData.id;
const price = MustBigNumber(entryPrice).toNumber();
const maybePositionLine = chartLinesRef.current[key]?.line;
const maybePositionLine = chartLines[key]?.line;
const shouldShow = size && size !== 0;
if (!shouldShow) {
if (maybePositionLine) {
maybePositionLine.remove();
delete chartLinesRef.current[key];
delete chartLines[key];
return;
}
} else {
@@ -112,9 +106,9 @@ export const useChartLines = ({
.setQuantity(quantity);
if (positionLine) {
const chartLine: ChartLine = { line: positionLine, chartLineType: 'position' };
const chartLine = { line: positionLine, chartLineType: 'position' };
setLineColors({ chartLine: chartLine });
chartLinesRef.current[key] = chartLine;
chartLines[key] = chartLine;
}
}
}
@@ -149,12 +143,12 @@ export const useChartLines = ({
!cancelReason &&
(status === AbacusOrderStatus.open || status === AbacusOrderStatus.untriggered);
const maybeOrderLine = chartLinesRef.current[key]?.line;
const maybeOrderLine = chartLines[key]?.line;
if (!shouldShow) {
if (maybeOrderLine) {
maybeOrderLine.remove();
delete chartLinesRef.current[key];
delete chartLines[key];
return;
}
} else {
@@ -176,7 +170,7 @@ export const useChartLines = ({
chartLineType: ORDER_SIDES[side.name],
};
setLineColors({ chartLine: chartLine });
chartLinesRef.current[key] = chartLine;
chartLines[key] = chartLine;
}
}
}
@@ -185,10 +179,10 @@ export const useChartLines = ({
};
const deleteChartLines = () => {
Object.values(chartLinesRef.current).forEach(({ line }) => {
Object.values(chartLines).forEach(({ line }) => {
line.remove();
});
chartLinesRef.current = {};
chartLines = {};
};
const setLineColors = ({ chartLine }: { chartLine: ChartLine }) => {
@@ -210,4 +204,6 @@ export const useChartLines = ({
maybeQuantityColor &&
line.setLineColor(maybeQuantityColor).setQuantityBackgroundColor(maybeQuantityColor);
};
return { chartLines };
};
@@ -38,7 +38,7 @@ export const useChartMarketAndResolution = ({
* @description Hook to handle changing markets - intentionally should avoid triggering on change of resolutions.
*/
useEffect(() => {
if (isWidgetReady && currentMarketId !== tvWidget?.activeChart().symbol()) {
if (currentMarketId && isWidgetReady) {
const resolution = savedResolution || selectedResolution;
tvWidget?.setSymbol(currentMarketId, resolution as ResolutionString, () => {});
}
+2
View File
@@ -31,6 +31,7 @@ import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDial
import { NewMarketMessageDetailsDialog } from '@/views/dialogs/NewMarketMessageDetailsDialog';
import { NewMarketAgreementDialog } from '@/views/dialogs/NewMarketAgreementDialog';
import { ExternalNavStrideDialog } from '@/views/dialogs/ExternalNavStrideDialog';
import { MobileDownloadDialog } from '@/views/dialogs/MobileDownloadDialog';
export const DialogManager = () => {
const dispatch = useDispatch();
@@ -63,6 +64,7 @@ export const DialogManager = () => {
[DialogTypes.ExternalNavStride]: <ExternalNavStrideDialog {...modalProps} />,
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
[DialogTypes.MobileDownload]: <MobileDownloadDialog {...modalProps} />,
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
[DialogTypes.Preferences]: <PreferencesDialog {...modalProps} />,
+2 -5
View File
@@ -4,7 +4,7 @@ import styled, { type AnyStyledComponent, css } from 'styled-components';
import type { ResolutionString } from 'public/tradingview/charting_library';
import type { ChartLine, TvWidget } from '@/constants/tvchart';
import type { TvWidget } from '@/constants/tvchart';
import {
useChartLines,
@@ -27,16 +27,13 @@ export const TvChart = () => {
const displayButtonRef = useRef<HTMLElement | null>(null);
const displayButton = displayButtonRef.current;
const chartLinesRef = useRef<Record<string, ChartLine>>({});
const chartLines = chartLinesRef.current;
const { savedResolution } = useTradingView({ tvWidgetRef, displayButtonRef, setIsChartReady });
useChartMarketAndResolution({
tvWidget,
isWidgetReady,
savedResolution: savedResolution as ResolutionString | undefined,
});
useChartLines({ tvWidget, displayButton, isChartReady, chartLinesRef });
const { chartLines } = useChartLines({ tvWidget, displayButton, isChartReady });
useTradingViewTheme({ tvWidget, isWidgetReady, chartLines });
return (
+140
View File
@@ -0,0 +1,140 @@
import styled, { AnyStyledComponent, css } from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { QrCode } from '@/components/QrCode';
import { useStringGetter } from '@/hooks';
import { STRING_KEYS } from '@/constants/localization';
type ElementProps = {
setIsOpen: (open: boolean) => void;
};
/*
When/if deployer deploys the web app with smartbanner, "smartbanner:button-url-apple" and/or
"smartbanner:button-url-google" <meta> are set.
This implementation assumes "smartbanner:button-url-apple" and "smartbanner:button-url-google"
are set to the same value with onelink or other redirect URL.
Since there is no way for the desktop web app to know what mobile device the user is using,
we should give a onelink URL which redirects to either iOS or Android app store depending on
the mobile device used to scan the link.
*/
// for testing only
// export const mobileAppUrl = "http://example.com";
let mobileAppUrl: string | undefined | null = undefined;
export const getMobileAppUrl = () => {
if (!mobileAppUrl) {
mobileAppUrl =
// for testing to verify <meta> is retrieved by name, QR code should show "@dYdX" as value
// document.querySelector('meta[name="twitter:creator"]')?.getAttribute('content') ??
document.querySelector('meta[name="smartbanner:button-url-apple"]')?.getAttribute('content') ??
document.querySelector('meta[name="smartbanner:button-url-google"]')?.getAttribute('content');
}
return mobileAppUrl;
}
const MobileQrCode = ({
url,
}: {
url: string;
}) => {
return (
<Styled.QrCodeContainer isShowing={true}>
<QrCode hasLogo size={432} value={url} />
</Styled.QrCodeContainer>
);
};
/*
MobileDownloadDialog should only been shown on desktop when mobileAppUrl has value. That's controlled by AccountMenu.tsx.
*/
export const MobileDownloadDialog = ({ setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
const content = (
<MobileQrCode url={mobileAppUrl!} />
);
return (
<Dialog isOpen setIsOpen={setIsOpen} title={
stringGetter({ key: STRING_KEYS.DOWNLOAD_MOBILE_APP })
}>
<Styled.Content>{content}</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
strong {
font-weight: 900;
color: var(--color-text-2);
}
footer {
${layoutMixins.row}
justify-content: space-between;
svg {
width: auto;
}
}
`;
Styled.WaitingSpan = styled.span`
strong {
color: var(--color-warning);
}
`;
Styled.QrCodeContainer = styled.figure<{ isShowing: boolean }>`
${layoutMixins.stack}
overflow: hidden;
border-radius: 0.75em;
cursor: pointer;
transition: 0.2s;
&:hover {
filter: brightness(var(--hover-filter-base));
}
> * {
position: relative;
transition: 0.16s;
}
> :first-child {
pointer-events: none;
${({ isShowing }) =>
!isShowing &&
css`
filter: blur(1rem) brightness(1.4);
will-change: filter;
`}
}
> span {
place-self: center;
font-size: 1.4em;
color: var(--color-text-2);
${({ isShowing }) =>
isShowing &&
css`
opacity: 0;
`}
}
`;
+13
View File
@@ -40,6 +40,7 @@ import { getAppTheme } from '@/state/configsSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { MustBigNumber } from '@/lib/numbers';
import { getMobileAppUrl } from '../dialogs/MobileDownloadDialog';
export const AccountMenu = () => {
const stringGetter = useStringGetter();
@@ -189,6 +190,18 @@ export const AccountMenu = () => {
label: stringGetter({ key: STRING_KEYS.DISPLAY_SETTINGS }),
onSelect: () => dispatch(openDialog({ type: DialogTypes.DisplaySettings })),
},
...(getMobileAppUrl()
? [
{
value: 'MobileDownload',
icon: <Icon iconName={IconName.Qr} />,
label: stringGetter({ key: STRING_KEYS.DOWNLOAD_MOBILE_APP }),
onSelect: () => {
dispatch(openDialog({ type: DialogTypes.MobileDownload }));
},
},
]
: []),
...(onboardingState === OnboardingState.AccountConnected && hdKey
? [
{