mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-24 08:43:51 +03:00
Migrated a lot more components to new DI system
This commit is contained in:
parent
5e6ad14a85
commit
5616d045ab
18 changed files with 237 additions and 213 deletions
|
@ -6,9 +6,8 @@ import React from 'react';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import DeleteServerButton from '../servers/DeleteServerButton';
|
|
||||||
import './AsideMenu.scss';
|
|
||||||
import { serverType } from '../servers/prop-types';
|
import { serverType } from '../servers/prop-types';
|
||||||
|
import './AsideMenu.scss';
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
className: '',
|
className: '',
|
||||||
|
@ -20,51 +19,57 @@ const propTypes = {
|
||||||
showOnMobile: PropTypes.bool,
|
showOnMobile: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AsideMenu({ selectedServer, className, showOnMobile }) {
|
const AsideMenu = (DeleteServerButton) => {
|
||||||
const serverId = selectedServer ? selectedServer.id : '';
|
const AsideMenu = ({ selectedServer, className, showOnMobile }) => {
|
||||||
const asideClass = classnames('aside-menu', className, {
|
const serverId = selectedServer ? selectedServer.id : '';
|
||||||
'aside-menu--hidden': !showOnMobile,
|
const asideClass = classnames('aside-menu', className, {
|
||||||
});
|
'aside-menu--hidden': !showOnMobile,
|
||||||
const shortUrlsIsActive = (match, location) => location.pathname.match('/list-short-urls');
|
});
|
||||||
|
const shortUrlsIsActive = (match, location) => location.pathname.match('/list-short-urls');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={asideClass}>
|
<aside className={asideClass}>
|
||||||
<nav className="nav flex-column aside-menu__nav">
|
<nav className="nav flex-column aside-menu__nav">
|
||||||
<NavLink
|
<NavLink
|
||||||
className="aside-menu__item"
|
className="aside-menu__item"
|
||||||
activeClassName="aside-menu__item--selected"
|
activeClassName="aside-menu__item--selected"
|
||||||
to={`/server/${serverId}/list-short-urls/1`}
|
to={`/server/${serverId}/list-short-urls/1`}
|
||||||
isActive={shortUrlsIsActive}
|
isActive={shortUrlsIsActive}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={listIcon} />
|
<FontAwesomeIcon icon={listIcon} />
|
||||||
<span className="aside-menu__item-text">List short URLs</span>
|
<span className="aside-menu__item-text">List short URLs</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink
|
<NavLink
|
||||||
className="aside-menu__item"
|
className="aside-menu__item"
|
||||||
activeClassName="aside-menu__item--selected"
|
activeClassName="aside-menu__item--selected"
|
||||||
to={`/server/${serverId}/create-short-url`}
|
to={`/server/${serverId}/create-short-url`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={createIcon} flip="horizontal" />
|
<FontAwesomeIcon icon={createIcon} flip="horizontal" />
|
||||||
<span className="aside-menu__item-text">Create short URL</span>
|
<span className="aside-menu__item-text">Create short URL</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
<NavLink
|
<NavLink
|
||||||
className="aside-menu__item"
|
className="aside-menu__item"
|
||||||
activeClassName="aside-menu__item--selected"
|
activeClassName="aside-menu__item--selected"
|
||||||
to={`/server/${serverId}/manage-tags`}
|
to={`/server/${serverId}/manage-tags`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={tagsIcon} />
|
<FontAwesomeIcon icon={tagsIcon} />
|
||||||
<span className="aside-menu__item-text">Manage tags</span>
|
<span className="aside-menu__item-text">Manage tags</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
<DeleteServerButton
|
<DeleteServerButton
|
||||||
className="aside-menu__item aside-menu__item--danger"
|
className="aside-menu__item aside-menu__item--danger"
|
||||||
server={selectedServer}
|
server={selectedServer}
|
||||||
/>
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
AsideMenu.defaultProps = defaultProps;
|
AsideMenu.defaultProps = defaultProps;
|
||||||
AsideMenu.propTypes = propTypes;
|
AsideMenu.propTypes = propTypes;
|
||||||
|
|
||||||
|
return AsideMenu;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AsideMenu;
|
||||||
|
|
|
@ -6,11 +6,10 @@ import { Link } from 'react-router-dom';
|
||||||
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
|
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ServersDropdown from '../servers/ServersDropdown';
|
|
||||||
import './MainHeader.scss';
|
|
||||||
import shlinkLogo from './shlink-logo-white.png';
|
import shlinkLogo from './shlink-logo-white.png';
|
||||||
|
import './MainHeader.scss';
|
||||||
|
|
||||||
const MainHeader = () => class MainHeaderComponent extends React.Component {
|
const MainHeader = (ServersDropdown) => class MainHeader extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,13 +7,10 @@ import classnames from 'classnames';
|
||||||
import * as PropTypes from 'prop-types';
|
import * as PropTypes from 'prop-types';
|
||||||
import ShortUrlsVisits from '../visits/ShortUrlVisits';
|
import ShortUrlsVisits from '../visits/ShortUrlVisits';
|
||||||
import CreateShortUrl from '../short-urls/CreateShortUrl';
|
import CreateShortUrl from '../short-urls/CreateShortUrl';
|
||||||
import ShortUrls from '../short-urls/ShortUrls';
|
|
||||||
import './MenuLayout.scss';
|
import './MenuLayout.scss';
|
||||||
import TagsList from '../tags/TagsList';
|
|
||||||
import { serverType } from '../servers/prop-types';
|
import { serverType } from '../servers/prop-types';
|
||||||
import AsideMenu from './AsideMenu';
|
|
||||||
|
|
||||||
export default class MenuLayout extends React.Component {
|
const MenuLayout = (TagsList, ShortUrls, AsideMenu) => class MenuLayout extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
match: PropTypes.object,
|
match: PropTypes.object,
|
||||||
selectServer: PropTypes.func,
|
selectServer: PropTypes.func,
|
||||||
|
@ -99,4 +96,6 @@ export default class MenuLayout extends React.Component {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default MenuLayout;
|
||||||
|
|
|
@ -2,37 +2,110 @@ import Bottle from 'bottlejs';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
import { pick } from 'ramda';
|
import { assoc, pick } from 'ramda';
|
||||||
|
import csvjson from 'csvjson';
|
||||||
|
import axios from 'axios';
|
||||||
import App from '../App';
|
import App from '../App';
|
||||||
import ScrollToTop from '../common/ScrollToTop';
|
import ScrollToTop from '../common/ScrollToTop';
|
||||||
import MainHeader from '../common/MainHeader';
|
import MainHeader from '../common/MainHeader';
|
||||||
import { resetSelectedServer, selectServer } from '../servers/reducers/selectedServer';
|
import { resetSelectedServer, selectServer } from '../servers/reducers/selectedServer';
|
||||||
import Home from '../common/Home';
|
import Home from '../common/Home';
|
||||||
import MenuLayout from '../common/MenuLayout';
|
import MenuLayout from '../common/MenuLayout';
|
||||||
import { createServer } from '../servers/reducers/server';
|
import { createServer, createServers, deleteServer, listServers } from '../servers/reducers/server';
|
||||||
import CreateServer from '../servers/CreateServer';
|
import CreateServer from '../servers/CreateServer';
|
||||||
import store from './store';
|
import ServersDropdown from '../servers/ServersDropdown';
|
||||||
|
import TagsList from '../tags/TagsList';
|
||||||
|
import { filterTags, forceListTags } from '../tags/reducers/tagsList';
|
||||||
|
import ShortUrls from '../short-urls/ShortUrls';
|
||||||
|
import SearchBar from '../short-urls/SearchBar';
|
||||||
|
import { listShortUrls } from '../short-urls/reducers/shortUrlsList';
|
||||||
|
import ShortUrlsList from '../short-urls/ShortUrlsList';
|
||||||
|
import { resetShortUrlParams } from '../short-urls/reducers/shortUrlsListParams';
|
||||||
|
import Tag from '../tags/helpers/Tag';
|
||||||
|
import { ColorGenerator } from '../utils/ColorGenerator';
|
||||||
|
import { Storage } from '../utils/Storage';
|
||||||
|
import ShortUrlsRow from '../short-urls/helpers/ShortUrlsRow';
|
||||||
|
import ShortUrlsRowMenu from '../short-urls/helpers/ShortUrlsRowMenu';
|
||||||
|
import { ShlinkApiClient } from '../api/ShlinkApiClient';
|
||||||
|
import DeleteServerModal from '../servers/DeleteServerModal';
|
||||||
|
import DeleteServerButton from '../servers/DeleteServerButton';
|
||||||
|
import AsideMenu from '../common/AsideMenu';
|
||||||
|
import ImportServersBtn from '../servers/helpers/ImportServersBtn';
|
||||||
|
import { ServersImporter } from '../servers/services/ServersImporter';
|
||||||
|
import { ServersExporter } from '../servers/services/ServersExporter';
|
||||||
|
import { ServersService } from '../servers/services/ServersService';
|
||||||
|
|
||||||
const bottle = new Bottle();
|
const bottle = new Bottle();
|
||||||
|
|
||||||
bottle.constant('store', store);
|
bottle.constant('ScrollToTop', ScrollToTop);
|
||||||
bottle.serviceFactory('ScrollToTop', () => withRouter(ScrollToTop));
|
bottle.decorator('ScrollToTop', withRouter);
|
||||||
bottle.serviceFactory('MainHeader', () => withRouter(MainHeader()));
|
|
||||||
bottle.serviceFactory('Home', () => connect(pick([ 'servers' ]), { resetSelectedServer })(Home));
|
bottle.serviceFactory('MainHeader', MainHeader, 'ServersDropdown');
|
||||||
bottle.serviceFactory(
|
bottle.decorator('MainHeader', withRouter);
|
||||||
|
|
||||||
|
bottle.serviceFactory('Home', () => Home);
|
||||||
|
bottle.decorator('Home', connect(pick([ 'servers' ]), { resetSelectedServer }));
|
||||||
|
|
||||||
|
bottle.serviceFactory('MenuLayout', MenuLayout, 'TagsList', 'ShortUrls', 'AsideMenu');
|
||||||
|
bottle.decorator(
|
||||||
'MenuLayout',
|
'MenuLayout',
|
||||||
() => compose(
|
compose(
|
||||||
connect(pick([ 'selectedServer', 'shortUrlsListParams' ]), { selectServer }),
|
connect(pick([ 'selectedServer', 'shortUrlsListParams' ]), { selectServer }),
|
||||||
withRouter
|
withRouter
|
||||||
)(MenuLayout)
|
)
|
||||||
);
|
|
||||||
bottle.serviceFactory(
|
|
||||||
'CreateServer',
|
|
||||||
() => connect(
|
|
||||||
pick([ 'selectedServer' ]),
|
|
||||||
{ createServer, resetSelectedServer }
|
|
||||||
)(CreateServer)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bottle.serviceFactory('CreateServer', CreateServer, 'ImportServersBtn');
|
||||||
|
bottle.decorator('CreateServer', connect(pick([ 'selectedServer' ]), { createServer, resetSelectedServer }));
|
||||||
|
|
||||||
bottle.serviceFactory('App', App, 'MainHeader', 'Home', 'MenuLayout', 'CreateServer');
|
bottle.serviceFactory('App', App, 'MainHeader', 'Home', 'MenuLayout', 'CreateServer');
|
||||||
|
|
||||||
|
bottle.serviceFactory('ServersDropdown', ServersDropdown, 'ServersExporter');
|
||||||
|
bottle.decorator('ServersDropdown', connect(pick([ 'servers', 'selectedServer' ]), { listServers, selectServer }));
|
||||||
|
|
||||||
|
bottle.serviceFactory('TagsList', () => TagsList);
|
||||||
|
bottle.decorator('TagsList', connect(pick([ 'tagsList' ]), { forceListTags, filterTags }));
|
||||||
|
|
||||||
|
bottle.serviceFactory('ShortUrls', ShortUrls, 'SearchBar', 'ShortUrlsList');
|
||||||
|
bottle.decorator('ShortUrls', connect(
|
||||||
|
(state) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList)
|
||||||
|
));
|
||||||
|
|
||||||
|
bottle.serviceFactory('SearchBar', SearchBar, 'Tag');
|
||||||
|
bottle.decorator('SearchBar', connect(pick([ 'shortUrlsListParams' ]), { listShortUrls }));
|
||||||
|
|
||||||
|
bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsRow');
|
||||||
|
bottle.decorator('ShortUrlsList', connect(
|
||||||
|
pick([ 'selectedServer', 'shortUrlsListParams' ]),
|
||||||
|
{ listShortUrls, resetShortUrlParams }
|
||||||
|
));
|
||||||
|
|
||||||
|
bottle.serviceFactory('Tag', Tag, 'ColorGenerator');
|
||||||
|
|
||||||
|
bottle.constant('localStorage', global.localStorage);
|
||||||
|
bottle.service('Storage', Storage, 'localStorage');
|
||||||
|
bottle.service('ColorGenerator', ColorGenerator, 'Storage');
|
||||||
|
|
||||||
|
bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'Tag', 'ShortUrlsRowMenu');
|
||||||
|
|
||||||
|
bottle.serviceFactory('ShortUrlsRowMenu', () => ShortUrlsRowMenu);
|
||||||
|
|
||||||
|
bottle.constant('axios', axios);
|
||||||
|
bottle.service('ShlinkApiClient', ShlinkApiClient, 'axios');
|
||||||
|
|
||||||
|
bottle.serviceFactory('DeleteServerModal', () => DeleteServerModal);
|
||||||
|
bottle.decorator('DeleteServerModal', compose(withRouter, connect(null, { deleteServer })));
|
||||||
|
|
||||||
|
bottle.serviceFactory('DeleteServerButton', DeleteServerButton, 'DeleteServerModal');
|
||||||
|
bottle.serviceFactory('AsideMenu', AsideMenu, 'DeleteServerButton');
|
||||||
|
|
||||||
|
bottle.serviceFactory('ImportServersBtn', ImportServersBtn, 'ServersImporter');
|
||||||
|
bottle.decorator('ImportServersBtn', connect(null, { createServers }));
|
||||||
|
|
||||||
|
bottle.constant('csvjson', csvjson);
|
||||||
|
bottle.constant('window', global.window);
|
||||||
|
bottle.service('ServersImporter', ServersImporter, 'csvjson');
|
||||||
|
bottle.service('ServersService', ServersService, 'Storage');
|
||||||
|
bottle.service('ServersExporter', ServersExporter, 'ServersService', 'window', 'csvjson');
|
||||||
|
|
||||||
export default bottle.container;
|
export default bottle.container;
|
||||||
|
|
|
@ -6,11 +6,12 @@ import { BrowserRouter } from 'react-router-dom';
|
||||||
import { homepage } from '../package.json';
|
import { homepage } from '../package.json';
|
||||||
import registerServiceWorker from './registerServiceWorker';
|
import registerServiceWorker from './registerServiceWorker';
|
||||||
import container from './container';
|
import container from './container';
|
||||||
|
import store from './container/store';
|
||||||
import '../node_modules/react-datepicker/dist/react-datepicker.css';
|
import '../node_modules/react-datepicker/dist/react-datepicker.css';
|
||||||
import './common/react-tagsinput.scss';
|
import './common/react-tagsinput.scss';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const { App, ScrollToTop, store } = container;
|
const { App, ScrollToTop } = container;
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
|
|
@ -4,11 +4,10 @@ import { v4 as uuid } from 'uuid';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { stateFlagTimeout } from '../utils/utils';
|
import { stateFlagTimeout } from '../utils/utils';
|
||||||
import './CreateServer.scss';
|
import './CreateServer.scss';
|
||||||
import ImportServersBtn from './helpers/ImportServersBtn';
|
|
||||||
|
|
||||||
const SHOW_IMPORT_MSG_TIME = 4000;
|
const SHOW_IMPORT_MSG_TIME = 4000;
|
||||||
|
|
||||||
export default class CreateServer extends React.Component {
|
const CreateServer = (ImportServersBtn) => class CreateServer extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
createServer: PropTypes.func,
|
createServer: PropTypes.func,
|
||||||
history: PropTypes.shape({
|
history: PropTypes.shape({
|
||||||
|
@ -88,4 +87,6 @@ export default class CreateServer extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default CreateServer;
|
||||||
|
|
|
@ -2,10 +2,9 @@ import deleteIcon from '@fortawesome/fontawesome-free-solid/faMinusCircle';
|
||||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import DeleteServerModal from './DeleteServerModal';
|
|
||||||
import { serverType } from './prop-types';
|
import { serverType } from './prop-types';
|
||||||
|
|
||||||
export default class DeleteServerButton extends React.Component {
|
const DeleteServerButton = (DeleteServerModal) => class DeleteServerButton extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
server: serverType,
|
server: serverType,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
@ -36,4 +35,6 @@ export default class DeleteServerButton extends React.Component {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default DeleteServerButton;
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
||||||
import { compose } from 'redux';
|
|
||||||
import { deleteServer } from './reducers/server';
|
|
||||||
import { serverType } from './prop-types';
|
import { serverType } from './prop-types';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
@ -17,7 +13,7 @@ const propTypes = {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeleteServerModalComponent = ({ server, toggle, isOpen, deleteServer, history }) => {
|
const DeleteServerModal = ({ server, toggle, isOpen, deleteServer, history }) => {
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
deleteServer(server);
|
deleteServer(server);
|
||||||
toggle();
|
toggle();
|
||||||
|
@ -42,11 +38,6 @@ export const DeleteServerModalComponent = ({ server, toggle, isOpen, deleteServe
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
DeleteServerModalComponent.propTypes = propTypes;
|
DeleteServerModal.propTypes = propTypes;
|
||||||
|
|
||||||
const DeleteServerModal = compose(
|
|
||||||
withRouter,
|
|
||||||
connect(null, { deleteServer })
|
|
||||||
)(DeleteServerModalComponent);
|
|
||||||
|
|
||||||
export default DeleteServerModal;
|
export default DeleteServerModal;
|
||||||
|
|
|
@ -1,30 +1,20 @@
|
||||||
import { isEmpty, pick, values } from 'ramda';
|
import { isEmpty, values } from 'ramda';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
|
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { selectServer } from '../servers/reducers/selectedServer';
|
|
||||||
import serversExporter from '../servers/services/ServersExporter';
|
|
||||||
import { listServers } from './reducers/server';
|
|
||||||
import { serverType } from './prop-types';
|
import { serverType } from './prop-types';
|
||||||
|
|
||||||
export class ServersDropdownComponent extends React.Component {
|
const ServersDropdown = (serversExporter) => class ServersDropdown extends React.Component {
|
||||||
static defaultProps = {
|
|
||||||
serversExporter,
|
|
||||||
};
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
servers: PropTypes.object,
|
servers: PropTypes.object,
|
||||||
serversExporter: PropTypes.shape({
|
|
||||||
exportServers: PropTypes.func,
|
|
||||||
}),
|
|
||||||
selectedServer: serverType,
|
selectedServer: serverType,
|
||||||
selectServer: PropTypes.func,
|
selectServer: PropTypes.func,
|
||||||
listServers: PropTypes.func,
|
listServers: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderServers = () => {
|
renderServers = () => {
|
||||||
const { servers, selectedServer, selectServer, serversExporter } = this.props;
|
const { servers, selectedServer, selectServer } = this.props;
|
||||||
|
|
||||||
if (isEmpty(servers)) {
|
if (isEmpty(servers)) {
|
||||||
return <DropdownItem disabled><i>Add a server first...</i></DropdownItem>;
|
return <DropdownItem disabled><i>Add a server first...</i></DropdownItem>;
|
||||||
|
@ -68,11 +58,6 @@ export class ServersDropdownComponent extends React.Component {
|
||||||
</UncontrolledDropdown>
|
</UncontrolledDropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const ServersDropdown = connect(
|
|
||||||
pick([ 'servers', 'selectedServer' ]),
|
|
||||||
{ listServers, selectServer }
|
|
||||||
)(ServersDropdownComponent);
|
|
||||||
|
|
||||||
export default ServersDropdown;
|
export default ServersDropdown;
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import { assoc } from 'ramda';
|
import { assoc } from 'ramda';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { createServers } from '../reducers/server';
|
|
||||||
import serversImporter, { serversImporterType } from '../services/ServersImporter';
|
|
||||||
|
|
||||||
export class ImportServersBtnComponent extends React.Component {
|
const ImportServersBtn = (serversImporter) => class ImportServersBtn extends React.Component {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
serversImporter,
|
|
||||||
onImport: () => ({}),
|
onImport: () => ({}),
|
||||||
};
|
};
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onImport: PropTypes.func,
|
onImport: PropTypes.func,
|
||||||
serversImporter: serversImporterType,
|
|
||||||
createServers: PropTypes.func,
|
createServers: PropTypes.func,
|
||||||
fileRef: PropTypes.oneOfType([ PropTypes.object, PropTypes.node ]),
|
fileRef: PropTypes.oneOfType([ PropTypes.object, PropTypes.node ]),
|
||||||
};
|
};
|
||||||
|
@ -25,7 +20,8 @@ export class ImportServersBtnComponent extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { serversImporter: { importServersFromFile }, onImport, createServers } = this.props;
|
const { importServersFromFile } = serversImporter;
|
||||||
|
const { onImport, createServers } = this.props;
|
||||||
const onChange = (e) =>
|
const onChange = (e) =>
|
||||||
importServersFromFile(e.target.files[0])
|
importServersFromFile(e.target.files[0])
|
||||||
.then((servers) => servers.map((server) => assoc('id', uuid(), server)))
|
.then((servers) => servers.map((server) => assoc('id', uuid(), server)))
|
||||||
|
@ -56,8 +52,6 @@ export class ImportServersBtnComponent extends React.Component {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const ImportServersBtn = connect(null, { createServers })(ImportServersBtnComponent);
|
|
||||||
|
|
||||||
export default ImportServersBtn;
|
export default ImportServersBtn;
|
||||||
|
|
|
@ -1,55 +1,54 @@
|
||||||
import tagsIcon from '@fortawesome/fontawesome-free-solid/faTags';
|
import tagsIcon from '@fortawesome/fontawesome-free-solid/faTags';
|
||||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { isEmpty } from 'ramda';
|
||||||
import { isEmpty, pick } from 'ramda';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Tag from '../tags/helpers/Tag';
|
|
||||||
import SearchField from '../utils/SearchField';
|
import SearchField from '../utils/SearchField';
|
||||||
import { listShortUrls } from './reducers/shortUrlsList';
|
|
||||||
import './SearchBar.scss';
|
|
||||||
import { shortUrlsListParamsType } from './reducers/shortUrlsListParams';
|
import { shortUrlsListParamsType } from './reducers/shortUrlsListParams';
|
||||||
|
import './SearchBar.scss';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
listShortUrls: PropTypes.func,
|
listShortUrls: PropTypes.func,
|
||||||
shortUrlsListParams: shortUrlsListParamsType,
|
shortUrlsListParams: shortUrlsListParamsType,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SearchBarComponent({ listShortUrls, shortUrlsListParams }) {
|
const SearchBar = (Tag) => {
|
||||||
const selectedTags = shortUrlsListParams.tags || [];
|
const SearchBar = ({ listShortUrls, shortUrlsListParams }) => {
|
||||||
|
const selectedTags = shortUrlsListParams.tags || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="serach-bar-container">
|
<div className="serach-bar-container">
|
||||||
<SearchField onChange={
|
<SearchField onChange={
|
||||||
(searchTerm) => listShortUrls({ ...shortUrlsListParams, searchTerm })
|
(searchTerm) => listShortUrls({ ...shortUrlsListParams, searchTerm })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isEmpty(selectedTags) && (
|
{!isEmpty(selectedTags) && (
|
||||||
<h4 className="search-bar__selected-tag mt-2">
|
<h4 className="search-bar__selected-tag mt-2">
|
||||||
<FontAwesomeIcon icon={tagsIcon} className="search-bar__tags-icon" />
|
<FontAwesomeIcon icon={tagsIcon} className="search-bar__tags-icon" />
|
||||||
|
|
||||||
{selectedTags.map((tag) => (
|
{selectedTags.map((tag) => (
|
||||||
<Tag
|
<Tag
|
||||||
key={tag}
|
key={tag}
|
||||||
text={tag}
|
text={tag}
|
||||||
clearable
|
clearable
|
||||||
onClose={() => listShortUrls(
|
onClose={() => listShortUrls(
|
||||||
{
|
{
|
||||||
...shortUrlsListParams,
|
...shortUrlsListParams,
|
||||||
tags: selectedTags.filter((selectedTag) => selectedTag !== tag),
|
tags: selectedTags.filter((selectedTag) => selectedTag !== tag),
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</h4>
|
</h4>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
SearchBarComponent.propTypes = propTypes;
|
SearchBar.propTypes = propTypes;
|
||||||
|
|
||||||
const SearchBar = connect(pick([ 'shortUrlsListParams' ]), { listShortUrls })(SearchBarComponent);
|
return SearchBar;
|
||||||
|
};
|
||||||
|
|
||||||
export default SearchBar;
|
export default SearchBar;
|
||||||
|
|
|
@ -1,27 +1,21 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { assoc } from 'ramda';
|
|
||||||
import Paginator from './Paginator';
|
import Paginator from './Paginator';
|
||||||
import SearchBar from './SearchBar';
|
|
||||||
import ShortUrlsList from './ShortUrlsList';
|
|
||||||
|
|
||||||
export function ShortUrlsComponent(props) {
|
const ShortUrls = (SearchBar, ShortUrlsList) => (props) => {
|
||||||
const { match: { params } } = props;
|
const { match: { params }, shortUrlsList } = props;
|
||||||
|
const { page, serverId } = params;
|
||||||
|
const { data = [], pagination } = shortUrlsList;
|
||||||
|
|
||||||
// Using a key on a component makes react to create a new instance every time the key changes
|
// Using a key on a component makes react to create a new instance every time the key changes
|
||||||
const urlsListKey = `${params.serverId}_${params.page}`;
|
const urlsListKey = `${serverId}_${page}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="shlink-container">
|
<div className="shlink-container">
|
||||||
<div className="form-group"><SearchBar /></div>
|
<div className="form-group"><SearchBar /></div>
|
||||||
<ShortUrlsList {...props} shortUrlsList={props.shortUrlsList.data || []} key={urlsListKey} />
|
<ShortUrlsList {...props} shortUrlsList={data} key={urlsListKey} />
|
||||||
<Paginator paginator={props.shortUrlsList.pagination} serverId={props.match.params.serverId} />
|
<Paginator paginator={pagination} serverId={serverId} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const ShortUrls = connect(
|
|
||||||
(state) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList)
|
|
||||||
)(ShortUrlsComponent);
|
|
||||||
|
|
||||||
export default ShortUrls;
|
export default ShortUrls;
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import caretDownIcon from '@fortawesome/fontawesome-free-solid/faCaretDown';
|
import caretDownIcon from '@fortawesome/fontawesome-free-solid/faCaretDown';
|
||||||
import caretUpIcon from '@fortawesome/fontawesome-free-solid/faCaretUp';
|
import caretUpIcon from '@fortawesome/fontawesome-free-solid/faCaretUp';
|
||||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||||
import { head, isEmpty, keys, pick, values } from 'ramda';
|
import { head, isEmpty, keys, values } from 'ramda';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { serverType } from '../servers/prop-types';
|
import { serverType } from '../servers/prop-types';
|
||||||
import SortingDropdown from '../utils/SortingDropdown';
|
import SortingDropdown from '../utils/SortingDropdown';
|
||||||
import { determineOrderDir } from '../utils/utils';
|
import { determineOrderDir } from '../utils/utils';
|
||||||
import { ShortUrlsRow } from './helpers/ShortUrlsRow';
|
import { shortUrlType } from './reducers/shortUrlsList';
|
||||||
import { listShortUrls, shortUrlType } from './reducers/shortUrlsList';
|
import { shortUrlsListParamsType } from './reducers/shortUrlsListParams';
|
||||||
import { resetShortUrlParams, shortUrlsListParamsType } from './reducers/shortUrlsListParams';
|
|
||||||
import './ShortUrlsList.scss';
|
import './ShortUrlsList.scss';
|
||||||
|
|
||||||
const SORTABLE_FIELDS = {
|
const SORTABLE_FIELDS = {
|
||||||
|
@ -21,7 +19,7 @@ const SORTABLE_FIELDS = {
|
||||||
visits: 'Visits',
|
visits: 'Visits',
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ShortUrlsListComponent extends React.Component {
|
const ShortUrlsList = (ShortUrlsRow) => class ShortUrlsList extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
listShortUrls: PropTypes.func,
|
listShortUrls: PropTypes.func,
|
||||||
resetShortUrlParams: PropTypes.func,
|
resetShortUrlParams: PropTypes.func,
|
||||||
|
@ -167,11 +165,6 @@ export class ShortUrlsListComponent extends React.Component {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const ShortUrlsList = connect(
|
|
||||||
pick([ 'selectedServer', 'shortUrlsListParams' ]),
|
|
||||||
{ listShortUrls, resetShortUrlParams }
|
|
||||||
)(ShortUrlsListComponent);
|
|
||||||
|
|
||||||
export default ShortUrlsList;
|
export default ShortUrlsList;
|
||||||
|
|
|
@ -2,16 +2,14 @@ import { isEmpty } from 'ramda';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Moment from 'react-moment';
|
import Moment from 'react-moment';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Tag from '../../tags/helpers/Tag';
|
|
||||||
import { shortUrlsListParamsType } from '../reducers/shortUrlsListParams';
|
import { shortUrlsListParamsType } from '../reducers/shortUrlsListParams';
|
||||||
import { serverType } from '../../servers/prop-types';
|
import { serverType } from '../../servers/prop-types';
|
||||||
import ExternalLink from '../../utils/ExternalLink';
|
import ExternalLink from '../../utils/ExternalLink';
|
||||||
import { shortUrlType } from '../reducers/shortUrlsList';
|
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||||
import { stateFlagTimeout } from '../../utils/utils';
|
import { stateFlagTimeout } from '../../utils/utils';
|
||||||
import { ShortUrlsRowMenu } from './ShortUrlsRowMenu';
|
|
||||||
import './ShortUrlsRow.scss';
|
import './ShortUrlsRow.scss';
|
||||||
|
|
||||||
export class ShortUrlsRow extends React.Component {
|
const ShortUrlsRow = (Tag, ShortUrlsRowMenu) => class ShortUrlsRow extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
refreshList: PropTypes.func,
|
refreshList: PropTypes.func,
|
||||||
shortUrlsListParams: shortUrlsListParamsType,
|
shortUrlsListParams: shortUrlsListParamsType,
|
||||||
|
@ -72,4 +70,6 @@ export class ShortUrlsRow extends React.Component {
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default ShortUrlsRow;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import EditTagsModal from './EditTagsModal';
|
||||||
import DeleteShortUrlModal from './DeleteShortUrlModal';
|
import DeleteShortUrlModal from './DeleteShortUrlModal';
|
||||||
import './ShortUrlsRowMenu.scss';
|
import './ShortUrlsRowMenu.scss';
|
||||||
|
|
||||||
export class ShortUrlsRowMenu extends React.Component {
|
export default class ShortUrlsRowMenu extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
completeShortUrl: PropTypes.string,
|
completeShortUrl: PropTypes.string,
|
||||||
onCopyToClipboard: PropTypes.func,
|
onCopyToClipboard: PropTypes.func,
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { splitEvery } from 'ramda';
|
||||||
import { pick, splitEvery } from 'ramda';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MuttedMessage from '../utils/MuttedMessage';
|
import MuttedMessage from '../utils/MuttedMessage';
|
||||||
import SearchField from '../utils/SearchField';
|
import SearchField from '../utils/SearchField';
|
||||||
import { filterTags, forceListTags } from './reducers/tagsList';
|
|
||||||
import TagCard from './TagCard';
|
import TagCard from './TagCard';
|
||||||
|
|
||||||
const { ceil } = Math;
|
const { ceil } = Math;
|
||||||
const TAGS_GROUPS_AMOUNT = 4;
|
const TAGS_GROUPS_AMOUNT = 4;
|
||||||
|
|
||||||
export class TagsListComponent extends React.Component {
|
export default class TagsList extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
filterTags: PropTypes.func,
|
filterTags: PropTypes.func,
|
||||||
forceListTags: PropTypes.func,
|
forceListTags: PropTypes.func,
|
||||||
|
@ -83,7 +81,3 @@ export class TagsListComponent extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagsList = connect(pick([ 'tagsList' ]), { forceListTags, filterTags })(TagsListComponent);
|
|
||||||
|
|
||||||
export default TagsList;
|
|
||||||
|
|
|
@ -1,31 +1,23 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import colorGenerator, { colorGeneratorType } from '../../utils/ColorGenerator';
|
|
||||||
import './Tag.scss';
|
import './Tag.scss';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
colorGenerator: colorGeneratorType,
|
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
clearable: PropTypes.bool,
|
clearable: PropTypes.bool,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
onClose: PropTypes.func,
|
onClose: PropTypes.func,
|
||||||
};
|
};
|
||||||
const defaultProps = {
|
|
||||||
colorGenerator,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Tag(
|
const Tag = (colorGenerator) => {
|
||||||
{
|
const Tag = ({
|
||||||
colorGenerator,
|
|
||||||
text,
|
text,
|
||||||
children,
|
children,
|
||||||
clearable,
|
clearable,
|
||||||
onClick = () => ({}),
|
onClick = () => {},
|
||||||
onClose = () => ({}),
|
onClose = () => {},
|
||||||
}
|
}) => (
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<span
|
<span
|
||||||
className="badge tag"
|
className="badge tag"
|
||||||
style={{ backgroundColor: colorGenerator.getColorForKey(text), cursor: clearable ? 'auto' : 'pointer' }}
|
style={{ backgroundColor: colorGenerator.getColorForKey(text), cursor: clearable ? 'auto' : 'pointer' }}
|
||||||
|
@ -35,7 +27,10 @@ export default function Tag(
|
||||||
{clearable && <span className="close tag__close-selected-tag" onClick={onClose}>×</span>}
|
{clearable && <span className="close tag__close-selected-tag" onClick={onClose}>×</span>}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
Tag.defaultProps = defaultProps;
|
Tag.propTypes = propTypes;
|
||||||
Tag.propTypes = propTypes;
|
|
||||||
|
return Tag;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tag;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { identity, values } from 'ramda';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { DropdownItem, DropdownToggle } from 'reactstrap';
|
import { DropdownItem, DropdownToggle } from 'reactstrap';
|
||||||
import { ServersDropdownComponent } from '../../src/servers/ServersDropdown';
|
import ServersDropdown from '../../src/servers/ServersDropdown';
|
||||||
|
|
||||||
describe('<ServersDropdown />', () => {
|
describe('<ServersDropdown />', () => {
|
||||||
let wrapped;
|
let wrapped;
|
||||||
|
@ -13,7 +13,7 @@ describe('<ServersDropdown />', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapped = shallow(<ServersDropdownComponent servers={servers} listServers={identity} />);
|
wrapped = shallow(<ServersDropdown servers={servers} listServers={identity} />);
|
||||||
});
|
});
|
||||||
afterEach(() => wrapped.unmount());
|
afterEach(() => wrapped.unmount());
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ describe('<ServersDropdown />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('contains a message when no servers exist yet', () => {
|
it('contains a message when no servers exist yet', () => {
|
||||||
wrapped = shallow(<ServersDropdownComponent servers={{}} listServers={identity} />);
|
wrapped = shallow(<ServersDropdown servers={{}} listServers={identity} />);
|
||||||
const item = wrapped.find(DropdownItem);
|
const item = wrapped.find(DropdownItem);
|
||||||
|
|
||||||
expect(item).toHaveLength(1);
|
expect(item).toHaveLength(1);
|
||||||
|
|
Loading…
Add table
Reference in a new issue