Nav re-imagined (#3422)
* Playthrough with top navigation * Sycky, but navigation works... * Adjust * grouping * It works, but is not great * Cleanups * Cleanups * hover color * Highlights * Highlights, part 2 * Background to body * Small adjustments (size, items) * Localize Modal/route switching * Remove light/full mode * Flatter single-entry menus * Adjustments for single-enrties (e.g. Kulupu) * Display NodeInfo * Misc * Content & Tabs sizing * Tab counters as bubbles * Alignment * Tabs styling * Adjust font color to defaults * Small tabs menu color adjustments * ... the previous, again, adjusted * Display Wiki/Github links * ar motions * Adjust Endpoints location * Ensure content grows to available height * Adjust tabs * Menu background adjustment * Adjust popup colors * Adjust highlights * Sligh breathing room * Adaptive header background * 2/3 * Adaptive is ok... for now. * Back to black... erm, gray * Lighten * Touchups. Stop playin, it is ok... * Sides readability * Cleanups. * Adjust. * Overall adjustments (theme). Fix Tag yellow. * Ok. Done. * Ship it.
This commit is contained in:
parent
1fb364b741
commit
33892a2e31
@ -12,6 +12,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
display: {
|
||||
needsApi: []
|
||||
},
|
||||
group: 'accounts',
|
||||
icon: 'users',
|
||||
name: 'accounts',
|
||||
text: t<string>('nav.accounts', 'Accounts', { ns: 'apps-routing' }),
|
||||
|
||||
@ -15,6 +15,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
'query.claims.claims'
|
||||
]
|
||||
},
|
||||
group: 'accounts',
|
||||
icon: 'star',
|
||||
name: 'claims',
|
||||
text: t<string>('nav.claims', 'Claim Tokens', { ns: 'apps-routing' }),
|
||||
|
||||
@ -15,6 +15,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
'tx.contracts.call'
|
||||
]
|
||||
},
|
||||
group: 'developer',
|
||||
icon: 'compress',
|
||||
name: 'contracts',
|
||||
text: t<string>('nav.contracts', 'Contracts', { ns: 'apps-routing' })
|
||||
|
||||
@ -17,6 +17,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
]
|
||||
]
|
||||
},
|
||||
group: 'governance',
|
||||
icon: 'building',
|
||||
name: 'council',
|
||||
text: t<string>('nav.council', 'Council', { ns: 'apps-routing' }),
|
||||
|
||||
@ -12,6 +12,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
display: {
|
||||
isHidden: true
|
||||
},
|
||||
group: 'network',
|
||||
icon: 'th',
|
||||
name: 'dashboard',
|
||||
text: t<string>('nav.dashboard', 'Dashboard', { ns: 'apps-routing' })
|
||||
|
||||
@ -14,6 +14,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
'tx.democracy.notePreimage'
|
||||
]
|
||||
},
|
||||
group: 'governance',
|
||||
icon: 'calendar-check',
|
||||
name: 'democracy',
|
||||
text: t<string>('nav.democracy', 'Democracy', { ns: 'apps-routing' }),
|
||||
|
||||
@ -12,6 +12,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
display: {
|
||||
needsApi: []
|
||||
},
|
||||
group: 'network',
|
||||
icon: 'braille',
|
||||
name: 'explorer',
|
||||
text: t<string>('nav.explorer', 'Explorer', { ns: 'apps-routing' })
|
||||
|
||||
@ -13,6 +13,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
needsAccounts: true,
|
||||
needsApi: []
|
||||
},
|
||||
group: 'developer',
|
||||
icon: 'sync',
|
||||
name: 'extrinsics',
|
||||
text: t<string>('nav.extrinsics', 'Extrinsics', { ns: 'apps-routing' })
|
||||
|
||||
@ -16,6 +16,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
'tx.genericAsset.transfer'
|
||||
]
|
||||
},
|
||||
group: 'network',
|
||||
icon: 'cubes',
|
||||
name: 'generic-asset',
|
||||
text: t<string>('nav.generic-asset', 'Generic asset', { ns: 'apps-routing' })
|
||||
|
||||
@ -4,10 +4,6 @@
|
||||
|
||||
import { Routes } from './types';
|
||||
|
||||
import appSettings from '@polkadot/ui-settings';
|
||||
|
||||
// When adding here, also ensure to add to Dummy.tsx
|
||||
|
||||
import accounts from './accounts';
|
||||
import claims from './claims';
|
||||
import contracts from './contracts';
|
||||
@ -31,47 +27,27 @@ import transfer from './transfer';
|
||||
import treasury from './treasury';
|
||||
|
||||
export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Routes {
|
||||
return appSettings.uiMode === 'light'
|
||||
? [
|
||||
// dashboard,
|
||||
explorer(t),
|
||||
accounts(t),
|
||||
claims(t),
|
||||
poll(t),
|
||||
transfer(t),
|
||||
genericAsset(t),
|
||||
null,
|
||||
staking(t),
|
||||
democracy(t),
|
||||
council(t),
|
||||
// TODO Not sure about the inclusion of treasury, parachains & society here
|
||||
null,
|
||||
settings(t)
|
||||
]
|
||||
: [
|
||||
// dashboard(t),
|
||||
explorer(t),
|
||||
accounts(t),
|
||||
claims(t),
|
||||
poll(t),
|
||||
transfer(t),
|
||||
genericAsset(t),
|
||||
null,
|
||||
staking(t),
|
||||
democracy(t),
|
||||
council(t),
|
||||
treasury(t),
|
||||
techcomm(t),
|
||||
parachains(t),
|
||||
society(t),
|
||||
null,
|
||||
contracts(t),
|
||||
storage(t),
|
||||
extrinsics(t),
|
||||
sudo(t),
|
||||
null,
|
||||
settings(t),
|
||||
toolbox(t),
|
||||
js(t)
|
||||
];
|
||||
return [
|
||||
// dashboard(t),
|
||||
accounts(t),
|
||||
explorer(t),
|
||||
claims(t),
|
||||
poll(t),
|
||||
transfer(t),
|
||||
genericAsset(t),
|
||||
staking(t),
|
||||
democracy(t),
|
||||
council(t),
|
||||
treasury(t),
|
||||
techcomm(t),
|
||||
parachains(t),
|
||||
society(t),
|
||||
contracts(t),
|
||||
storage(t),
|
||||
extrinsics(t),
|
||||
sudo(t),
|
||||
settings(t),
|
||||
toolbox(t),
|
||||
js(t)
|
||||
];
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
display: {
|
||||
needsApi: []
|
||||
},
|
||||
group: 'developer',
|
||||
icon: 'code',
|
||||
name: 'js',
|
||||
text: t<string>('nav.js', 'Javascript', { ns: 'apps-routing' })
|
||||
|
||||
@ -14,6 +14,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
['query.parachains.code', 'query.parachainUpgrade.didUpdateVFPs']
|
||||
]
|
||||
},
|
||||
group: 'network',
|
||||
icon: 'link',
|
||||
name: 'parachains',
|
||||
text: t<string>('nav.parachains', 'Parachains', { ns: 'apps-routing' })
|
||||
|
||||
@ -15,6 +15,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
'tx.poll.vote'
|
||||
]
|
||||
},
|
||||
group: 'governance',
|
||||
icon: 'podcast',
|
||||
name: 'poll',
|
||||
text: t<string>('nav.poll', 'Token poll', { ns: 'apps-routing' })
|
||||
|
||||
@ -10,6 +10,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
return {
|
||||
Component,
|
||||
display: {},
|
||||
group: 'settings',
|
||||
icon: 'cogs',
|
||||
name: 'settings',
|
||||
text: t<string>('nav.settings', 'Settings', { ns: 'apps-routing' }),
|
||||
|
||||
@ -15,6 +15,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
'query.society.pot'
|
||||
]
|
||||
},
|
||||
group: 'network',
|
||||
icon: 'hand-spock',
|
||||
name: 'society',
|
||||
text: t<string>('nav.society', 'Society', { ns: 'apps-routing' }),
|
||||
|
||||
@ -14,6 +14,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
['tx.staking.bond']
|
||||
]
|
||||
},
|
||||
group: 'network',
|
||||
icon: 'certificate',
|
||||
name: 'staking',
|
||||
text: t<string>('nav.staking', 'Staking', { ns: 'apps-routing' })
|
||||
|
||||
@ -12,6 +12,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
display: {
|
||||
needsApi: []
|
||||
},
|
||||
group: 'developer',
|
||||
icon: 'database',
|
||||
name: 'chainstate',
|
||||
text: t<string>('nav.storage', 'Chain state', { ns: 'apps-routing' })
|
||||
|
||||
@ -16,6 +16,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
],
|
||||
needsSudo: true
|
||||
},
|
||||
group: 'developer',
|
||||
icon: 'unlock',
|
||||
name: 'sudo',
|
||||
text: t<string>('nav.sudo', 'Sudo', { ns: 'apps-routing' })
|
||||
|
||||
@ -15,6 +15,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
'query.technicalCommittee.members'
|
||||
]
|
||||
},
|
||||
group: 'governance',
|
||||
icon: 'microchip',
|
||||
name: 'techcomm',
|
||||
text: t<string>('nav.tech-comm', 'Tech. comm.', { ns: 'apps-routing' }),
|
||||
|
||||
@ -12,6 +12,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
display: {
|
||||
needsApi: []
|
||||
},
|
||||
group: 'developer',
|
||||
icon: 'tools',
|
||||
name: 'toolbox',
|
||||
text: t<string>('nav.toolbox', 'Toolbox', { ns: 'apps-routing' })
|
||||
|
||||
@ -17,6 +17,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
'tx.balances.transfer'
|
||||
]
|
||||
},
|
||||
group: 'accounts',
|
||||
icon: 'paper-plane',
|
||||
name: 'transfer',
|
||||
text: t<string>('nav.transfer', 'Transfer', { ns: 'apps-routing' })
|
||||
|
||||
@ -14,6 +14,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
|
||||
'tx.treasury.proposeSpend'
|
||||
]
|
||||
},
|
||||
group: 'governance',
|
||||
icon: 'gem',
|
||||
name: 'treasury',
|
||||
text: t<string>('nav.treasury', 'Treasury', { ns: 'apps-routing' }),
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
import { IconName } from '@fortawesome/fontawesome-svg-core';
|
||||
import { AppProps, BareProps } from '@polkadot/react-components/types';
|
||||
|
||||
export type RouteGroup = 'accounts' | 'developer' | 'governance' | 'network' | 'settings';
|
||||
|
||||
export interface RouteProps extends AppProps, BareProps {
|
||||
location: any;
|
||||
}
|
||||
@ -19,6 +21,7 @@ export interface Route {
|
||||
needsApi?: (string | string[])[];
|
||||
needsSudo?: boolean;
|
||||
};
|
||||
group: RouteGroup;
|
||||
icon: IconName;
|
||||
isIgnored?: boolean;
|
||||
name: string;
|
||||
@ -26,4 +29,4 @@ export interface Route {
|
||||
useCounter?: () => number | string | null;
|
||||
}
|
||||
|
||||
export type Routes = (Route | null)[];
|
||||
export type Routes = Route[];
|
||||
|
||||
@ -248,7 +248,7 @@
|
||||
"Mnemonic": "ذاكري",
|
||||
"Most profitable": "الأكثر ربحية",
|
||||
"Most recent head data": "أحدث بيانات الرأس",
|
||||
"Motions ({{count}})": "({{الحركات ({{احسب",
|
||||
"Motions": "الحركات",
|
||||
"Multiple council proposals could exist, both approval and rejection. Apply your vote to the correct council proposal (also available on council motions page)": "(قد تنشأ مقترحات متعددة من جانب المجلس، سواء بالموافقة أو الرفض. طبق تصويتك على اقتراح المجلس الصحيح (متاح أيضا في صفحة طلبات المجلس",
|
||||
"Multisig": "متعدد الدخول",
|
||||
"Multisig approval with hash (non-final approval)": "(اعتماد متعدد التوقيعات مع تجزئة (موافقة غير نهائية",
|
||||
@ -1277,4 +1277,4 @@
|
||||
"{{threshold}}, not passing": "سقف}}، لا يتم تمريره}}",
|
||||
"{{threshold}}, passing": "سقف}}، يتم تمريره}}",
|
||||
"{{value}}x voting balance, locked for {{lock}}x enactment ({{period}} days)": "({{قفل}} (تاريخ إصدار {{أيام}}x رصيد التصويت، مقفل لـ x {{قيمة }}"
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
"Council overview": "Council overview",
|
||||
"Current prime member, default voting": "Current prime member, default voting",
|
||||
"In calculating the election outcome, this prioritized vote ordering will be used to determine the final score for the candidates.": "In calculating the election outcome, this prioritized vote ordering will be used to determine the final score for the candidates.",
|
||||
"Motions ({{count}})": "Motions ({{count}})",
|
||||
"Motions": "Motions",
|
||||
"Nay {{counter}}": "Nay {{counter}}",
|
||||
"No candidates found": "No candidates found",
|
||||
"No council motions": "No council motions",
|
||||
@ -77,4 +77,4 @@
|
||||
"voting account": "voting account",
|
||||
"voting end": "voting end",
|
||||
"voting round": "voting round"
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,7 +221,7 @@
|
||||
"Metadata {{count}}": "",
|
||||
"Mnemonic": "",
|
||||
"Most recent head data": "",
|
||||
"Motions ({{count}})": "",
|
||||
"Motions": "",
|
||||
"Multiple council proposals could exist, both approval and rejection. Apply your vote to the correct council proposal (also available on council motions page)": "",
|
||||
"Multisig": "",
|
||||
"Multisig approval with hash (non-final approval)": "",
|
||||
@ -1152,4 +1152,4 @@
|
||||
"{{threshold}}, not passing": "",
|
||||
"{{threshold}}, passing": "",
|
||||
"{{value}}x voting balance, locked for {{lock}}x enactment ({{period}} days)": ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +205,7 @@
|
||||
"Metadata {{count}}": "Metadatos {{count}}",
|
||||
"Mnemonic": "Mnemonic",
|
||||
"Most recent head data": "Datos más recientes de la cabecera",
|
||||
"Motions ({{count}})": "Mociones ({{count}})",
|
||||
"Motions": "Mociones",
|
||||
"Multiple council proposals could exist, both approval and rejection. Apply your vote to the correct council proposal (also available on council motions page)": "Podrían existir múltiples propuestas del consejo, tanto de aprobación como de rechazo. Aplique su voto a la propuesta correcta del consejo (también disponible en la página de mociones del consejo)",
|
||||
"Multisig": "Multisig",
|
||||
"Multisig approval with hash (non-final approval)": "Aprobación de Multisig con hash (aprobación no definitiva)",
|
||||
@ -1113,4 +1113,4 @@
|
||||
"{{threshold}}, not passing": "{{threshold}}, no pasará",
|
||||
"{{threshold}}, passing": "{{threshold}}, de aprobación",
|
||||
"{{value}}x voting balance, locked for {{lock}}x enactment ({{period}} days)": "{{value}}x saldo de votos, bloqueado por {{lock}}x sentencia ({{period}} días."
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@
|
||||
"Metadata {{count}}": "メタデータ {{count}}",
|
||||
"Mnemonic": "ニーモニック",
|
||||
"Most recent head data": "最新のヘッドデータ",
|
||||
"Motions ({{count}})": "モーション({{count}})",
|
||||
"Motions": "モーション",
|
||||
"Multisig": "マルチシグ",
|
||||
"Multisig approvals": "マルチシグの承認",
|
||||
"Mutates contract state": "コントラクトの状態を変える",
|
||||
@ -562,4 +562,4 @@
|
||||
"{{relayDispatchQueueSize}} dispatch messages pending": "{{relayDispatchQueueSize}} ディスパッチメッセージペンディング",
|
||||
"{{step}}. Sign with your ETH address": "{{step}}. ETHアドレスでサインしてください",
|
||||
"{{s}} s": "{{s}} 秒"
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +205,7 @@
|
||||
"Metadata {{count}}": "Metadados {{count}}",
|
||||
"Mnemonic": "Mnemônico",
|
||||
"Most recent head data": "Dados de cabeçalho mais recentes",
|
||||
"Motions ({{count}})": "Moções ({{count}})",
|
||||
"Motions": "Moções",
|
||||
"Multiple council proposals could exist, both approval and rejection. Apply your vote to the correct council proposal (also available on council motions page)": "Múltiplas propostas de conselho podem existir, tanto aprovação quanto rejeição. Aplique seu voto à proposta correta do conselho (também disponível na página de moções do conselho)",
|
||||
"Multisig": "Multi-assinatura",
|
||||
"Multisig approval with hash (non-final approval)": "Aprovação multi-assinatura com hash (aprovação não final)",
|
||||
@ -1120,4 +1120,4 @@
|
||||
"{{threshold}}, not passing": "{{threshold}}, não passando",
|
||||
"{{threshold}}, passing": "{{threshold}}, passando",
|
||||
"{{value}}x voting balance, locked for {{lock}}x enactment ({{period}} days)": "{{value}} x saldo de votos, bloqueado por {{lock}} x promulgação ({{period}} dias)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@
|
||||
"Mnemonic": "Мнемонический",
|
||||
"More": "Ещё",
|
||||
"Most recent head data": "Последние данные",
|
||||
"Motions ({{count}})": "Ходатайства ({{count}})",
|
||||
"Motions": "Ходатайства",
|
||||
"Multiple council proposals could exist, both approval and rejection. Apply your vote to the correct council proposal (also available on council motions page)": "Нескольо предложний могут существовать, как одобренных так и отклоненных. Голосуйте в правильном предложении (это можно сделать и на странице Решения Совета)",
|
||||
"Multisig": "Мультиподпись",
|
||||
"Multisig approval with hash (non-final approval)": "Разрешить с хэшом мультиподписи (не финальное разрешение)",
|
||||
@ -1131,4 +1131,4 @@
|
||||
"{{threshold}}, not passing": "{{threshold}}, не проходит",
|
||||
"{{threshold}}, passing": "{{threshold}}, проходит",
|
||||
"{{value}}x voting balance, locked for {{lock}}x enactment ({{period}} days)": "{{value}}x баланс голосования, заблокировано на {{lock}}x ({{period}} дней)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@
|
||||
"Manage your connection to Ledger S": "管理你的连接到 Ledger S",
|
||||
"Messages": "消息",
|
||||
"Mnemonic": "助记词",
|
||||
"Motions ({{count}})": "Motions ({{count}})",
|
||||
"Motions": "Motions",
|
||||
"Multisig": "多重签名",
|
||||
"Multisig approvals": "多重签名批准",
|
||||
"My accounts": "我的账户",
|
||||
@ -561,4 +561,4 @@
|
||||
"yes": "是",
|
||||
"your current password": "你当前的密码",
|
||||
"your new password": "你的新密码"
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,8 +4,7 @@
|
||||
|
||||
import { BareProps as Props } from '@polkadot/react-components/types';
|
||||
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import store from 'store';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import AccountSidebar from '@polkadot/app-accounts/Sidebar';
|
||||
import { getSystemChainColor } from '@polkadot/apps-config/ui';
|
||||
@ -15,86 +14,26 @@ import { useApi } from '@polkadot/react-hooks';
|
||||
import Signer from '@polkadot/react-signer';
|
||||
|
||||
import ConnectingOverlay from './overlays/Connecting';
|
||||
import { SideBarTransition, SIDEBAR_MENU_THRESHOLD } from './constants';
|
||||
import Content from './Content';
|
||||
import SideBar from './SideBar';
|
||||
import Menu from './Menu';
|
||||
import WarmUp from './WarmUp';
|
||||
import { WindowDimensionsCtx } from './WindowDimensions';
|
||||
|
||||
interface SidebarState {
|
||||
isCollapsed: boolean;
|
||||
isMenu: boolean;
|
||||
isMenuOpen: boolean;
|
||||
transition: SideBarTransition;
|
||||
}
|
||||
|
||||
export const PORTAL_ID = 'portals';
|
||||
|
||||
function saveSidebar (sidebar: SidebarState): SidebarState {
|
||||
return store.set('sidebar', sidebar) as SidebarState;
|
||||
}
|
||||
|
||||
function Apps ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
const windowDimensions = useContext(WindowDimensionsCtx);
|
||||
const { systemChain, systemName } = useApi();
|
||||
const [sidebar, setSidebar] = useState<SidebarState>({
|
||||
isCollapsed: false,
|
||||
isMenuOpen: false,
|
||||
transition: SideBarTransition.COLLAPSED,
|
||||
...store.get('sidebar', {}),
|
||||
isMenu: window.innerWidth < SIDEBAR_MENU_THRESHOLD
|
||||
});
|
||||
|
||||
const uiHighlight = useMemo(
|
||||
() => getSystemChainColor(systemChain, systemName),
|
||||
[systemChain, systemName]
|
||||
);
|
||||
|
||||
const _collapse = useCallback(
|
||||
() => setSidebar((sidebar: SidebarState) => saveSidebar({ ...sidebar, isCollapsed: !sidebar.isCollapsed })),
|
||||
[]
|
||||
);
|
||||
const _toggleMenu = useCallback(
|
||||
() => setSidebar((sidebar: SidebarState) => saveSidebar({ ...sidebar, isCollapsed: false, isMenuOpen: true })),
|
||||
[]
|
||||
);
|
||||
const _handleResize = useCallback(
|
||||
(): void => {
|
||||
const transition = window.innerWidth < SIDEBAR_MENU_THRESHOLD
|
||||
? SideBarTransition.MINIMISED_AND_EXPANDED
|
||||
: SideBarTransition.EXPANDED_AND_MAXIMISED;
|
||||
|
||||
setSidebar((sidebar: SidebarState) => saveSidebar({
|
||||
...sidebar,
|
||||
isMenu: transition === SideBarTransition.MINIMISED_AND_EXPANDED,
|
||||
isMenuOpen: false,
|
||||
transition
|
||||
}));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
_handleResize();
|
||||
}, [_handleResize, windowDimensions]);
|
||||
|
||||
const { isCollapsed, isMenu, isMenuOpen } = sidebar;
|
||||
|
||||
return (
|
||||
<>
|
||||
<GlobalStyle uiHighlight={defaultColor || uiHighlight} />
|
||||
<div className={`apps--Wrapper ${isCollapsed ? 'collapsed' : 'expanded'}${isMenu ? ' fixed' : ''}${isMenuOpen ? ' menu-open' : ''} theme--default ${className}`}>
|
||||
<div className={`apps--Wrapper theme--default ${className}`}>
|
||||
<Menu />
|
||||
<AccountSidebar>
|
||||
<div
|
||||
className={`apps--Menu-bg ${isMenuOpen ? 'open' : 'closed'}`}
|
||||
onClick={_handleResize}
|
||||
/>
|
||||
<SideBar
|
||||
collapse={_collapse}
|
||||
handleResize={_handleResize}
|
||||
isCollapsed={isCollapsed}
|
||||
isMenuOpen={isMenuOpen}
|
||||
toggleMenu={_toggleMenu}
|
||||
/>
|
||||
<Signer>
|
||||
<Content />
|
||||
</Signer>
|
||||
@ -110,116 +49,6 @@ function Apps ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
export default React.memo(styled(Apps)`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100vh;
|
||||
|
||||
&.theme--default {
|
||||
a.apps--SideBar-Item-NavLink {
|
||||
color: #f5f4f3;
|
||||
display: block;
|
||||
padding: 0.75em 0.75em;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: #5f5f5f;
|
||||
border-radius: 0.28571429rem 0 0 0.28571429rem;
|
||||
color: #eee;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
a.apps--SideBar-Item-NavLink-active {
|
||||
background: #f5f4f3;
|
||||
border-radius: 0.28571429rem 0 0 0.28571429rem;
|
||||
/* border-bottom: 2px solid transparent; */
|
||||
color: #3f3f3f;
|
||||
|
||||
&:hover {
|
||||
background: #f5f4f3;
|
||||
color: #3f3f3f;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed .apps--SideBar {
|
||||
text-align: center;
|
||||
|
||||
.divider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.apps--SideBar-Item {
|
||||
margin-left: 5px;
|
||||
|
||||
.text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.apps--SideBar-logo {
|
||||
.apps--SideBar-logo-inner {
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
width: 3rem;
|
||||
|
||||
img {
|
||||
margin: 0 0.4rem;
|
||||
}
|
||||
|
||||
> div.info {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apps--SideBar-collapse .ui.basic.secondary.button {
|
||||
left: 0.66rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.expanded .apps--SideBar {
|
||||
text-align: left;
|
||||
|
||||
.apps--SideBar-Scroll {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.fixed {
|
||||
.apps--SideBar-Wrapper {
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
|
||||
.apps--SideBar {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.menu-open {
|
||||
.apps--SideBar-Wrapper {
|
||||
width: 12rem;
|
||||
}
|
||||
}
|
||||
|
||||
.apps--Menu-bg {
|
||||
background: transparent;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transition: opacity 0.2s;
|
||||
width: 100%;
|
||||
z-index: 299;
|
||||
|
||||
&.closed {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
&.open {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
`);
|
||||
|
||||
@ -25,6 +25,7 @@ const NOT_FOUND: Route = {
|
||||
display: {
|
||||
needsApi: undefined
|
||||
},
|
||||
group: 'settings',
|
||||
icon: 'times',
|
||||
isIgnored: false,
|
||||
name: 'unknown',
|
||||
@ -78,13 +79,10 @@ function Content ({ className }: Props): React.ReactElement<Props> {
|
||||
}
|
||||
|
||||
export default React.memo(styled(Content)`
|
||||
background: #f5f4f3;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 0 1.5rem;
|
||||
padding: 0 2.5rem 1rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
|
||||
@ -7,73 +7,80 @@ import { RuntimeVersion } from '@polkadot/types/interfaces';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ChainImg, Icon } from '@polkadot/react-components';
|
||||
import { useCall, useApi } from '@polkadot/react-hooks';
|
||||
import { useApi, useCall, useIpfs, useToggle } from '@polkadot/react-hooks';
|
||||
import { BestNumber, Chain } from '@polkadot/react-query';
|
||||
|
||||
import Endpoints from '../Endpoints';
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
isToggled?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
function ChainInfo ({ className = '', isToggled, onClick }: Props): React.ReactElement<Props> {
|
||||
function ChainInfo ({ className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const runtimeVersion = useCall<RuntimeVersion>(api.rpc.state.subscribeRuntimeVersion);
|
||||
const { ipnsChain } = useIpfs();
|
||||
const [isEndpointsVisible, toggleEndpoints] = useToggle();
|
||||
const canToggle = !ipnsChain;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`apps--SideBar-logo${onClick ? ' isClickable' : ''} ${className} ui--highlight--border`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className='apps--SideBar-logo-inner'>
|
||||
<div className={className}>
|
||||
<div
|
||||
className={`apps--SideBar-logo-inner${canToggle ? ' isClickable' : ''}`}
|
||||
onClick={toggleEndpoints}
|
||||
>
|
||||
<ChainImg />
|
||||
<div className='info'>
|
||||
<Chain className='chain' />
|
||||
{runtimeVersion && (
|
||||
<div className='runtimeVersion'>{t<string>('version {{version}}', { replace: { version: runtimeVersion.specVersion.toNumber() } })}</div>
|
||||
)}
|
||||
<BestNumber label='#' />
|
||||
<BestNumber
|
||||
className='bestNumber'
|
||||
label='#'
|
||||
/>
|
||||
</div>
|
||||
{onClick && (
|
||||
{canToggle && (
|
||||
<Icon
|
||||
className='dropdown'
|
||||
icon={isToggled ? 'caret-right' : 'caret-down'}
|
||||
icon={isEndpointsVisible ? 'caret-right' : 'caret-down'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isEndpointsVisible && (
|
||||
<Endpoints onClose={toggleEndpoints} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(ChainInfo)`
|
||||
border-top: 0.5rem solid transparent;
|
||||
box-sizing: border-box;
|
||||
padding: 0.75rem;
|
||||
margin: 0 0 0.5rem -1rem;
|
||||
|
||||
&.isClickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
padding: 0.75rem 1rem 0.75rem 1.5rem;
|
||||
margin: 0;
|
||||
|
||||
.apps--SideBar-logo-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 10.5rem;
|
||||
|
||||
&.isClickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
img {
|
||||
flex: 0;
|
||||
height: 2.75rem;
|
||||
width: 2.75rem;
|
||||
height: 3rem;
|
||||
margin-right: 0.5rem;
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.ui--Icon.dropdown,
|
||||
> div.info {
|
||||
color: white;
|
||||
opacity: 0.75;
|
||||
opacity: 0.85;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@ -88,14 +95,15 @@ export default React.memo(styled(ChainInfo)`
|
||||
flex: 1;
|
||||
padding-right: 0.5rem;
|
||||
|
||||
> div.bestNumber,
|
||||
> div.chain {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
> div.runtimeVersion {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
91
packages/apps/src/Menu/Grouping.tsx
Normal file
91
packages/apps/src/Menu/Grouping.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2017-2020 @polkadot/apps authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Group } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Icon } from '@polkadot/react-components';
|
||||
|
||||
import Item from './Item';
|
||||
|
||||
interface Props extends Group {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Grouping ({ className, name, routes }: Props): React.ReactElement<Props> {
|
||||
if (routes.length === 1) {
|
||||
return (
|
||||
<Item
|
||||
className='topLevel'
|
||||
route={routes[0]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={className}>
|
||||
<div className='groupHdr'>
|
||||
<span>{name}</span>
|
||||
<Icon icon='caret-down' />
|
||||
</div>
|
||||
<ul className='groupMenu'>
|
||||
{routes.map((route): React.ReactNode => (
|
||||
<Item
|
||||
key={route.name}
|
||||
route={route}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Grouping)`
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
.groupHdr {
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
padding: 1rem 1.5rem;
|
||||
|
||||
> .ui--Icon {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.groupMenu {
|
||||
background: #6a6662;
|
||||
border-radius: 0 0 0.25rem 0.25rem;
|
||||
display: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 250;
|
||||
|
||||
> li {
|
||||
z-index: 1;
|
||||
|
||||
a {
|
||||
padding-right: 3.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.groupHdr,
|
||||
.groupMenu li {
|
||||
background: #74706c;
|
||||
}
|
||||
|
||||
.groupMenu {
|
||||
display: block;
|
||||
|
||||
> li:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
80
packages/apps/src/Menu/Item.tsx
Normal file
80
packages/apps/src/Menu/Item.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2017-2020 @polkadot/apps authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ItemRoute } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Badge, Icon } from '@polkadot/react-components';
|
||||
import { useToggle } from '@polkadot/react-hooks';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
route: ItemRoute;
|
||||
}
|
||||
|
||||
const DUMMY_COUNTER = () => 0;
|
||||
|
||||
function Item ({ className = '', route: { Modal, href, icon, name, text, useCounter = DUMMY_COUNTER } }: Props): React.ReactElement<Props> {
|
||||
const [isModalVisible, toggleModal] = useToggle();
|
||||
const count = useCounter();
|
||||
|
||||
return (
|
||||
<li className={`${className}${count ? ' withCounter' : ''}`}>
|
||||
<a
|
||||
href={Modal ? undefined : (href || `#/${name}`)}
|
||||
onClick={Modal ? toggleModal : undefined}
|
||||
rel='noopener noreferrer'
|
||||
target={href ? '_blank' : undefined}
|
||||
>
|
||||
<Icon icon={icon} />
|
||||
{text}
|
||||
{!!count && (
|
||||
<Badge
|
||||
color='counter'
|
||||
info={count}
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
{Modal && isModalVisible && (
|
||||
<Modal onClose={toggleModal} />
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Item)`
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
|
||||
&.topLevel {
|
||||
padding: 0;
|
||||
|
||||
a {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.withCounter a {
|
||||
padding-right: 3.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit !important;
|
||||
display: block;
|
||||
padding: 0.75rem 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ui--Badge {
|
||||
position: absolute;
|
||||
right: 0.25rem;
|
||||
top: 0.65rem;
|
||||
}
|
||||
|
||||
.ui--Icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
`);
|
||||
@ -14,11 +14,11 @@ const pkgJson = require('../../package.json') as { version: string };
|
||||
|
||||
const uiInfo = `apps v${pkgJson.version}`;
|
||||
|
||||
function NodeInfo ({ className }: Props): React.ReactElement<Props> {
|
||||
function NodeInfo ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
const { api, isApiReady } = useApi();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={`${className} ui--media-1400`}>
|
||||
{isApiReady && (
|
||||
<div>
|
||||
<NodeName />
|
||||
@ -34,9 +34,10 @@ function NodeInfo ({ className }: Props): React.ReactElement<Props> {
|
||||
export default React.memo(styled(NodeInfo)`
|
||||
background: transparent;
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.5;
|
||||
padding: 0 1.5rem 0 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.2;
|
||||
opacity: 0.85;
|
||||
padding: 0 1.5rem;
|
||||
text-align: right;
|
||||
|
||||
> div {
|
||||
205
packages/apps/src/Menu/index.tsx
Normal file
205
packages/apps/src/Menu/index.tsx
Normal file
@ -0,0 +1,205 @@
|
||||
// Copyright 2017-2020 @polkadot/apps authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Route, Routes } from '@polkadot/apps-routing/types';
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
import { Group, Groups, ItemRoute } from './types';
|
||||
|
||||
import { TFunction } from 'i18next';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import createRoutes from '@polkadot/apps-routing';
|
||||
import { Icon } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
import { findMissingApis } from '../endpoint';
|
||||
import { useTranslation } from '../translate';
|
||||
import ChainInfo from './ChainInfo';
|
||||
import Grouping from './Grouping';
|
||||
import Item from './Item';
|
||||
import NodeInfo from './NodeInfo';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const disabledLog = new Map<string, string>();
|
||||
|
||||
function createExternals (t: TFunction): ItemRoute[] {
|
||||
return [
|
||||
{ href: 'https://github.com/polkadot-js/apps', icon: 'code-branch', name: 'github', text: t<string>('nav.github', 'GitHub', { ns: 'apps-routing' }) },
|
||||
{ href: 'https://wiki.polkadot.network', icon: 'book', name: 'wiki', text: t<string>('nav.wiki', 'Wiki', { ns: 'apps-routing' }) }
|
||||
];
|
||||
}
|
||||
|
||||
function logDisabled (route: string, message: string): void {
|
||||
if (!disabledLog.get(route)) {
|
||||
disabledLog.set(route, message);
|
||||
|
||||
console.warn(`Disabling ${route}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function checkVisible (name: string, { api, isApiConnected, isApiReady }: ApiProps, hasAccounts: boolean, hasSudo: boolean, { isHidden, needsAccounts, needsApi, needsSudo }: Route['display']): boolean {
|
||||
if (isHidden) {
|
||||
return false;
|
||||
} else if (needsAccounts && !hasAccounts) {
|
||||
return false;
|
||||
} else if (!needsApi) {
|
||||
return true;
|
||||
} else if (!isApiReady || !isApiConnected) {
|
||||
return false;
|
||||
} else if (needsSudo && !hasSudo) {
|
||||
logDisabled(name, 'Sudo key not available');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const notFound = findMissingApis(api, needsApi);
|
||||
|
||||
if (notFound.length !== 0) {
|
||||
logDisabled(name, `API not available: ${notFound.toString()}`);
|
||||
}
|
||||
|
||||
return notFound.length === 0;
|
||||
}
|
||||
|
||||
function extractGroups (routing: Routes, groupNames: Record<string, string>, apiProps: ApiProps, hasAccounts: boolean, hasSudo: boolean): Group[] {
|
||||
return Object
|
||||
.values(
|
||||
routing.reduce((all: Groups, route): Groups => {
|
||||
if (!all[route.group]) {
|
||||
all[route.group] = { name: groupNames[route.group], routes: [route] };
|
||||
} else {
|
||||
all[route.group].routes.push(route);
|
||||
}
|
||||
|
||||
return all;
|
||||
}, {})
|
||||
)
|
||||
.map(({ name, routes }): Group => ({
|
||||
name,
|
||||
routes: routes.filter(({ display, name }) => checkVisible(name, apiProps, hasAccounts, hasSudo, display))
|
||||
}))
|
||||
.filter(({ routes }) => routes.length);
|
||||
}
|
||||
|
||||
function Menu ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { allAccounts, hasAccounts } = useAccounts();
|
||||
const apiProps = useApi();
|
||||
const sudoKey = useCall<AccountId>(apiProps.isApiReady && apiProps.api.query.sudo?.key);
|
||||
const location = useLocation();
|
||||
|
||||
const externalRef = useRef(createExternals(t));
|
||||
|
||||
const groupRef = useRef({
|
||||
accounts: t('Accounts'),
|
||||
developer: t('Developer'),
|
||||
governance: t('Governance'),
|
||||
network: t('Network'),
|
||||
settings: t('Settings')
|
||||
});
|
||||
|
||||
const routeRef = useRef(createRoutes(t));
|
||||
|
||||
const hasSudo = useMemo(
|
||||
() => !!sudoKey && allAccounts.some((address) => sudoKey.eq(address)),
|
||||
[allAccounts, sudoKey]
|
||||
);
|
||||
|
||||
const visibleGroups = useMemo(
|
||||
() => extractGroups(routeRef.current, groupRef.current, apiProps, hasAccounts, hasSudo),
|
||||
[apiProps, hasAccounts, hasSudo]
|
||||
);
|
||||
|
||||
const activeRoute = useMemo(
|
||||
() => routeRef.current.find((route) => location.pathname.startsWith(`/${route.name}`)) || null,
|
||||
[location]
|
||||
);
|
||||
|
||||
const isLoading = !apiProps.isApiReady || !apiProps.isApiConnected;
|
||||
|
||||
return (
|
||||
<div className={`${className} ui--highlight--border`}>
|
||||
<div className='menuSection'>
|
||||
<ChainInfo />
|
||||
{activeRoute && (
|
||||
<div className={`menuActive${isLoading ? ' isLoading' : ''} ui--highlight--border`}>
|
||||
<Icon icon={activeRoute.icon} />
|
||||
<span>{activeRoute.text}</span>
|
||||
</div>
|
||||
)}
|
||||
<ul className='menuItems'>
|
||||
{visibleGroups.map(({ name, routes }): React.ReactNode => (
|
||||
<Grouping
|
||||
key={name}
|
||||
name={name}
|
||||
routes={routes}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className='menuSection'>
|
||||
<ul className='menuItems'>
|
||||
{externalRef.current.map((route): React.ReactNode => (
|
||||
<Item
|
||||
className='topLevel'
|
||||
key={route.name}
|
||||
route={route}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<NodeInfo />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Menu)`
|
||||
align-items: center;
|
||||
background: #6a6662;
|
||||
border-top: 0.5rem solid transparent;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
z-index: 220;
|
||||
|
||||
.menuSection {
|
||||
align-items: flex-end;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.menuActive {
|
||||
background: #fefcfa;
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 0 1.5rem;
|
||||
|
||||
&.isLoading {
|
||||
background: #f5f3f1;
|
||||
}
|
||||
|
||||
.ui--Icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.menuItems {
|
||||
background: #6a6662;
|
||||
color: #f5f4f3;
|
||||
flex: 1 1;
|
||||
list-style: none;
|
||||
margin: 0 2rem 0 0;
|
||||
padding: 0;
|
||||
|
||||
> li {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
`);
|
||||
24
packages/apps/src/Menu/types.tsx
Normal file
24
packages/apps/src/Menu/types.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2017-2020 @polkadot/apps authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { IconName } from '@fortawesome/fontawesome-svg-core';
|
||||
import { Routes } from '@polkadot/apps-routing/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export interface ItemRoute {
|
||||
Modal?: React.ComponentType<any>;
|
||||
href?: string;
|
||||
icon: IconName;
|
||||
name: string;
|
||||
text: string;
|
||||
useCounter?: () => number | string | null;
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
name: string;
|
||||
routes: Routes;
|
||||
}
|
||||
|
||||
export type Groups = Record<string, Group>;
|
||||
@ -1,135 +0,0 @@
|
||||
// Copyright 2017-2020 @polkadot/apps authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { Route } from '@polkadot/apps-routing/types';
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { Badge, Icon, Menu, Tooltip } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
import { findMissingApis } from '../endpoint';
|
||||
|
||||
const DUMMY_COUNTER = (): null => null;
|
||||
|
||||
interface Props {
|
||||
isCollapsed: boolean;
|
||||
onClick: () => void;
|
||||
route: Route;
|
||||
}
|
||||
|
||||
const disabledLog = new Map<string, string>();
|
||||
const TOOLTIP_OFFSET = { right: -4 };
|
||||
|
||||
function logDisabled (route: string, message: string): void {
|
||||
if (!disabledLog.get(route)) {
|
||||
disabledLog.set(route, message);
|
||||
|
||||
console.warn(`Disabling ${route}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function checkVisible (name: string, { api, isApiConnected, isApiReady }: ApiProps, hasAccounts: boolean, hasSudo: boolean, { isHidden, needsAccounts, needsApi, needsSudo }: Route['display']): boolean {
|
||||
if (isHidden) {
|
||||
return false;
|
||||
} else if (needsAccounts && !hasAccounts) {
|
||||
return false;
|
||||
} else if (!needsApi) {
|
||||
return true;
|
||||
} else if (!isApiReady || !isApiConnected) {
|
||||
return false;
|
||||
} else if (needsSudo && !hasSudo) {
|
||||
logDisabled(name, 'Sudo key not available');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const notFound = findMissingApis(api, needsApi);
|
||||
|
||||
if (notFound.length !== 0) {
|
||||
logDisabled(name, `API not available: ${notFound.toString()}`);
|
||||
}
|
||||
|
||||
return notFound.length === 0;
|
||||
}
|
||||
|
||||
function Item ({ isCollapsed, onClick, route }: Props): React.ReactElement<Props> | null {
|
||||
const { allAccounts, hasAccounts } = useAccounts();
|
||||
const apiProps = useApi();
|
||||
const sudoKey = useCall<AccountId>(apiProps.isApiReady && apiProps.api.query.sudo?.key);
|
||||
const [hasSudo, setHasSudo] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const count = (route.useCounter || DUMMY_COUNTER)();
|
||||
|
||||
useEffect((): void => {
|
||||
setHasSudo(!!sudoKey && allAccounts.some((address) => sudoKey.eq(address)));
|
||||
}, [allAccounts, sudoKey]);
|
||||
|
||||
useEffect((): void => {
|
||||
const isVisible = checkVisible(route.name, apiProps, hasAccounts, hasSudo, route.display);
|
||||
|
||||
route.isIgnored = !isVisible;
|
||||
setIsVisible(isVisible);
|
||||
}, [apiProps, hasAccounts, hasSudo, route]);
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { Modal, icon, name, text } = route;
|
||||
|
||||
const body = (
|
||||
<>
|
||||
<Icon icon={icon} />
|
||||
<span className='text'>{text}</span>
|
||||
{!!count && (
|
||||
<Badge
|
||||
color='counter'
|
||||
info={count}
|
||||
/>
|
||||
)}
|
||||
<Tooltip
|
||||
offset={TOOLTIP_OFFSET}
|
||||
place='right'
|
||||
text={text}
|
||||
trigger={`nav-${name}`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu.Item className='apps--SideBar-Item'>
|
||||
{Modal
|
||||
? (
|
||||
<a
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
data-for={`nav-${name}`}
|
||||
data-tip
|
||||
data-tip-disable={!isCollapsed}
|
||||
onClick={onClick}
|
||||
>
|
||||
{body}
|
||||
</a>
|
||||
)
|
||||
: (
|
||||
<NavLink
|
||||
activeClassName='apps--SideBar-Item-NavLink-active ui--highlight--border'
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
data-for={`nav-${name}`}
|
||||
data-tip
|
||||
data-tip-disable={!isCollapsed}
|
||||
onClick={onClick}
|
||||
to={`/${name}`}
|
||||
>
|
||||
{body}
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
</Menu.Item>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Item);
|
||||
@ -1,294 +0,0 @@
|
||||
// Copyright 2017-2020 @polkadot/apps authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Routes } from '@polkadot/apps-routing/types';
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import createRoutes from '@polkadot/apps-routing';
|
||||
import { Button, ChainImg, Icon, Menu, media } from '@polkadot/react-components';
|
||||
import { useIpfs } from '@polkadot/react-hooks';
|
||||
|
||||
import Endpoints from '../Endpoints';
|
||||
import { SIDEBAR_MENU_THRESHOLD } from '../constants';
|
||||
import { useTranslation } from '../translate';
|
||||
import ChainInfo from './ChainInfo';
|
||||
import Item from './Item';
|
||||
import NodeInfo from './NodeInfo';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
collapse: () => void;
|
||||
handleResize: () => void;
|
||||
isCollapsed: boolean;
|
||||
isMenuOpen: boolean;
|
||||
toggleMenu: () => void;
|
||||
}
|
||||
|
||||
const SIDE_BORDER_WIDTH = '0.65rem';
|
||||
const MENU_WIDTH_COLLAPSED = '4.2rem';
|
||||
const MENU_WIDTH_EXPANDED = '12rem';
|
||||
|
||||
function AppsSidebar ({ className = '', collapse, handleResize, isCollapsed, isMenuOpen, toggleMenu }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { ipnsChain } = useIpfs();
|
||||
const [modals, setModals] = useState<Record<string, boolean>>(
|
||||
createRoutes(t).reduce((result: Record<string, boolean>, route): Record<string, boolean> => {
|
||||
if (route && route.Modal) {
|
||||
result[route.name] = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, { network: false })
|
||||
);
|
||||
|
||||
const routing = useMemo<Routes>(
|
||||
() => createRoutes(t),
|
||||
[t]
|
||||
);
|
||||
|
||||
const _toggleModal = useCallback(
|
||||
(name: string): () => void =>
|
||||
(): void => setModals((modals: Record<string, boolean>) => ({
|
||||
...modals,
|
||||
[name]: !modals[name]
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`apps--SideBar-Wrapper ${className} ${isCollapsed ? 'collapsed' : 'expanded'}`}>
|
||||
<ChainImg
|
||||
className={`toggleImg ${isMenuOpen ? 'closed' : 'open delayed'}`}
|
||||
onClick={toggleMenu}
|
||||
/>
|
||||
{modals.network && (
|
||||
<Endpoints
|
||||
offset={isCollapsed ? MENU_WIDTH_COLLAPSED : MENU_WIDTH_EXPANDED}
|
||||
onClose={_toggleModal('network')}
|
||||
/>
|
||||
)}
|
||||
{routing.map((route): React.ReactNode => (
|
||||
route?.Modal
|
||||
? route.Modal && modals[route.name]
|
||||
? (
|
||||
<route.Modal
|
||||
key={route.name}
|
||||
onClose={_toggleModal(route.name)}
|
||||
/>
|
||||
)
|
||||
: <div key={route.name} />
|
||||
: null
|
||||
))}
|
||||
<div className='apps--SideBar'>
|
||||
<Menu
|
||||
secondary
|
||||
vertical
|
||||
>
|
||||
<div className='apps--SideBar-Scroll'>
|
||||
<ChainInfo
|
||||
isToggled={modals.network}
|
||||
onClick={
|
||||
ipnsChain
|
||||
? undefined
|
||||
: _toggleModal('network')
|
||||
}
|
||||
/>
|
||||
{routing.map((route, index): React.ReactNode => (
|
||||
route
|
||||
? (
|
||||
<Item
|
||||
isCollapsed={isCollapsed}
|
||||
key={route.name}
|
||||
onClick={
|
||||
route.Modal
|
||||
? _toggleModal(route.name)
|
||||
: handleResize
|
||||
}
|
||||
route={route}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Menu.Divider
|
||||
hidden
|
||||
key={index}
|
||||
/>
|
||||
)
|
||||
))}
|
||||
<Menu.Divider hidden />
|
||||
<Menu.Item className='apps--SideBar-Item'>
|
||||
<a
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
href='https://github.com/polkadot-js/apps'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<Icon icon='code-branch' /><span className='text'>{t<string>('nav.github', 'GitHub', { ns: 'apps-routing' })}</span>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item className='apps--SideBar-Item'>
|
||||
<a
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
href='https://wiki.polkadot.network'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<Icon icon='book' /><span className='text'>{t<string>('nav.wiki', 'Wiki', { ns: 'apps-routing' })}</span>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Divider hidden />
|
||||
{!isCollapsed && <NodeInfo />}
|
||||
</div>
|
||||
<div className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}>
|
||||
<Button
|
||||
icon={isCollapsed ? 'angle-double-right' : 'angle-double-left'}
|
||||
isBasic
|
||||
isCircular
|
||||
onClick={collapse}
|
||||
/>
|
||||
</div>
|
||||
</Menu>
|
||||
<div
|
||||
className='apps--SideBar-toggle'
|
||||
onClick={collapse}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(AppsSidebar)`
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 300;
|
||||
|
||||
&.collapsed {
|
||||
width: ${MENU_WIDTH_COLLAPSED};
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
width: ${MENU_WIDTH_EXPANDED};
|
||||
}
|
||||
|
||||
.apps--SideBar {
|
||||
align-items: center;
|
||||
background: #4f5255;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: auto;
|
||||
position: relative;
|
||||
transition: left 0.3s linear;
|
||||
width: 100%;
|
||||
|
||||
.apps--SideBar-border {
|
||||
border-top: ${SIDE_BORDER_WIDTH} solid transparent;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.ui.vertical.menu {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.apps--SideBar-Scroll {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.apps--SideBar-Item {
|
||||
align-self: flex-end;
|
||||
flex-grow: 0;
|
||||
padding: 0 !important;
|
||||
position: relative;
|
||||
width: inherit;
|
||||
|
||||
.text {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.ui--Badge {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 0.55rem;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.apps--SideBar-collapse {
|
||||
background: #4f5255;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 0.75rem 0 .75rem 0.65rem;
|
||||
position: sticky;
|
||||
right: 0;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.apps--SideBar-toggle {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
transition: all 0.2s;
|
||||
width: 6px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255,255,255,0.15);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggleImg {
|
||||
cursor: pointer;
|
||||
height: 2.75rem;
|
||||
left: 0.9rem;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
transition: opacity 0.2s ease-in, top 0.2s ease-in;
|
||||
width: 2.75rem;
|
||||
|
||||
&.delayed {
|
||||
transition-delay: 0.4s;
|
||||
}
|
||||
|
||||
&.open {
|
||||
opacity: 1;
|
||||
top: 0.9rem;
|
||||
}
|
||||
|
||||
${media.DESKTOP`
|
||||
opacity: 0 !important;
|
||||
top: -2.9rem !important;
|
||||
`}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${SIDEBAR_MENU_THRESHOLD}px) {
|
||||
.apps--SideBar-collapse,
|
||||
.apps--Sidebar-toggle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -1,15 +0,0 @@
|
||||
// Copyright 2017-2020 @polkadot/apps authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ScreenSizes } from '@polkadot/react-components/constants';
|
||||
|
||||
export enum SideBarTransition {
|
||||
COLLAPSED = 'COLLAPSED',
|
||||
EXPANDED = 'EXPANDED',
|
||||
EXPANDED_AND_MAXIMISED = 'EXPANDED_AND_MAXIMISED',
|
||||
MINIMISED_AND_EXPANDED = 'MINIMISED_AND_EXPANDED'
|
||||
}
|
||||
|
||||
export const SIDEBAR_MENU_THRESHOLD = ScreenSizes.DESKTOP;
|
||||
export const SIDEBAR_TRANSITION_DURATION = 300;
|
||||
@ -51,7 +51,6 @@ export default React.memo(styled(BaseOverlay)`
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 0.25rem;
|
||||
right: 0;
|
||||
line-height: 1.5em;
|
||||
padding: 0 1rem;
|
||||
position: fixed;
|
||||
|
||||
@ -44,8 +44,9 @@ function CouncilApp ({ basePath, className }: Props): React.ReactElement<Props>
|
||||
text: t<string>('Council overview')
|
||||
},
|
||||
{
|
||||
count: numMotions,
|
||||
name: 'motions',
|
||||
text: t<string>('Motions ({{count}})', { replace: { count: numMotions } })
|
||||
text: t<string>('Motions')
|
||||
}
|
||||
], [numMotions, t]);
|
||||
|
||||
|
||||
@ -24,14 +24,17 @@ function General ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
// tri-state: null = nothing changed, false = no reload, true = reload required
|
||||
const [changed, setChanged] = useState<boolean | null>(null);
|
||||
const [settings, setSettings] = useState(uiSettings.get());
|
||||
|
||||
const iconOptions = useMemo(
|
||||
() => uiSettings.availableIcons.map((o): Option => createIdenticon(o, ['default'])),
|
||||
[]
|
||||
);
|
||||
|
||||
const prefixOptions = useMemo(
|
||||
() => createSs58(t).map((o): Option | React.ReactNode => createOption(o, ['default'])),
|
||||
[t]
|
||||
);
|
||||
|
||||
const translateLanguages = useMemo(
|
||||
() => createLanguages(t),
|
||||
[t]
|
||||
@ -54,10 +57,12 @@ function General ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
setSettings((settings) => ({ ...settings, [key]: value })),
|
||||
[]
|
||||
);
|
||||
|
||||
const _saveAndReload = useCallback(
|
||||
() => saveAndReload(settings),
|
||||
[settings]
|
||||
);
|
||||
|
||||
const _save = useCallback(
|
||||
(): void => {
|
||||
save(settings);
|
||||
@ -66,7 +71,7 @@ function General ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
[settings]
|
||||
);
|
||||
|
||||
const { i18nLang, icon, ledgerConn, prefix, uiMode } = settings;
|
||||
const { i18nLang, icon, ledgerConn, prefix } = settings;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
@ -88,15 +93,6 @@ function General ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
options={iconOptions}
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<Dropdown
|
||||
defaultValue={uiMode}
|
||||
help={t<string>('Adjust the mode from basic (with a limited number of beginner-user-friendly apps) to full (with all basic & advanced apps available)')}
|
||||
label={t<string>('interface operation mode')}
|
||||
onChange={_handleChange('uiMode')}
|
||||
options={uiSettings.availableUIModes}
|
||||
/>
|
||||
</div>
|
||||
{isLedgerCapable() && (
|
||||
<div className='ui--row'>
|
||||
<Dropdown
|
||||
|
||||
@ -28,12 +28,9 @@ function SettingsApp ({ basePath, onStatusChange }: Props): React.ReactElement<P
|
||||
text: t<string>('General')
|
||||
},
|
||||
{
|
||||
count: numExtensions,
|
||||
name: 'metadata',
|
||||
text: t<string>('Metadata {{count}}', {
|
||||
replace: {
|
||||
count: numExtensions ? `(${numExtensions})` : ''
|
||||
}
|
||||
})
|
||||
text: t<string>('Metadata')
|
||||
},
|
||||
{
|
||||
name: 'developer',
|
||||
|
||||
@ -90,8 +90,9 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
|
||||
text: t<string>('Waiting')
|
||||
},
|
||||
{
|
||||
count: slashes.reduce((count, [, unapplied]) => count + unapplied.length, 0),
|
||||
name: 'slashes',
|
||||
text: t<string>('Slashes ({{count}})', { replace: { count: slashes.reduce((count, [, unapplied]) => count + unapplied.length, 0) } })
|
||||
text: t<string>('Slashes')
|
||||
},
|
||||
{
|
||||
hasParams: true,
|
||||
|
||||
@ -12,7 +12,7 @@ import Tooltip from './Tooltip';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
color: 'counter' | 'green' | 'blue' | 'gray' | 'normal' | 'purple' | 'red' | 'transparent';
|
||||
color: 'blue' | 'counter' | 'gray' | 'green' | 'highlight' | 'normal' | 'purple' | 'red' | 'transparent';
|
||||
hover?: React.ReactNode;
|
||||
icon?: IconName;
|
||||
info?: React.ReactNode;
|
||||
@ -20,6 +20,8 @@ interface Props {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const HIGHLIGHTS = ['counter', 'highlight'];
|
||||
|
||||
let badgeId = 0;
|
||||
|
||||
function Badge ({ className = '', color = 'normal', hover, icon, info, isSmall, onClick }: Props): React.ReactElement<Props> | null {
|
||||
@ -27,11 +29,12 @@ function Badge ({ className = '', color = 'normal', hover, icon, info, isSmall,
|
||||
const extraProps = hover
|
||||
? { 'data-for': trigger, 'data-tip': true }
|
||||
: {};
|
||||
const isHighlight = HIGHLIGHTS.includes(color);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...extraProps}
|
||||
className={`ui--Badge${hover ? ' isTooltip' : ''}${isSmall ? ' isSmall' : ''}${onClick ? ' isClickable' : ''} ${color}Color ${className}`}
|
||||
className={`ui--Badge${hover ? ' isTooltip' : ''}${isSmall ? ' isSmall' : ''}${onClick ? ' isClickable' : ''}${isHighlight ? ' ui--highlight--bg' : ''} ${color}Color ${className}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{info || (icon && <Icon icon={icon} />)}
|
||||
@ -47,7 +50,7 @@ function Badge ({ className = '', color = 'normal', hover, icon, info, isSmall,
|
||||
|
||||
export default React.memo(styled(Badge)`
|
||||
border-radius: 16px;
|
||||
color: #eee;
|
||||
color: #eeedec;
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
height: 22px;
|
||||
@ -93,14 +96,13 @@ export default React.memo(styled(Badge)`
|
||||
}
|
||||
|
||||
&.counterColor {
|
||||
background: red;
|
||||
margin: 0 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&.grayColor {
|
||||
background: #eee !important;
|
||||
color: #aaa;
|
||||
background: #eeedec !important;
|
||||
color: #aaa9a8;
|
||||
}
|
||||
|
||||
&.redColor {
|
||||
|
||||
@ -32,7 +32,7 @@ export default React.memo(styled(FilterOverlay)`
|
||||
justify-content: flex-end;
|
||||
position: absolute;
|
||||
right: 5rem;
|
||||
top: 0.4rem;
|
||||
top: 0.525rem;
|
||||
|
||||
> div {
|
||||
max-width: 35rem !important;
|
||||
|
||||
@ -46,13 +46,13 @@ export default React.memo(styled(HelpOverlay)`
|
||||
.help-button {
|
||||
cursor: pointer;
|
||||
font-size: 2rem;
|
||||
padding: 1.25rem 1.5rem 0 0;
|
||||
padding: 1rem 2.5rem 0 0;
|
||||
}
|
||||
|
||||
> .help-button {
|
||||
position: absolute;
|
||||
right: 0rem;
|
||||
top: 0rem;
|
||||
top: 0.125rem;
|
||||
}
|
||||
|
||||
.help-slideout {
|
||||
|
||||
@ -139,7 +139,7 @@ export default React.memo(styled(Progress)`
|
||||
div {
|
||||
line-height: 1;
|
||||
font-size: 1.25rem;
|
||||
text-shadow: 0 0 2px #f5f4f3;
|
||||
text-shadow: 0 0 2px #f5f3f1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ function Sidebar ({ button, children, className = '', onClose }: Props): React.R
|
||||
}
|
||||
|
||||
export default React.memo(styled(Sidebar)(({ offset = 0, position }: Props) => `
|
||||
background: #f5f4f3;
|
||||
background: #f5f3f1;
|
||||
bottom: 0;
|
||||
box-shadow: ${position === 'right' ? '-6px' : '6px'} 0px 20px 0px rgba(0, 0, 0, 0.3);
|
||||
margin-left: -0.125rem;
|
||||
|
||||
@ -137,7 +137,7 @@ export default React.memo(styled(Body)`
|
||||
|
||||
tr {
|
||||
&:nth-child(even) {
|
||||
background: #faf9f8;
|
||||
background: #faf8f6;
|
||||
}
|
||||
|
||||
&:nth-child(odd) {
|
||||
|
||||
@ -8,17 +8,19 @@ import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Badge from '../Badge';
|
||||
import Icon from '../Icon';
|
||||
|
||||
interface Props extends TabItem {
|
||||
basePath: string;
|
||||
className?: string;
|
||||
count?: number;
|
||||
index: number;
|
||||
isSequence?: boolean;
|
||||
num: number;
|
||||
}
|
||||
|
||||
function Tab ({ basePath, className = '', hasParams, index, isExact, isRoot, isSequence, name, num, text }: Props): React.ReactElement<Props> {
|
||||
function Tab ({ basePath, className = '', count, hasParams, index, isExact, isRoot, isSequence, name, num, text }: Props): React.ReactElement<Props> {
|
||||
const to = isRoot
|
||||
? basePath
|
||||
: `${basePath}/${name}`;
|
||||
@ -41,19 +43,24 @@ function Tab ({ basePath, className = '', hasParams, index, isExact, isRoot, isS
|
||||
icon='arrow-right'
|
||||
/>
|
||||
)}
|
||||
{!!count && (
|
||||
<Badge
|
||||
className='tabCounter'
|
||||
color='counter'
|
||||
info={count}
|
||||
/>
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Tab)`
|
||||
border-bottom: 2px solid transparent;
|
||||
color: rgba(0, 0, 0, 0.87) !important;
|
||||
padding: 0.75rem 1.5rem;
|
||||
color: #4e4e4e !important;
|
||||
padding: 0.5rem 1.5rem 0.75rem;
|
||||
|
||||
&.tabLinkActive {
|
||||
border-bottom-color: #e6e6e6;
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@ -64,6 +71,10 @@ export default React.memo(styled(Tab)`
|
||||
}
|
||||
}
|
||||
|
||||
.tabCounter {
|
||||
margin: -1rem 0 -1rem 0.5rem;
|
||||
}
|
||||
|
||||
.tabIcon {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
@ -59,10 +59,13 @@ function Tabs ({ basePath, className = '', hidden, isSequence, items }: Props):
|
||||
}
|
||||
|
||||
export default React.memo(styled(Tabs)`
|
||||
align-items: flex-end;
|
||||
background: #fefcfa;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
margin: 0 -2em;
|
||||
display: flex;
|
||||
margin: 0 -3rem;
|
||||
overflow-x: scroll;
|
||||
padding: 2em 2em 0.75rem;
|
||||
padding: 1.5rem 3rem 0;
|
||||
text-align: left;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
|
||||
@ -6,6 +6,7 @@ import React from 'react';
|
||||
|
||||
export interface TabItem {
|
||||
alias?: string;
|
||||
count?: number;
|
||||
hasParams?: boolean;
|
||||
isExact?: boolean;
|
||||
isRoot?: boolean;
|
||||
|
||||
@ -9,7 +9,7 @@ import Tooltip from './Tooltip';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
color?: 'blue' | 'green' | 'grey' | 'orange' | 'pink' | 'red';
|
||||
color?: 'blue' | 'green' | 'grey' | 'orange' | 'pink' | 'red' | 'yellow';
|
||||
hover?: React.ReactNode;
|
||||
isTag?: boolean;
|
||||
label: React.ReactNode;
|
||||
@ -82,6 +82,10 @@ export default React.memo(styled(Tag)`
|
||||
background: #db2828;
|
||||
}
|
||||
|
||||
&.yellowColor {
|
||||
background: darkgoldenrod;
|
||||
}
|
||||
|
||||
&.isTag {
|
||||
border-radius: 0 0.25rem 0.25rem 0;
|
||||
padding-left: 1.5em;
|
||||
|
||||
@ -228,6 +228,7 @@ export default createGlobalStyle<Props>`
|
||||
}
|
||||
|
||||
body {
|
||||
background: #f5f3f1;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
@ -301,8 +302,6 @@ export default createGlobalStyle<Props>`
|
||||
}
|
||||
|
||||
main {
|
||||
min-height: 100vh;
|
||||
|
||||
> section {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
@ -13,10 +13,11 @@ interface Props {
|
||||
|
||||
function NodeVersion ({ children, className = '', label }: Props): React.ReactElement<Props> {
|
||||
const { systemVersion } = useApi();
|
||||
const displayVersion = systemVersion.split('-').filter((_, index) => index <= 1).join('-');
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{label || ''}{systemVersion}{children}
|
||||
{label || ''}{displayVersion}{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user