feat: rework web header for mobile, add new settings and raw manifest button (#3129)
* feat: rework header, dialogs and new raw mode * chore: add test for raw button and hide download tarball * chore: add test hide footer * chore: add docs to config files * chore: add changeset * chore: enable raw by default
43
.changeset/orange-flowers-cover.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
'@verdaccio/config': minor
|
||||
'@verdaccio/types': minor
|
||||
'@verdaccio/ui-theme': minor
|
||||
---
|
||||
|
||||
feat: rework web header for mobile, add new settings and raw manifest button
|
||||
|
||||
### New set of variables to hide features
|
||||
|
||||
Add set of new variables that allow hide different parts of the UI, buttons, footer or download tarballs. _All are
|
||||
enabled by default_.
|
||||
|
||||
```yaml
|
||||
# login: true <-- already exist but worth the reminder
|
||||
# showInfo: true
|
||||
# showSettings: true
|
||||
# In combination with darkMode you can force specific theme
|
||||
# showThemeSwitch: true
|
||||
# showFooter: true
|
||||
# showSearch: true
|
||||
# showDownloadTarball: true
|
||||
```
|
||||
|
||||
> If you disable `showThemeSwitch` and force `darkMode: true` the local storage settings would be
|
||||
> ignored and force all themes to the one in the configuration file.
|
||||
|
||||
Future could be extended to
|
||||
|
||||
### Raw button to display manifest package
|
||||
|
||||
A new experimental feature (enabled by default), button named RAW to be able navigate on the package manifest directly on the ui, kudos to [react-json-view](https://www.npmjs.com/package/react-json-view) that allows an easy integration, not configurable yet until get more feedback.
|
||||
|
||||
```yaml
|
||||
showRaw: true
|
||||
```
|
||||
|
||||
#### Rework header buttons
|
||||
|
||||
- The header has been rework, the mobile was not looking broken.
|
||||
- Removed info button in the header and moved to a dialog
|
||||
- Info dialog now contains more information about the project, license and the aid content for Ukrania now is inside of the info modal.
|
||||
- Separate settings and info to avoid collapse too much info (for mobile still need some work)
|
@ -20,6 +20,16 @@ web:
|
||||
# convert your UI to the dark side
|
||||
# darkMode: true
|
||||
# html_cache: true
|
||||
# by default all features are displayed
|
||||
# login: true
|
||||
# showInfo: true
|
||||
# showSettings: true
|
||||
# In combination with darkMode you can force specific theme
|
||||
# showThemeSwitch: true
|
||||
# showFooter: true
|
||||
# showSearch: true
|
||||
# showRaw: true
|
||||
# showDownloadTarball: true
|
||||
# HTML tags injected after manifest <scripts/>
|
||||
# scriptsBodyAfter:
|
||||
# - '<script type="text/javascript" src="https://my.company.com/customJS.min.js"></script>'
|
||||
|
@ -26,6 +26,16 @@ web:
|
||||
# convert your UI to the dark side
|
||||
# darkMode: true
|
||||
# html_cache: true
|
||||
# by default all features are displayed
|
||||
# login: true
|
||||
# showInfo: true
|
||||
# showSettings: true
|
||||
# In combination with darkMode you can force specific theme
|
||||
# showThemeSwitch: true
|
||||
# showFooter: true
|
||||
# showSearch: true
|
||||
# showRaw: true
|
||||
# showDownloadTarball: true
|
||||
# HTML tags injected after manifest <scripts/>
|
||||
# scriptsBodyAfter:
|
||||
# - '<script type="text/javascript" src="https://my.company.com/customJS.min.js"></script>'
|
||||
|
7
packages/core/types/index.d.ts
vendored
@ -43,6 +43,13 @@ declare module '@verdaccio/types' {
|
||||
// deprecated
|
||||
basename?: string;
|
||||
scope?: string;
|
||||
showInfo?: boolean;
|
||||
showSettings?: boolean;
|
||||
showSearch?: boolean;
|
||||
showFooter?: boolean;
|
||||
showThemeSwitch?: boolean;
|
||||
showDownloadTarball?: boolean;
|
||||
showRaw?: boolean;
|
||||
base: string;
|
||||
primaryColor?: string;
|
||||
version?: string;
|
||||
|
@ -30,6 +30,7 @@ module.exports = Object.assign({}, config, {
|
||||
'\\.(png)$': '<rootDir>/jest/identity.js',
|
||||
'\\.(svg)$': '<rootDir>/jest/unit/empty.ts',
|
||||
'\\.(jpg)$': '<rootDir>/jest/unit/empty.ts',
|
||||
'\\.(md)$': '<rootDir>/jest/unit/empty-string.ts',
|
||||
'github-markdown-css': '<rootDir>/jest/identity.js',
|
||||
// note: this section has to be on sync with webpack configuration
|
||||
'verdaccio-ui/components/(.*)': '<rootDir>/src/components/$1',
|
||||
|
1
packages/plugins/ui-theme/jest/unit/empty-string.ts
Normal file
@ -0,0 +1 @@
|
||||
export default 'empty string module';
|
@ -57,6 +57,7 @@
|
||||
"node-mocks-http": "1.11.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"react-markdown": "8.0.0",
|
||||
"react-json-view": "1.21.3",
|
||||
"remark-gfm": "3.0.1",
|
||||
"optimize-css-assets-webpack-plugin": "6.0.1",
|
||||
"ora": "5.4.1",
|
||||
@ -71,6 +72,7 @@
|
||||
"react-redux": "7.2.6",
|
||||
"redux": "4.1.2",
|
||||
"rimraf": "3.0.2",
|
||||
"raw-loader": "4.0.2",
|
||||
"msw": "0.36.5",
|
||||
"style-loader": "3.3.1",
|
||||
"stylelint": "14.6.0",
|
||||
|
@ -118,4 +118,19 @@ describe('<App />', () => {
|
||||
expect(store.getState().packages.response).toHaveLength(1);
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('footer', () => {
|
||||
test('should display the Header component', () => {
|
||||
renderWithStore(<App />, store);
|
||||
expect(screen.getByTestId('footer')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not display the Header component', () => {
|
||||
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||
showFooter: false,
|
||||
};
|
||||
renderWithStore(<App />, store);
|
||||
expect(screen.queryByTestId('footer')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,6 +7,7 @@ import Loading from 'verdaccio-ui/components/Loading';
|
||||
import StyleBaseline from 'verdaccio-ui/design-tokens/StyleBaseline';
|
||||
import loadDayJSLocale from 'verdaccio-ui/design-tokens/load-dayjs-locale';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
||||
|
||||
import '../i18n/config';
|
||||
import AppRoute, { history } from './AppRoute';
|
||||
@ -27,10 +28,11 @@ const StyledBoxContent = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
|
||||
}));
|
||||
|
||||
const App: React.FC = () => {
|
||||
const { configOptions } = useConfig();
|
||||
|
||||
useEffect(() => {
|
||||
loadDayJSLocale();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<StyleBaseline />
|
||||
@ -42,7 +44,7 @@ const App: React.FC = () => {
|
||||
<AppRoute />
|
||||
</StyledBoxContent>
|
||||
</Router>
|
||||
<Footer />
|
||||
{configOptions.showFooter && <Footer />}
|
||||
</>
|
||||
</StyledBox>
|
||||
</Suspense>
|
||||
|
@ -18,7 +18,7 @@ const Footer = () => {
|
||||
const { t } = useTranslation();
|
||||
const { configOptions } = useConfig();
|
||||
return (
|
||||
<Wrapper>
|
||||
<Wrapper data-testid="footer">
|
||||
<Inner>
|
||||
<Left>
|
||||
<Trans components={[<Love />]} i18nKey="footer.made-with-love-on" />
|
||||
|
@ -165,6 +165,7 @@ exports[`<Footer /> component should load the initial state of Footer component
|
||||
|
||||
<div
|
||||
class="emotion-0 emotion-1"
|
||||
data-testid="footer"
|
||||
>
|
||||
<div
|
||||
class="emotion-2 emotion-3"
|
||||
|
28
packages/plugins/ui-theme/src/App/Header/Contributors.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import AvatarGroup from '@mui/material/AvatarGroup';
|
||||
import Link from '@mui/material/Link';
|
||||
import React from 'react';
|
||||
|
||||
import contributors from './generated_contributors_list.json';
|
||||
|
||||
const generateImage = (id) => `https://avatars3.githubusercontent.com/u/${id}?s=120&v=4`;
|
||||
|
||||
const Contributors: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Link href={`https://verdaccio.org/contributors`} rel="noreferrer" target="_blank">
|
||||
<AvatarGroup max={18} spacing={15} total={400}>
|
||||
{contributors?.map(({ username, id }) => {
|
||||
return (
|
||||
<div key={username}>
|
||||
<Avatar alt={username} src={generateImage(id)} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</AvatarGroup>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contributors;
|
@ -80,33 +80,61 @@ describe('<Header /> component with logged in state', () => {
|
||||
expect(getByText('Login')).toBeTruthy();
|
||||
});
|
||||
|
||||
test("The question icon should open a new tab of verdaccio's website - installation doc", () => {
|
||||
const { getByTestId } = renderWithStore(
|
||||
test('should display info button', () => {
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
expect(screen.getByTestId('header--tooltip-info')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const documentationBtn = getByTestId('header--tooltip-documentation');
|
||||
expect(documentationBtn.getAttribute('href')).toBe(
|
||||
'https://verdaccio.org/docs/en/installation'
|
||||
test('should display settings button', () => {
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
expect(screen.getByTestId('header--tooltip-settings')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should display light button switch', () => {
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
expect(screen.getByTestId('header--button--light')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test.todo('should test display dark button switch');
|
||||
|
||||
test('should display search box', () => {
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
expect(screen.getByTestId('search-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should open the registrationInfo modal when clicking on the info icon', async () => {
|
||||
const { getByTestId } = renderWithStore(
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
const infoBtn = getByTestId('header--tooltip-info');
|
||||
const infoBtn = screen.getByTestId('header--tooltip-info');
|
||||
expect(infoBtn).toBeInTheDocument();
|
||||
fireEvent.click(infoBtn);
|
||||
|
||||
// wait for registrationInfo modal appearance and return the element
|
||||
const registrationInfoModal = await waitFor(() => getByTestId('registryInfo--dialog'));
|
||||
const registrationInfoModal = await waitFor(() => screen.getByTestId('registryInfo--dialog'));
|
||||
expect(registrationInfoModal).toBeTruthy();
|
||||
});
|
||||
|
||||
@ -143,7 +171,67 @@ describe('<Header /> component with logged in state', () => {
|
||||
store
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('header--button-login')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('header--button-login')).toBeNull();
|
||||
});
|
||||
|
||||
test('should hide search if is disabled', () => {
|
||||
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||
base: 'foo',
|
||||
showSearch: false,
|
||||
};
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('search-container')).toBeNull();
|
||||
});
|
||||
|
||||
test('should hide settings if is disabled', () => {
|
||||
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||
base: 'foo',
|
||||
showSettings: false,
|
||||
};
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
expect(screen.queryByTitle('header--tooltip-settings')).toBeNull();
|
||||
});
|
||||
|
||||
test('should hide info if is disabled', () => {
|
||||
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||
base: 'foo',
|
||||
showSettings: false,
|
||||
};
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
expect(screen.queryByTitle('header.registry-info')).toBeNull();
|
||||
});
|
||||
|
||||
test('should hide theme switch if is disabled', () => {
|
||||
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||
base: 'foo',
|
||||
showThemeSwitch: false,
|
||||
};
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
expect(screen.queryByTitle('header.registry-info')).toBeNull();
|
||||
});
|
||||
|
||||
test.todo('autocompletion should display suggestions according to the type value');
|
||||
|
@ -8,18 +8,16 @@ import { Dispatch, RootState } from '../../store/store';
|
||||
import HeaderInfoDialog from './HeaderInfoDialog';
|
||||
import HeaderLeft from './HeaderLeft';
|
||||
import HeaderRight from './HeaderRight';
|
||||
import HeaderSettingsDialog from './HeaderSettingsDialog';
|
||||
import LoginDialog from './LoginDialog';
|
||||
import Search from './Search';
|
||||
import { InnerMobileNavBar, InnerNavBar, MobileNavBar, NavBar } from './styles';
|
||||
|
||||
interface Props {
|
||||
withoutSearch?: boolean;
|
||||
}
|
||||
|
||||
/* eslint-disable react/jsx-no-bind*/
|
||||
const Header: React.FC<Props> = ({ withoutSearch }) => {
|
||||
const Header: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [isInfoDialogOpen, setOpenInfoDialog] = useState<boolean>(false);
|
||||
const [isSettingsDialogOpen, setSettingsDialogOpen] = useState<boolean>(false);
|
||||
const [showMobileNavBar, setShowMobileNavBar] = useState<boolean>(false);
|
||||
const [showLoginModal, setShowLoginModal] = useState<boolean>(false);
|
||||
const loginStore = useSelector((state: RootState) => state.login);
|
||||
@ -28,30 +26,35 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
|
||||
const handleLogout = () => {
|
||||
dispatch.login.logOutUser();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavBar data-testid="header" position="static">
|
||||
<InnerNavBar>
|
||||
<HeaderLeft />
|
||||
<HeaderLeft showSearch={configOptions.showSearch} />
|
||||
<HeaderRight
|
||||
hasLogin={configOptions?.login}
|
||||
onLogout={handleLogout}
|
||||
onOpenRegistryInfoDialog={() => setOpenInfoDialog(true)}
|
||||
onOpenSettingsDialog={() => setSettingsDialogOpen(true)}
|
||||
onToggleLogin={() => setShowLoginModal(!showLoginModal)}
|
||||
onToggleMobileNav={() => setShowMobileNavBar(!showMobileNavBar)}
|
||||
showInfo={configOptions.showInfo}
|
||||
showSearch={configOptions.showSearch}
|
||||
showSettings={configOptions.showSettings}
|
||||
showThemeSwitch={configOptions.showThemeSwitch}
|
||||
username={loginStore?.username}
|
||||
withoutSearch={withoutSearch}
|
||||
/>
|
||||
</InnerNavBar>
|
||||
{
|
||||
<HeaderInfoDialog
|
||||
isOpen={isInfoDialogOpen}
|
||||
onCloseDialog={() => setOpenInfoDialog(false)}
|
||||
/>
|
||||
}
|
||||
<HeaderSettingsDialog
|
||||
isOpen={isSettingsDialogOpen}
|
||||
onCloseDialog={() => setSettingsDialogOpen(false)}
|
||||
/>
|
||||
<HeaderInfoDialog
|
||||
isOpen={isInfoDialogOpen}
|
||||
onCloseDialog={() => setOpenInfoDialog(false)}
|
||||
/>
|
||||
</NavBar>
|
||||
{showMobileNavBar && !withoutSearch && (
|
||||
{showMobileNavBar && (
|
||||
<MobileNavBar>
|
||||
<InnerMobileNavBar>
|
||||
<Search />
|
||||
|
@ -1,19 +1,22 @@
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
|
||||
/* eslint-disable verdaccio/jsx-spread */
|
||||
import styled from '@emotion/styled';
|
||||
import { Theme } from '@mui/material';
|
||||
import Box from '@mui/material/Box';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import Tabs from '@mui/material/Tabs';
|
||||
import FlagsIcon from 'country-flag-icons/react/3x2';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { useSelector } from 'react-redux';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
|
||||
import { RootState } from '../../store/store';
|
||||
import LanguageSwitch from './LanguageSwitch';
|
||||
import RegistryInfoContent from './RegistryInfoContent';
|
||||
import Contributors from './Contributors';
|
||||
import RegistryInfoDialog from './RegistryInfoDialog';
|
||||
import { Support } from './Support';
|
||||
import about from './about.md';
|
||||
import license from './license.md';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
@ -43,36 +46,47 @@ function TabPanel(props) {
|
||||
);
|
||||
}
|
||||
|
||||
const TextContent = styled('div')<{ theme?: Theme }>(({ theme }) => ({
|
||||
padding: '10px 0',
|
||||
backgroundColor: theme?.palette.background.default,
|
||||
const Flags = styled('span')<{ theme?: Theme }>(() => ({
|
||||
width: '25px',
|
||||
}));
|
||||
|
||||
const HeaderInfoDialog: React.FC<Props> = ({ onCloseDialog, isOpen }) => {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
const handleChange = (_event, newValue) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
const configStore = useSelector((state: RootState) => state.configuration.config);
|
||||
const { scope, base } = configStore;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<RegistryInfoDialog onClose={onCloseDialog} open={isOpen}>
|
||||
<RegistryInfoDialog
|
||||
onClose={onCloseDialog}
|
||||
open={isOpen}
|
||||
title={t('dialog.registry-info.title')}
|
||||
>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs aria-label="basic tabs example" onChange={handleChange} value={value}>
|
||||
<Tab label={t('packageManagers.title')} {...a11yProps(0)} />
|
||||
<Tab label={t('language.title')} {...a11yProps(1)} />
|
||||
<Tab label={t('about')} {...a11yProps(0)} />
|
||||
<Tab label={t('dialog.license')} {...a11yProps(1)} />
|
||||
<Tab
|
||||
{...a11yProps(2)}
|
||||
icon={
|
||||
<Flags>
|
||||
<FlagsIcon.UA />
|
||||
</Flags>
|
||||
}
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
<TabPanel index={0} value={value}>
|
||||
<RegistryInfoContent registryUrl={base} scope={scope} />
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{about}</ReactMarkdown>
|
||||
<Contributors />
|
||||
</TabPanel>
|
||||
<TabPanel index={1} value={value}>
|
||||
<TextContent>{t('language.description')}</TextContent>
|
||||
<LanguageSwitch />
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{t('language.contribute')}</ReactMarkdown>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{license}</ReactMarkdown>
|
||||
</TabPanel>
|
||||
<TabPanel index={2} value={value}>
|
||||
<Support />
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</RegistryInfoDialog>
|
||||
|
@ -7,20 +7,20 @@ import Search from './Search';
|
||||
import { LeftSide, SearchWrapper } from './styles';
|
||||
|
||||
interface Props {
|
||||
withoutSearch?: boolean;
|
||||
showSearch?: boolean;
|
||||
}
|
||||
|
||||
const StyledLink = styled(Link)({
|
||||
marginRight: '1em',
|
||||
});
|
||||
|
||||
const HeaderLeft: React.FC<Props> = ({ withoutSearch = false }) => (
|
||||
const HeaderLeft: React.FC<Props> = ({ showSearch }) => (
|
||||
<LeftSide>
|
||||
<StyledLink to={'/'}>
|
||||
<Logo />
|
||||
</StyledLink>
|
||||
{!withoutSearch && (
|
||||
<SearchWrapper>
|
||||
{showSearch && (
|
||||
<SearchWrapper data-testid="search-container">
|
||||
<Search />
|
||||
</SearchWrapper>
|
||||
)}
|
||||
|
@ -5,27 +5,34 @@ import ThemeContext from 'verdaccio-ui/design-tokens/ThemeContext';
|
||||
|
||||
import HeaderMenu from './HeaderMenu';
|
||||
import HeaderToolTip from './HeaderToolTip';
|
||||
import { Support } from './Support';
|
||||
import { RightSide } from './styles';
|
||||
|
||||
interface Props {
|
||||
withoutSearch?: boolean;
|
||||
showSearch?: boolean;
|
||||
username?: string | null;
|
||||
hasLogin?: boolean;
|
||||
showInfo?: boolean;
|
||||
showSettings?: boolean;
|
||||
showThemeSwitch?: boolean;
|
||||
onToggleLogin: () => void;
|
||||
onOpenRegistryInfoDialog: () => void;
|
||||
onOpenSettingsDialog: () => void;
|
||||
onToggleMobileNav: () => void;
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
const HeaderRight: React.FC<Props> = ({
|
||||
withoutSearch = false,
|
||||
showSearch,
|
||||
username,
|
||||
onToggleLogin,
|
||||
hasLogin,
|
||||
showInfo,
|
||||
showSettings,
|
||||
showThemeSwitch,
|
||||
onLogout,
|
||||
onToggleMobileNav,
|
||||
onOpenRegistryInfoDialog,
|
||||
onOpenSettingsDialog,
|
||||
}) => {
|
||||
const themeContext = useContext(ThemeContext);
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||
@ -72,25 +79,35 @@ const HeaderRight: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<RightSide data-testid="header-right">
|
||||
{!withoutSearch && (
|
||||
{showSearch === true && (
|
||||
<HeaderToolTip
|
||||
onClick={onToggleMobileNav}
|
||||
title={t('search.packages')}
|
||||
tooltipIconType={'search'}
|
||||
/>
|
||||
)}
|
||||
<Support />
|
||||
<HeaderToolTip title={t('header.documentation')} tooltipIconType={'help'} />
|
||||
<HeaderToolTip
|
||||
onClick={onOpenRegistryInfoDialog}
|
||||
title={t('header.registry-info')}
|
||||
tooltipIconType={'info'}
|
||||
/>
|
||||
<HeaderToolTip
|
||||
onClick={handleToggleDarkLightMode}
|
||||
title={t('header.documentation')}
|
||||
tooltipIconType={themeContext.isDarkMode ? 'dark-mode' : 'light-mode'}
|
||||
/>
|
||||
|
||||
{showSettings === true && (
|
||||
<HeaderToolTip
|
||||
onClick={onOpenSettingsDialog}
|
||||
title={t('header.settings')}
|
||||
tooltipIconType={'settings'}
|
||||
/>
|
||||
)}
|
||||
{showInfo === true && (
|
||||
<HeaderToolTip
|
||||
onClick={onOpenRegistryInfoDialog}
|
||||
title={t('header.registry-info')}
|
||||
tooltipIconType={'info'}
|
||||
/>
|
||||
)}
|
||||
{showThemeSwitch === true && (
|
||||
<HeaderToolTip
|
||||
onClick={handleToggleDarkLightMode}
|
||||
title={t('header.documentation')}
|
||||
tooltipIconType={themeContext.isDarkMode ? 'dark-mode' : 'light-mode'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!hideLoginSection && (
|
||||
<>
|
||||
|
@ -0,0 +1,82 @@
|
||||
/* eslint-disable verdaccio/jsx-spread */
|
||||
import styled from '@emotion/styled';
|
||||
import Box from '@mui/material/Box';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import Tabs from '@mui/material/Tabs';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { useSelector } from 'react-redux';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
|
||||
import { RootState } from '../../store/store';
|
||||
import LanguageSwitch from './LanguageSwitch';
|
||||
import RegistryInfoContent from './RegistryInfoContent';
|
||||
import RegistryInfoDialog from './RegistryInfoDialog';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onCloseDialog: () => void;
|
||||
}
|
||||
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
function TabPanel(props) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
role="tabpanel"
|
||||
{...other}
|
||||
>
|
||||
{value === index && <Box sx={{ paddingTop: 3 }}>{children}</Box>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const TextContent = styled('div')<{ theme?: Theme }>(({ theme }) => ({
|
||||
padding: '10px 0',
|
||||
backgroundColor: theme?.palette.background.default,
|
||||
}));
|
||||
|
||||
const HeaderSettingsDialog: React.FC<Props> = ({ onCloseDialog, isOpen }) => {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
const handleChange = (_event, newValue) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
const configStore = useSelector((state: RootState) => state.configuration.config);
|
||||
const { scope, base } = configStore;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<RegistryInfoDialog onClose={onCloseDialog} open={isOpen} title={t('dialog.settings.title')}>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs aria-label="basic tabs example" onChange={handleChange} value={value}>
|
||||
<Tab label={t('packageManagers.title')} {...a11yProps(0)} />
|
||||
<Tab label={t('language.title')} {...a11yProps(1)} />
|
||||
</Tabs>
|
||||
</Box>
|
||||
<TabPanel index={0} value={value}>
|
||||
<RegistryInfoContent registryUrl={base} scope={scope} />
|
||||
</TabPanel>
|
||||
<TabPanel index={1} value={value}>
|
||||
<TextContent>{t('language.description')}</TextContent>
|
||||
<LanguageSwitch />
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{t('language.contribute')}</ReactMarkdown>
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</RegistryInfoDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderSettingsDialog;
|
@ -1,14 +1,13 @@
|
||||
import Help from '@mui/icons-material/Help';
|
||||
import Info from '@mui/icons-material/Info';
|
||||
import NightsStay from '@mui/icons-material/NightsStay';
|
||||
import Search from '@mui/icons-material/Search';
|
||||
import Settings from '@mui/icons-material/Settings';
|
||||
import WbSunny from '@mui/icons-material/WbSunny';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { IconSearchButton, StyledLink } from './styles';
|
||||
import { IconSearchButton, InfoButton, SettingsButtom, SwitchThemeButton } from './styles';
|
||||
|
||||
export type TooltipIconType = 'search' | 'help' | 'info' | 'dark-mode' | 'light-mode';
|
||||
export type TooltipIconType = 'search' | 'info' | 'dark-mode' | 'light-mode' | 'settings';
|
||||
interface Props {
|
||||
tooltipIconType: TooltipIconType;
|
||||
onClick?: () => void;
|
||||
@ -23,21 +22,9 @@ const HeaderToolTipIcon = forwardRef<HeaderToolTipIconRef, Props>(function Heade
|
||||
ref
|
||||
) {
|
||||
switch (tooltipIconType) {
|
||||
case 'help':
|
||||
return (
|
||||
<StyledLink
|
||||
data-testid={'header--tooltip-documentation'}
|
||||
external={true}
|
||||
to={'https://verdaccio.org/docs/en/installation'}
|
||||
>
|
||||
<IconButton color={'inherit'} size="large">
|
||||
<Help />
|
||||
</IconButton>
|
||||
</StyledLink>
|
||||
);
|
||||
case 'info':
|
||||
return (
|
||||
<IconButton
|
||||
<InfoButton
|
||||
color="inherit"
|
||||
data-testid={'header--tooltip-info'}
|
||||
id="header--button-registryInfo"
|
||||
@ -46,7 +33,20 @@ const HeaderToolTipIcon = forwardRef<HeaderToolTipIconRef, Props>(function Heade
|
||||
size="large"
|
||||
>
|
||||
<Info />
|
||||
</IconButton>
|
||||
</InfoButton>
|
||||
);
|
||||
case 'settings':
|
||||
return (
|
||||
<SettingsButtom
|
||||
color="inherit"
|
||||
data-testid={'header--tooltip-settings'}
|
||||
id="header--button-settings"
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
size="large"
|
||||
>
|
||||
<Settings />
|
||||
</SettingsButtom>
|
||||
);
|
||||
case 'search':
|
||||
return (
|
||||
@ -56,16 +56,28 @@ const HeaderToolTipIcon = forwardRef<HeaderToolTipIconRef, Props>(function Heade
|
||||
);
|
||||
case 'dark-mode':
|
||||
return (
|
||||
<IconButton color="inherit" onClick={onClick} ref={ref} size="large">
|
||||
<SwitchThemeButton
|
||||
color="inherit"
|
||||
data-testid={'header--button--dark'}
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
size="large"
|
||||
>
|
||||
<NightsStay />
|
||||
</IconButton>
|
||||
</SwitchThemeButton>
|
||||
);
|
||||
|
||||
case 'light-mode':
|
||||
return (
|
||||
<IconButton color="inherit" onClick={onClick} ref={ref} size="large">
|
||||
<SwitchThemeButton
|
||||
color="inherit"
|
||||
data-testid={'header--button--light'}
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
size="large"
|
||||
>
|
||||
<WbSunny />
|
||||
</IconButton>
|
||||
</SwitchThemeButton>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
|
@ -7,16 +7,17 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Content, Title } from './styles';
|
||||
import { Props } from './types';
|
||||
|
||||
const RegistryInfoDialog: React.FC<Props> = ({ open = false, children, onClose }) => {
|
||||
const RegistryInfoDialog: React.FC<Props> = ({ open = false, children, onClose, title = '' }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Dialog
|
||||
data-testid={'registryInfo--dialog'}
|
||||
id="registryInfo--dialog-container"
|
||||
maxWidth="sm"
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
>
|
||||
<Title>{t('dialog.registry-info.title')}</Title>
|
||||
<Title>{title}</Title>
|
||||
<Content>{children}</Content>
|
||||
<DialogActions>
|
||||
<Button color="inherit" id="registryInfo--dialog-close" onClick={onClose}>
|
||||
|
@ -3,5 +3,6 @@ import { ReactNode } from 'react';
|
||||
export interface Props {
|
||||
children: ReactNode;
|
||||
open: boolean;
|
||||
title: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
@ -3,27 +3,11 @@
|
||||
/* eslint-disable react/jsx-max-depth */
|
||||
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
import styled from '@emotion/styled';
|
||||
import { Dialog, Link, Theme } from '@mui/material';
|
||||
import Box from '@mui/material/Box';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import { Link } from '@mui/material';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import FlagsIcon from 'country-flag-icons/react/3x2';
|
||||
import React from 'react';
|
||||
|
||||
import flag from './uk.jpg';
|
||||
|
||||
const style = {
|
||||
p: 4,
|
||||
};
|
||||
|
||||
const Flags = styled('span')<{ theme?: Theme }>(() => ({
|
||||
width: '25px',
|
||||
}));
|
||||
|
||||
const title = 'Support people affected by the war in Ukraine';
|
||||
|
||||
const links = [
|
||||
@ -54,10 +38,6 @@ const links = [
|
||||
];
|
||||
|
||||
const Support = () => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const handleOpen = () => setOpen(true);
|
||||
const handleClose = () => setOpen(false);
|
||||
|
||||
const linkElements = links.map((link) => (
|
||||
<li key={link.text}>
|
||||
<Link href={link.href} target="_blank">
|
||||
@ -68,53 +48,32 @@ const Support = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={title}>
|
||||
<IconButton color="inherit" onClick={handleOpen} size="large">
|
||||
<Flags>
|
||||
<FlagsIcon.UA />
|
||||
</Flags>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
aria-describedby="modal-modal-description"
|
||||
aria-labelledby="modal-modal-title"
|
||||
maxWidth="md"
|
||||
onClose={handleClose}
|
||||
open={open}
|
||||
>
|
||||
<Box sx={style}>
|
||||
<Grid container={true} spacing={2}>
|
||||
<Grid item={true} xs={12}>
|
||||
<Typography component="h2" variant="h6">
|
||||
{title}
|
||||
</Typography>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item={true} lg={4} xs={12}>
|
||||
<img alt={title} height="150" src={flag} />
|
||||
</Grid>
|
||||
<Grid item={true} lg={8} xs={12}>
|
||||
<span style={{ fontStyle: 'italic', fontSize: '0.75rem' }}>
|
||||
<Typography>
|
||||
{`Hi, this is a message that I've composed to call your attention to ask
|
||||
<Grid container={true} spacing={2}>
|
||||
<Grid item={true} xs={12}>
|
||||
<Typography component="h2" variant="h6">
|
||||
{title}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item={true} lg={12} xs={12}>
|
||||
<span style={{ fontStyle: 'italic', fontSize: '0.75rem' }}>
|
||||
<Typography>
|
||||
{`Hi, this is a message that I've composed to call your attention to ask
|
||||
for humanitarian support for more than 44 million Ukrainians that are having
|
||||
a hard time suffering for a horrible and unjustified war. It would be great if you
|
||||
decide today to make a difference and help others. You could help by donating
|
||||
to very well-known humanitarian organizations, helping in your local
|
||||
area with food, clothes, donate blood, toys for kids, or your own time. Any help is very welcome.`}
|
||||
</Typography>
|
||||
</span>
|
||||
<ul style={{ padding: '10px 0' }}>{linkElements}</ul>
|
||||
<div>
|
||||
<Typography variant="div">{`Spread the voice, make the difference today.`}</Typography>
|
||||
</div>
|
||||
<div style={{ padding: '10px 0', fontWeight: 600 }}>
|
||||
<Typography variant="div">{`Att: Verdaccio Lead Mantainer, Juan P.`}</Typography>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Dialog>
|
||||
</Typography>
|
||||
</span>
|
||||
<ul style={{ padding: '10px 0' }}>{linkElements}</ul>
|
||||
<div>
|
||||
<Typography variant="div">{`Spread the voice, make the difference today.`}</Typography>
|
||||
</div>
|
||||
<div style={{ padding: '10px 0', fontWeight: 600 }}>
|
||||
<Typography variant="div">{`Att: Verdaccio Lead Mantainer, Juan P.`}</Typography>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Before Width: | Height: | Size: 9.2 KiB |
22
packages/plugins/ui-theme/src/App/Header/about.md
Normal file
@ -0,0 +1,22 @@
|
||||
Verdaccio is an open source lightweight private proxy Node.js registry,
|
||||
you can [learn more about the project in our blog](https://verdaccio.org/blog/2019/02/08/the-crazy-story-of-verdaccio).
|
||||
|
||||
#### Useful links
|
||||
|
||||
- [Installation](https://verdaccio.org/docs/en/installation)
|
||||
- [Translate](https://translate.verdaccio.org/)
|
||||
- [Best practices](https://verdaccio.org/docs/best)
|
||||
- [Security policy](https://github.com/verdaccio/verdaccio/security/policy)
|
||||
- [Sponsor via open collective](https://opencollective.com/verdaccio)
|
||||
- [Sponsor via GitHub](https://github.com/sponsors/verdaccio)
|
||||
|
||||
#### How to connect
|
||||
|
||||
- [Discussions](https://github.com/verdaccio/verdaccio/discussions)
|
||||
- [YouTube channel](https://www.youtube.com/channel/UC5i20v6o7lSjXzAHOvatt0w)
|
||||
- [Discord chat](https://discord.gg/hv42jfs)
|
||||
- [Twitter community](https://twitter.com/i/communities/1502550839499579393)
|
||||
|
||||
https://www.verdaccio.org
|
||||
|
||||
#### Meet the contributors
|
21
packages/plugins/ui-theme/src/App/Header/license.md
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Verdaccio contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -42,9 +42,10 @@ export const InnerMobileNavBar = styled('div')<{ theme?: Theme }>((props) => ({
|
||||
margin: '0 10px 0 0',
|
||||
}));
|
||||
|
||||
export const IconSearchButton = styled(IconButton)({
|
||||
display: 'block',
|
||||
});
|
||||
export const IconSearchButton = styled(IconButton)({});
|
||||
export const InfoButton = styled(IconButton)({});
|
||||
export const SwitchThemeButton = styled(IconButton)({});
|
||||
export const SettingsButtom = styled(IconButton)({});
|
||||
|
||||
export const SearchWrapper = styled('div')({
|
||||
display: 'none',
|
||||
@ -58,13 +59,21 @@ export const NavBar = styled(AppBar)<{ theme?: Theme }>(({ theme }) => ({
|
||||
minHeight: 60,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
[`@media (max-width: ${theme?.breakPoints.xsmall}px)`]: css`
|
||||
${InfoButton} {
|
||||
display: none;
|
||||
}
|
||||
${SwitchThemeButton} {
|
||||
display: none;
|
||||
}
|
||||
${SettingsButtom} {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
[`@media (min-width: ${theme?.breakPoints.medium}px)`]: css`
|
||||
${SearchWrapper} {
|
||||
display: flex;
|
||||
}
|
||||
${IconSearchButton} {
|
||||
display: none;
|
||||
}
|
||||
${MobileNavBar} {
|
||||
display: none;
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
import React from 'react';
|
||||
import { cleanup, renderWithStore, screen } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||
import {
|
||||
cleanup,
|
||||
fireEvent,
|
||||
renderWithStore,
|
||||
screen,
|
||||
} from 'verdaccio-ui/utils/test-react-testing-library';
|
||||
|
||||
import { DetailContext, DetailContextProps } from '../../pages/Version';
|
||||
import { store } from '../../store/store';
|
||||
import ActionBar from './ActionBar';
|
||||
import ActionBar, { Props } from './ActionBar';
|
||||
|
||||
const detailContextValue: DetailContextProps = {
|
||||
packageName: 'foo',
|
||||
@ -29,11 +34,12 @@ const detailContextValue: DetailContextProps = {
|
||||
},
|
||||
};
|
||||
|
||||
const ComponentToBeRendered: React.FC<{ contextValue: DetailContextProps }> = ({
|
||||
const ComponentToBeRendered: React.FC<{ contextValue: DetailContextProps; props?: Props }> = ({
|
||||
contextValue,
|
||||
props,
|
||||
}) => (
|
||||
<DetailContext.Provider value={contextValue}>
|
||||
<ActionBar />
|
||||
<ActionBar {...props} />
|
||||
</DetailContext.Provider>
|
||||
);
|
||||
|
||||
@ -76,6 +82,42 @@ describe('<ActionBar /> component', () => {
|
||||
expect(screen.getByLabelText('Download tarball')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('when there is a button to raw manifest', () => {
|
||||
renderWithStore(
|
||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} props={{ showRaw: true }} />,
|
||||
store
|
||||
);
|
||||
expect(screen.getByLabelText('Raw Manifest')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('when click button to raw manifest open a dialog with viewver', () => {
|
||||
renderWithStore(
|
||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} props={{ showRaw: true }} />,
|
||||
store
|
||||
);
|
||||
fireEvent.click(screen.getByLabelText('Raw Manifest'));
|
||||
expect(screen.getByTestId('raw-viewver-dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not display download tarball button', () => {
|
||||
renderWithStore(
|
||||
<ComponentToBeRendered
|
||||
contextValue={{ ...detailContextValue }}
|
||||
props={{ showDownloadTarball: false }}
|
||||
/>,
|
||||
store
|
||||
);
|
||||
expect(screen.queryByLabelText('Download tarball')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should not display show raw button', () => {
|
||||
renderWithStore(
|
||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} props={{ showRaw: false }} />,
|
||||
store
|
||||
);
|
||||
expect(screen.queryByLabelText('Raw Manifest')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('when there is a button to open an issue', () => {
|
||||
renderWithStore(<ComponentToBeRendered contextValue={{ ...detailContextValue }} />, store);
|
||||
expect(screen.getByLabelText('Open an issue')).toBeTruthy();
|
||||
|
@ -1,12 +1,19 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { isURL } from 'verdaccio-ui/utils/url';
|
||||
|
||||
import { DetailContext } from '../../pages/Version';
|
||||
import RawViewer from '../RawViewer';
|
||||
import ActionBarAction, { ActionBarActionProps } from './ActionBarAction';
|
||||
|
||||
export type Props = {
|
||||
showRaw?: boolean;
|
||||
showDownloadTarball?: boolean;
|
||||
};
|
||||
|
||||
/* eslint-disable verdaccio/jsx-spread */
|
||||
const ActionBar: React.FC = () => {
|
||||
const ActionBar: React.FC<Props> = ({ showRaw, showDownloadTarball = true }) => {
|
||||
const [isRawViewerOpen, setIsRawViewerOpen] = useState(false);
|
||||
const detailContext = React.useContext(DetailContext);
|
||||
|
||||
const { packageMeta } = detailContext;
|
||||
@ -27,15 +34,27 @@ const ActionBar: React.FC = () => {
|
||||
actions.push({ type: 'OPEN_AN_ISSUE', link: bugs.url });
|
||||
}
|
||||
|
||||
if (dist?.tarball && isURL(dist.tarball)) {
|
||||
if (dist?.tarball && isURL(dist.tarball) && showDownloadTarball) {
|
||||
actions.push({ type: 'DOWNLOAD_TARBALL', link: dist.tarball });
|
||||
}
|
||||
|
||||
if (showRaw) {
|
||||
actions.push({ type: 'RAW_DATA', action: () => setIsRawViewerOpen(true) });
|
||||
}
|
||||
|
||||
return (
|
||||
<Box alignItems="center" display="flex" marginBottom="14px">
|
||||
{actions.map((action) => (
|
||||
<ActionBarAction key={action.link} {...action} />
|
||||
<ActionBarAction key={action.type} {...action} />
|
||||
))}
|
||||
{isRawViewerOpen && (
|
||||
<RawViewer
|
||||
isOpen={isRawViewerOpen}
|
||||
onClose={() => {
|
||||
setIsRawViewerOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import styled from '@emotion/styled';
|
||||
import BugReportIcon from '@mui/icons-material/BugReport';
|
||||
import DownloadIcon from '@mui/icons-material/CloudDownload';
|
||||
import HomeIcon from '@mui/icons-material/Home';
|
||||
import RawOnIcon from '@mui/icons-material/RawOn';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -23,15 +24,16 @@ export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(({ theme }) =
|
||||
},
|
||||
}));
|
||||
|
||||
type ActionType = 'VISIT_HOMEPAGE' | 'OPEN_AN_ISSUE' | 'DOWNLOAD_TARBALL';
|
||||
type ActionType = 'VISIT_HOMEPAGE' | 'OPEN_AN_ISSUE' | 'DOWNLOAD_TARBALL' | 'RAW_DATA';
|
||||
|
||||
export interface ActionBarActionProps {
|
||||
type: ActionType;
|
||||
link: string;
|
||||
link?: string;
|
||||
action?: () => void;
|
||||
}
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
|
||||
const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link, action }) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch<Dispatch>();
|
||||
|
||||
@ -42,7 +44,7 @@ const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
|
||||
switch (type) {
|
||||
case 'VISIT_HOMEPAGE':
|
||||
return (
|
||||
<Tooltip title={t('action-bar-action.visit-home-page')}>
|
||||
<Tooltip title={t('action-bar-action.visit-home-page') as string}>
|
||||
<Link external={true} to={link}>
|
||||
<Fab size="small">
|
||||
<HomeIcon />
|
||||
@ -52,7 +54,7 @@ const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
|
||||
);
|
||||
case 'OPEN_AN_ISSUE':
|
||||
return (
|
||||
<Tooltip title={t('action-bar-action.open-an-issue')}>
|
||||
<Tooltip title={t('action-bar-action.open-an-issue') as string}>
|
||||
<Link external={true} to={link}>
|
||||
<Fab size="small">
|
||||
<BugReportIcon />
|
||||
@ -62,12 +64,20 @@ const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
|
||||
);
|
||||
case 'DOWNLOAD_TARBALL':
|
||||
return (
|
||||
<Tooltip title={t('action-bar-action.download-tarball')}>
|
||||
<Tooltip title={t('action-bar-action.download-tarball') as string}>
|
||||
<Fab data-testid="download-tarball-btn" onClick={handleDownload} size="small">
|
||||
<DownloadIcon />
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
);
|
||||
case 'RAW_DATA':
|
||||
return (
|
||||
<Tooltip title={t('action-bar-action.raw') as string}>
|
||||
<Fab data-testid="raw-btn" onClick={action} size="small">
|
||||
<RawOnIcon />
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3,8 +3,8 @@ import React from 'react';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
||||
|
||||
import blackAndWithLogo from './img/logo-uk.svg';
|
||||
import defaultLogo from './img/logo-uk.svg';
|
||||
import blackAndWithLogo from './img/logo-black-and-white.svg';
|
||||
import defaultLogo from './img/logo.svg';
|
||||
|
||||
const sizes = {
|
||||
'x-small': '30px',
|
||||
|
@ -1,38 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100" height="100" rx="37" fill="#F7F8F6"/>
|
||||
<g filter="url(#filter0_d_0_18)">
|
||||
<mask id="path-2-inside-1_0_18" fill="white">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70 46.6L54.8 77H46L22.4 29.8L37.6 29.8L50.4 55.4L54.8 46.6H70Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70 46.6L54.8 77H46L22.4 29.8L37.6 29.8L50.4 55.4L54.8 46.6H70Z" fill="#005EB8"/>
|
||||
<path d="M70 46.6L72.1466 47.6733L73.8833 44.2H70V46.6ZM54.8 77V79.4H56.2833L56.9466 78.0733L54.8 77ZM46 77L43.8534 78.0733L44.5167 79.4H46V77ZM22.4 29.8V27.4H18.5167L20.2534 30.8733L22.4 29.8ZM37.6 29.8L39.7466 28.7267L39.0833 27.4H37.6V29.8ZM50.4 55.4L48.2534 56.4733L50.4 60.7666L52.5466 56.4733L50.4 55.4ZM54.8 46.6V44.2H53.3167L52.6534 45.5267L54.8 46.6ZM67.8534 45.5267L52.6534 75.9267L56.9466 78.0733L72.1466 47.6733L67.8534 45.5267ZM54.8 74.6H46V79.4H54.8V74.6ZM48.1466 75.9267L24.5466 28.7267L20.2534 30.8733L43.8534 78.0733L48.1466 75.9267ZM22.4 32.2L37.6 32.2V27.4L22.4 27.4V32.2ZM35.4534 30.8733L48.2534 56.4733L52.5466 54.3267L39.7466 28.7267L35.4534 30.8733ZM52.5466 56.4733L56.9466 47.6733L52.6534 45.5267L48.2534 54.3267L52.5466 56.4733ZM54.8 49H70V44.2H54.8V49Z" fill="#005EB8" mask="url(#path-2-inside-1_0_18)"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d_0_18)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M72.8 41H57.6L63.2 29.8L78.4 29.8L72.8 41Z" fill="#005EB8"/>
|
||||
<path d="M76.4584 31L72.0584 39.8H59.5416L63.9416 31H76.4584Z" stroke="#005EB8" stroke-width="2.4"/>
|
||||
</g>
|
||||
<path d="M56.6351 70.688L54.0607 75.8H46.7416L24.3416 31H36.8573L56.6351 70.688Z" fill="#FFD101" stroke="#FFD101" stroke-width="2.4"/>
|
||||
<path d="M59.6 31H74.821" stroke="#005EB8" stroke-width="2.4" stroke-linecap="square"/>
|
||||
<path d="M55.6 35H70.821" stroke="#005EB8" stroke-width="2.4" stroke-linecap="square"/>
|
||||
<path d="M51.6 39.8H66.821" stroke="#005EB8" stroke-width="2.4" stroke-linecap="square"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_0_18" x="17.4" y="28.8" width="57.6" height="57.2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="2.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0906646 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_0_18"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_0_18" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d_0_18" x="52.6" y="28.8" width="30.8" height="21.2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="2.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0906646 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_0_18"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_0_18" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.2 KiB |
@ -0,0 +1,76 @@
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactJson from 'react-json-view';
|
||||
|
||||
import { DetailContext } from '../../pages/Version';
|
||||
|
||||
export interface ViewerTitleProps {
|
||||
id: string;
|
||||
children?: React.ReactNode;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const ViewerTitle = (props: ViewerTitleProps) => {
|
||||
const { children, onClose, ...other } = props;
|
||||
|
||||
return (
|
||||
<DialogTitle sx={{ m: 0, p: 2 }} {...other} data-testid="raw-viewver-dialog">
|
||||
{children}
|
||||
{onClose ? (
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: (theme) => theme.palette.grey[500],
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</DialogTitle>
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
/* eslint-disable verdaccio/jsx-spread */
|
||||
const RawViewer: React.FC<Props> = ({ isOpen = false, onClose }) => {
|
||||
const detailContext = React.useContext(DetailContext);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { packageMeta } = detailContext;
|
||||
return (
|
||||
<Dialog
|
||||
data-testid={'rawViewer--dialog'}
|
||||
id="raw-viewer--dialog-container"
|
||||
fullScreen={true}
|
||||
open={isOpen}
|
||||
>
|
||||
<ViewerTitle id="viewer-title" onClose={onClose}>
|
||||
{t('action-bar-action.raw')}
|
||||
</ViewerTitle>
|
||||
<DialogContent>
|
||||
<ReactJson
|
||||
src={packageMeta as any}
|
||||
collapsed={true}
|
||||
collapseStringsAfterLength={40}
|
||||
groupArraysAfterLength={10}
|
||||
enableClipboard={false}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default RawViewer;
|
@ -0,0 +1 @@
|
||||
export { default } from './RawViewer';
|
@ -13,15 +13,23 @@ declare module '@mui/styles/defaultTheme' {
|
||||
interface DefaultTheme extends Theme {}
|
||||
}
|
||||
|
||||
const ThemeProviderWrapper: React.FC = ({ children }) => {
|
||||
function getDarkModeDefault(darkModeConfig) {
|
||||
const prefersDarkMode = window.matchMedia?.('(prefers-color-scheme:dark)').matches;
|
||||
const isDarkModeDefault = window?.__VERDACCIO_BASENAME_UI_OPTIONS?.darkMode || prefersDarkMode;
|
||||
if (typeof darkModeConfig === 'boolean') {
|
||||
return darkModeConfig;
|
||||
} else {
|
||||
return prefersDarkMode;
|
||||
}
|
||||
}
|
||||
|
||||
const ThemeProviderWrapper: React.FC = ({ children }) => {
|
||||
const currentLanguage = i18next.languages?.[0];
|
||||
const { configOptions } = useConfig();
|
||||
|
||||
const [isDarkMode, setIsDarkMode] = useLocalStorage('darkMode', !!isDarkModeDefault);
|
||||
const isDarkModeDefault = getDarkModeDefault(configOptions.darkMode);
|
||||
const isSwitchThemeEnabled = configOptions.showThemeSwitch;
|
||||
const [isDarkModeStorage, setIsDarkMode] = useLocalStorage('darkMode', isDarkModeDefault);
|
||||
const [language, setLanguage] = useLocalStorage('language', currentLanguage);
|
||||
|
||||
const isDarkMode = isSwitchThemeEnabled === true ? isDarkModeStorage : isDarkModeDefault;
|
||||
const themeMode: ThemeMode = isDarkMode ? 'dark' : 'light';
|
||||
|
||||
const changeLanguage = useCallback(async () => {
|
||||
|
@ -73,6 +73,7 @@ const fontWeight = {
|
||||
export type FontWeight = keyof typeof fontWeight;
|
||||
|
||||
export const breakPoints = {
|
||||
xsmall: 400,
|
||||
small: 576,
|
||||
medium: 768,
|
||||
large: 1024,
|
||||
|
@ -5,17 +5,25 @@
|
||||
"action-bar-action": {
|
||||
"visit-home-page": "Visit homepage",
|
||||
"open-an-issue": "Open an issue",
|
||||
"download-tarball": "Download tarball"
|
||||
"download-tarball": "Download tarball",
|
||||
"raw": "Raw Manifest"
|
||||
},
|
||||
"dialog": {
|
||||
"registry-info": {
|
||||
"title": "Configuration Details"
|
||||
}
|
||||
"title": "Information"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configuration"
|
||||
},
|
||||
"license": "License",
|
||||
"totalContributors": "Total contributors"
|
||||
},
|
||||
"header": {
|
||||
"documentation": "Documentation",
|
||||
"registry-info": "Registry Information",
|
||||
"registry-info-link": "Learn more",
|
||||
"settings": "Settings",
|
||||
"help": "Help",
|
||||
"registry-no-conf": "No configurations available",
|
||||
"greetings": "Hi "
|
||||
},
|
||||
@ -152,13 +160,14 @@
|
||||
"portuguese": "Portuguese",
|
||||
"spanish": "Spanish",
|
||||
"german": "German",
|
||||
"chinese": "Chinese",
|
||||
"chineseTraditional": "Chinese (Traditional)",
|
||||
"italian": "Italian",
|
||||
"french": "French",
|
||||
"russian": "Russian",
|
||||
"turkish": "Turkish",
|
||||
"ukraine": "Ukraine",
|
||||
"khmer": "Khmer"
|
||||
"khmer": "Khmer",
|
||||
"chinese": "Chinese Simplified",
|
||||
"chineseTraditional": "Chinese Traditional"
|
||||
},
|
||||
"flag": {
|
||||
"austria": "Austria",
|
||||
|
@ -15,13 +15,14 @@ export const listLanguages: LanguageConfiguration[] = [
|
||||
{ lng: 'pt-BR', icon: Flags.BR, menuKey: 'lng.portuguese' },
|
||||
{ lng: 'es-ES', icon: Flags.ES, menuKey: 'lng.spanish' },
|
||||
{ lng: 'de-DE', icon: Flags.DE, menuKey: 'lng.german' },
|
||||
{ lng: 'it-IT', icon: Flags.IT, menuKey: 'lng.italian' },
|
||||
{ lng: 'fr-FR', icon: Flags.FR, menuKey: 'lng.french' },
|
||||
{ lng: 'zh-CN', icon: Flags.CN, menuKey: 'lng.chinese' },
|
||||
{ lng: 'ja-JP', icon: Flags.JP, menuKey: 'lng.japanese' },
|
||||
{ lng: 'ru-RU', icon: Flags.RU, menuKey: 'lng.russian' },
|
||||
{ lng: 'tr-TR', icon: Flags.TR, menuKey: 'lng.turkish' },
|
||||
{ lng: 'uk-UA', icon: Flags.UA, menuKey: 'lng.ukraine' },
|
||||
{ lng: 'km-KH', icon: Flags.KH, menuKey: 'lng.khme' },
|
||||
{ lng: 'zh-CN', icon: Flags.CN, menuKey: 'lng.chinese' },
|
||||
{ lng: 'zh-TW', icon: Flags.TW, menuKey: 'lng.chineseTraditional' },
|
||||
];
|
||||
|
||||
|
@ -5,6 +5,7 @@ import ActionBar from 'verdaccio-ui/components/ActionBar';
|
||||
import Author from 'verdaccio-ui/components/Author';
|
||||
import Paper from 'verdaccio-ui/components/Paper';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
||||
|
||||
import { DetailContext } from '..';
|
||||
import loadable from '../../../App/utils/loadable';
|
||||
@ -30,6 +31,7 @@ const getModuleType = (manifest: PackageMetaInterface) => {
|
||||
const DetailSidebar: React.FC = () => {
|
||||
const detailContext = useContext(DetailContext);
|
||||
const { packageMeta, packageName, packageVersion } = detailContext;
|
||||
const { configOptions } = useConfig();
|
||||
|
||||
if (!packageMeta || !packageName) {
|
||||
return null;
|
||||
@ -45,7 +47,10 @@ const DetailSidebar: React.FC = () => {
|
||||
packageName={packageName}
|
||||
version={packageVersion || packageMeta.latest.version}
|
||||
/>
|
||||
<ActionBar />
|
||||
<ActionBar
|
||||
showDownloadTarball={configOptions.showDownloadTarball}
|
||||
showRaw={configOptions.showRaw}
|
||||
/>
|
||||
<Install />
|
||||
<DetailSidebarFundButton />
|
||||
<Repository />
|
||||
|
@ -24,7 +24,6 @@ const Install: React.FC = () => {
|
||||
if (!packageMeta || !packageName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasNpm = configOptions?.pkgManagers?.includes('npm');
|
||||
const hasYarn = configOptions?.pkgManagers?.includes('yarn');
|
||||
const hasPnpm = configOptions?.pkgManagers?.includes('pnpm') ?? true;
|
||||
|
@ -55,6 +55,7 @@ export interface PackageInterface {
|
||||
homepage?: string;
|
||||
bugs?: Bugs;
|
||||
dist?: Dist;
|
||||
showDownload?: boolean;
|
||||
}
|
||||
|
||||
const Package: React.FC<PackageInterface> = ({
|
||||
@ -67,6 +68,7 @@ const Package: React.FC<PackageInterface> = ({
|
||||
license,
|
||||
name: packageName,
|
||||
time,
|
||||
showDownload = true,
|
||||
version,
|
||||
}) => {
|
||||
const config = useSelector((state: RootState) => state.configuration.config);
|
||||
@ -189,7 +191,7 @@ const Package: React.FC<PackageInterface> = ({
|
||||
>
|
||||
{renderHomePageLink()}
|
||||
{renderBugsLink()}
|
||||
{renderDownloadLink()}
|
||||
{showDownload && renderDownloadLink()}
|
||||
</GridRightAligned>
|
||||
</Grid>
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
|
||||
import { CellMeasurer, CellMeasurerCache } from 'react-virtualized/dist/commonjs/CellMeasurer';
|
||||
import { List, ListRowProps } from 'react-virtualized/dist/commonjs/List';
|
||||
import { WindowScroller } from 'react-virtualized/dist/commonjs/WindowScroller';
|
||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
||||
import { formatLicense } from 'verdaccio-ui/utils/package';
|
||||
|
||||
import Help from './Help';
|
||||
@ -20,6 +21,7 @@ const cache = new CellMeasurerCache({
|
||||
|
||||
/* eslint-disable verdaccio/jsx-no-style */
|
||||
const PackageList: React.FC<Props> = ({ packages }) => {
|
||||
const { configOptions } = useConfig();
|
||||
const renderRow = ({ index, key, parent, style }: ListRowProps) => {
|
||||
const { name, version, description, time, keywords, dist, homepage, bugs, author, license } =
|
||||
packages[index];
|
||||
@ -38,6 +40,7 @@ const PackageList: React.FC<Props> = ({ packages }) => {
|
||||
keywords={keywords}
|
||||
license={formattedLicense}
|
||||
name={name}
|
||||
showDownload={configOptions.showDownloadTarball}
|
||||
time={time}
|
||||
version={version}
|
||||
/>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import isNil from 'lodash/isNil';
|
||||
import merge from 'lodash/merge';
|
||||
import React, { FunctionComponent, createContext, useContext, useMemo, useState } from 'react';
|
||||
import { PRIMARY_COLOR } from 'verdaccio-ui/utils/colors';
|
||||
|
||||
@ -12,14 +13,21 @@ type ConfigProviderProps = {
|
||||
|
||||
const defaultValues: ConfigProviderProps = {
|
||||
configOptions: {
|
||||
// note: dark mode set as undefined by design
|
||||
primaryColor: PRIMARY_COLOR,
|
||||
darkMode: false,
|
||||
pkgManagers: ['yarn', 'pnpm', 'npm'],
|
||||
scope: '',
|
||||
base: '',
|
||||
flags: {},
|
||||
login: true,
|
||||
url_prefix: '',
|
||||
showInfo: true,
|
||||
showSettings: true,
|
||||
showThemeSwitch: true,
|
||||
showFooter: true,
|
||||
showSearch: true,
|
||||
showRaw: true,
|
||||
showDownloadTarball: true,
|
||||
title: 'Verdaccio',
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
@ -27,7 +35,15 @@ const defaultValues: ConfigProviderProps = {
|
||||
};
|
||||
|
||||
function getConfiguration() {
|
||||
const uiConfiguration = window?.__VERDACCIO_BASENAME_UI_OPTIONS ?? defaultValues.configOptions;
|
||||
const uiConfiguration = merge(
|
||||
defaultValues.configOptions,
|
||||
window?.__VERDACCIO_BASENAME_UI_OPTIONS
|
||||
);
|
||||
|
||||
if (window?.__VERDACCIO_BASENAME_UI_OPTIONS.pkgManagers) {
|
||||
uiConfiguration.pkgManagers = window?.__VERDACCIO_BASENAME_UI_OPTIONS.pkgManagers;
|
||||
}
|
||||
|
||||
if (isNil(uiConfiguration.primaryColor) || isEmpty(uiConfiguration.primaryColor)) {
|
||||
uiConfiguration.primaryColor = PRIMARY_COLOR;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -2,7 +2,8 @@ web:
|
||||
title: Verdaccio Local Dev
|
||||
sort_packages: asc
|
||||
primary_color: #CCC
|
||||
login: true
|
||||
# showRaw: true
|
||||
# darkMode: true
|
||||
pkgManagers:
|
||||
- npm
|
||||
- yarn
|
||||
|
@ -67,6 +67,10 @@ module.exports = {
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.md$/,
|
||||
use: 'raw-loader',
|
||||
},
|
||||
/* Typescript loader */
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
|
10
packages/plugins/ui-theme/types/files.d.ts
vendored
@ -7,3 +7,13 @@ declare module '*.png' {
|
||||
const value: string;
|
||||
export = value;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const value: string;
|
||||
export = value;
|
||||
}
|
||||
|
||||
declare module '*.md' {
|
||||
const value: string;
|
||||
export = value;
|
||||
}
|
||||
|
88
pnpm-lock.yaml
generated
@ -780,11 +780,13 @@ importers:
|
||||
normalize.css: 8.0.1
|
||||
optimize-css-assets-webpack-plugin: 6.0.1
|
||||
ora: 5.4.1
|
||||
raw-loader: 4.0.2
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2
|
||||
react-hook-form: 7.25.0
|
||||
react-hot-loader: 4.13.0
|
||||
react-i18next: 11.15.3
|
||||
react-json-view: 1.21.3
|
||||
react-markdown: 8.0.0
|
||||
react-redux: 7.2.6
|
||||
react-router: 5.2.1
|
||||
@ -859,11 +861,13 @@ importers:
|
||||
normalize.css: 8.0.1
|
||||
optimize-css-assets-webpack-plugin: 6.0.1_webpack@5.67.0
|
||||
ora: 5.4.1
|
||||
raw-loader: 4.0.2_webpack@5.67.0
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
react-hook-form: 7.25.0_react@17.0.2
|
||||
react-hot-loader: 4.13.0_b3482aaf5744fc7c2aeb7941b0e0a78f
|
||||
react-i18next: 11.15.3_ad209b3ec0793904285d43906e66750b
|
||||
react-json-view: 1.21.3_b3482aaf5744fc7c2aeb7941b0e0a78f
|
||||
react-markdown: 8.0.0_b08e3c15324cbe90a6ff8fcd416c932c
|
||||
react-redux: 7.2.6_react-dom@17.0.2+react@17.0.2
|
||||
react-router: 5.2.1_react@17.0.2
|
||||
@ -11139,7 +11143,6 @@ packages:
|
||||
|
||||
/base16/1.0.0:
|
||||
resolution: {integrity: sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=}
|
||||
dev: false
|
||||
|
||||
/base64-js/1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
@ -12382,7 +12385,6 @@ packages:
|
||||
node-fetch: 2.6.7
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/cross-spawn/5.1.0:
|
||||
resolution: {integrity: sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=}
|
||||
@ -14899,11 +14901,9 @@ packages:
|
||||
fbjs: 3.0.4
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/fbjs-css-vars/1.0.2:
|
||||
resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==}
|
||||
dev: false
|
||||
|
||||
/fbjs/3.0.4:
|
||||
resolution: {integrity: sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==}
|
||||
@ -14917,7 +14917,6 @@ packages:
|
||||
ua-parser-js: 0.7.31
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/fd-slicer/1.1.0:
|
||||
resolution: {integrity: sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=}
|
||||
@ -15092,7 +15091,6 @@ packages:
|
||||
react: 17.0.2
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/follow-redirects/1.13.0:
|
||||
resolution: {integrity: sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==}
|
||||
@ -18223,7 +18221,6 @@ packages:
|
||||
big.js: 5.2.2
|
||||
emojis-list: 3.0.0
|
||||
json5: 2.2.0
|
||||
dev: false
|
||||
|
||||
/loader-utils/3.2.0:
|
||||
resolution: {integrity: sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==}
|
||||
@ -18295,7 +18292,6 @@ packages:
|
||||
|
||||
/lodash.curry/4.1.1:
|
||||
resolution: {integrity: sha1-JI42By7ekGUB11lmIAqG2riyMXA=}
|
||||
dev: false
|
||||
|
||||
/lodash.debounce/4.0.8:
|
||||
resolution: {integrity: sha1-gteb/zCmfEAF/9XiUVMArZyk168=}
|
||||
@ -18313,7 +18309,6 @@ packages:
|
||||
|
||||
/lodash.flow/3.5.0:
|
||||
resolution: {integrity: sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=}
|
||||
dev: false
|
||||
|
||||
/lodash.foreach/4.5.0:
|
||||
resolution: {integrity: sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=}
|
||||
@ -21808,7 +21803,6 @@ packages:
|
||||
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
|
||||
dependencies:
|
||||
asap: 2.0.6
|
||||
dev: false
|
||||
|
||||
/prompts/2.4.0:
|
||||
resolution: {integrity: sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==}
|
||||
@ -21970,7 +21964,6 @@ packages:
|
||||
|
||||
/pure-color/1.3.0:
|
||||
resolution: {integrity: sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=}
|
||||
dev: false
|
||||
|
||||
/q/1.5.1:
|
||||
resolution: {integrity: sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=}
|
||||
@ -22075,6 +22068,17 @@ packages:
|
||||
unpipe: 1.0.0
|
||||
dev: false
|
||||
|
||||
/raw-loader/4.0.2_webpack@5.67.0:
|
||||
resolution: {integrity: sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
peerDependencies:
|
||||
webpack: ^4.0.0 || ^5.0.0
|
||||
dependencies:
|
||||
loader-utils: 2.0.2
|
||||
schema-utils: 3.1.1
|
||||
webpack: 5.67.0_webpack-cli@4.7.2
|
||||
dev: true
|
||||
|
||||
/rc/1.2.8:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
hasBin: true
|
||||
@ -22091,7 +22095,6 @@ packages:
|
||||
lodash.curry: 4.1.1
|
||||
lodash.flow: 3.5.0
|
||||
pure-color: 1.3.0
|
||||
dev: false
|
||||
|
||||
/react-dev-utils/11.0.4:
|
||||
resolution: {integrity: sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A==}
|
||||
@ -22267,6 +22270,23 @@ packages:
|
||||
/react-is/17.0.2:
|
||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||
|
||||
/react-json-view/1.21.3_b3482aaf5744fc7c2aeb7941b0e0a78f:
|
||||
resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==}
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^16.3.0 || ^15.5.4
|
||||
react-dom: ^17.0.0 || ^16.3.0 || ^15.5.4
|
||||
dependencies:
|
||||
flux: 4.0.3_react@17.0.2
|
||||
react: 17.0.2
|
||||
react-base16-styling: 0.6.0
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
react-lifecycles-compat: 3.0.4
|
||||
react-textarea-autosize: 8.3.3_b08e3c15324cbe90a6ff8fcd416c932c
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- encoding
|
||||
dev: true
|
||||
|
||||
/react-json-view/1.21.3_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==}
|
||||
peerDependencies:
|
||||
@ -22462,6 +22482,20 @@ packages:
|
||||
react: 17.0.2
|
||||
dev: false
|
||||
|
||||
/react-textarea-autosize/8.3.3_b08e3c15324cbe90a6ff8fcd416c932c:
|
||||
resolution: {integrity: sha512-2XlHXK2TDxS6vbQaoPbMOfQ8GK7+irc2fVK6QFIcC8GOnH3zI/v481n+j1L0WaPVvKxwesnY93fEfH++sus2rQ==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.17.2
|
||||
react: 17.0.2
|
||||
use-composed-ref: 1.2.1_react@17.0.2
|
||||
use-latest: 1.2.0_b08e3c15324cbe90a6ff8fcd416c932c
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: true
|
||||
|
||||
/react-textarea-autosize/8.3.3_react@17.0.2:
|
||||
resolution: {integrity: sha512-2XlHXK2TDxS6vbQaoPbMOfQ8GK7+irc2fVK6QFIcC8GOnH3zI/v481n+j1L0WaPVvKxwesnY93fEfH++sus2rQ==}
|
||||
engines: {node: '>=10'}
|
||||
@ -23460,7 +23494,6 @@ packages:
|
||||
|
||||
/setimmediate/1.0.5:
|
||||
resolution: {integrity: sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=}
|
||||
dev: false
|
||||
|
||||
/setprototypeof/1.1.0:
|
||||
resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==}
|
||||
@ -25107,7 +25140,6 @@ packages:
|
||||
|
||||
/ua-parser-js/0.7.31:
|
||||
resolution: {integrity: sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==}
|
||||
dev: false
|
||||
|
||||
/uglify-js/3.12.4:
|
||||
resolution: {integrity: sha512-L5i5jg/SHkEqzN18gQMTWsZk3KelRsfD1wUVNqtq0kzqWQqcJjyL8yc1o8hJgRrWqrAl2mUFbhfznEIoi7zi2A==}
|
||||
@ -25492,7 +25524,6 @@ packages:
|
||||
react: ^16.8.0 || ^17.0.0
|
||||
dependencies:
|
||||
react: 17.0.2
|
||||
dev: false
|
||||
|
||||
/use-is-in-viewport/1.0.9_react@17.0.2:
|
||||
resolution: {integrity: sha512-Dgi0z/X9eTk3ziI+b28mZVoYtCtyoUFQ+9VBq6fR5EdjqmmsSlbr8ysXAwmEl89OUNBQwVGLGdI9nqwiu3168g==}
|
||||
@ -25504,6 +25535,19 @@ packages:
|
||||
react: 17.0.2
|
||||
dev: false
|
||||
|
||||
/use-isomorphic-layout-effect/1.1.1_b08e3c15324cbe90a6ff8fcd416c932c:
|
||||
resolution: {integrity: sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 17.0.38
|
||||
react: 17.0.2
|
||||
dev: true
|
||||
|
||||
/use-isomorphic-layout-effect/1.1.1_react@17.0.2:
|
||||
resolution: {integrity: sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==}
|
||||
peerDependencies:
|
||||
@ -25516,6 +25560,20 @@ packages:
|
||||
react: 17.0.2
|
||||
dev: false
|
||||
|
||||
/use-latest/1.2.0_b08e3c15324cbe90a6ff8fcd416c932c:
|
||||
resolution: {integrity: sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 17.0.38
|
||||
react: 17.0.2
|
||||
use-isomorphic-layout-effect: 1.1.1_b08e3c15324cbe90a6ff8fcd416c932c
|
||||
dev: true
|
||||
|
||||
/use-latest/1.2.0_react@17.0.2:
|
||||
resolution: {integrity: sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==}
|
||||
peerDependencies:
|
||||
|
@ -5,6 +5,7 @@ import path from 'path';
|
||||
const token = process.env.TOKEN;
|
||||
const excludebots = [
|
||||
'verdacciobot',
|
||||
'github-actions[bot]',
|
||||
'dependabot-preview[bot]',
|
||||
'dependabot[bot]',
|
||||
'64b2b6d12bfe4baae7dad3d01',
|
||||
@ -30,7 +31,19 @@ const excludebots = [
|
||||
__dirname,
|
||||
'../packages/tools/docusaurus-plugin-contributors/src/contributors.json'
|
||||
);
|
||||
// for the website
|
||||
await fs.writeFile(pathContributorsFile, JSON.stringify(result, null, 4));
|
||||
const contributorsListId = result.map((contributor: any) => {
|
||||
return { username: contributor?.login, id: contributor.id };
|
||||
});
|
||||
// .sort()
|
||||
// .slice(0, 15);
|
||||
// for the ui, list of ids to be added on the contributors.
|
||||
const pathContributorsUIFile = path.join(
|
||||
__dirname,
|
||||
'../packages/plugins/ui-theme/src/App/Header/generated_contributors_list.json'
|
||||
);
|
||||
await fs.writeFile(pathContributorsUIFile, JSON.stringify(contributorsListId, null, 4));
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('error on update', err);
|
||||
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB |