Local search for apps
This commit is contained in:
parent
8521995758
commit
6ae6c58f4c
14 changed files with 297 additions and 4751 deletions
|
@ -32,7 +32,7 @@ git clone https://github.com/pawelmalak/flame
|
|||
cd flame
|
||||
|
||||
# run only once
|
||||
npm run dev-init
|
||||
npm run dev:init
|
||||
|
||||
# start backend and frontend development servers
|
||||
npm run dev
|
||||
|
|
|
@ -7,6 +7,7 @@ import AppCard from '../AppCard/AppCard';
|
|||
interface ComponentProps {
|
||||
apps: App[];
|
||||
totalApps?: number;
|
||||
searching: boolean;
|
||||
}
|
||||
|
||||
const AppGrid = (props: ComponentProps): JSX.Element => {
|
||||
|
@ -16,26 +17,37 @@ const AppGrid = (props: ComponentProps): JSX.Element => {
|
|||
apps = (
|
||||
<div className={classes.AppGrid}>
|
||||
{props.apps.map((app: App): JSX.Element => {
|
||||
return <AppCard
|
||||
key={app.id}
|
||||
app={app}
|
||||
/>
|
||||
return <AppCard key={app.id} app={app} />;
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
} else {
|
||||
if (props.totalApps) {
|
||||
apps = (
|
||||
<p className={classes.AppsMessage}>There are no pinned applications. You can pin them from the <Link to='/applications'>/applications</Link> menu</p>
|
||||
);
|
||||
if (props.searching) {
|
||||
apps = (
|
||||
<p className={classes.AppsMessage}>
|
||||
No apps match your search criteria
|
||||
</p>
|
||||
);
|
||||
} else {
|
||||
apps = (
|
||||
<p className={classes.AppsMessage}>
|
||||
There are no pinned applications. You can pin them from the{' '}
|
||||
<Link to="/applications">/applications</Link> menu
|
||||
</p>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
apps = (
|
||||
<p className={classes.AppsMessage}>You don't have any applications. You can add a new one from <Link to='/applications'>/applications</Link> menu</p>
|
||||
<p className={classes.AppsMessage}>
|
||||
You don't have any applications. You can add a new one from{' '}
|
||||
<Link to="/applications">/applications</Link> menu
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return apps;
|
||||
}
|
||||
};
|
||||
|
||||
export default AppGrid;
|
||||
export default AppGrid;
|
||||
|
|
|
@ -27,14 +27,11 @@ interface ComponentProps {
|
|||
getApps: Function;
|
||||
apps: App[];
|
||||
loading: boolean;
|
||||
searching: boolean;
|
||||
}
|
||||
|
||||
const Apps = (props: ComponentProps): JSX.Element => {
|
||||
const {
|
||||
getApps,
|
||||
apps,
|
||||
loading
|
||||
} = props;
|
||||
const { getApps, apps, loading, searching = false } = props;
|
||||
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
const [isInEdit, setIsInEdit] = useState(false);
|
||||
|
@ -47,8 +44,8 @@ const Apps = (props: ComponentProps): JSX.Element => {
|
|||
orderId: 0,
|
||||
id: 0,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
})
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (apps.length === 0) {
|
||||
|
@ -59,63 +56,57 @@ const Apps = (props: ComponentProps): JSX.Element => {
|
|||
const toggleModal = (): void => {
|
||||
setModalIsOpen(!modalIsOpen);
|
||||
setIsInUpdate(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleEdit = (): void => {
|
||||
setIsInEdit(!isInEdit);
|
||||
setIsInUpdate(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleUpdate = (app: App): void => {
|
||||
setAppInUpdate(app);
|
||||
setIsInUpdate(true);
|
||||
setModalIsOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Modal isOpen={modalIsOpen} setIsOpen={setModalIsOpen}>
|
||||
{!isInUpdate
|
||||
? <AppForm modalHandler={toggleModal} />
|
||||
: <AppForm modalHandler={toggleModal} app={appInUpdate} />
|
||||
}
|
||||
{!isInUpdate ? (
|
||||
<AppForm modalHandler={toggleModal} />
|
||||
) : (
|
||||
<AppForm modalHandler={toggleModal} app={appInUpdate} />
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
<Headline
|
||||
title='All Applications'
|
||||
subtitle={(<Link to='/'>Go back</Link>)}
|
||||
title="All Applications"
|
||||
subtitle={<Link to="/">Go back</Link>}
|
||||
/>
|
||||
|
||||
|
||||
<div className={classes.ActionsContainer}>
|
||||
<ActionButton
|
||||
name='Add'
|
||||
icon='mdiPlusBox'
|
||||
handler={toggleModal}
|
||||
/>
|
||||
<ActionButton
|
||||
name='Edit'
|
||||
icon='mdiPencil'
|
||||
handler={toggleEdit}
|
||||
/>
|
||||
<ActionButton name="Add" icon="mdiPlusBox" handler={toggleModal} />
|
||||
<ActionButton name="Edit" icon="mdiPencil" handler={toggleEdit} />
|
||||
</div>
|
||||
|
||||
<div className={classes.Apps}>
|
||||
{loading
|
||||
? <Spinner />
|
||||
: (!isInEdit
|
||||
? <AppGrid apps={apps} />
|
||||
: <AppTable updateAppHandler={toggleUpdate} />)
|
||||
}
|
||||
{loading ? (
|
||||
<Spinner />
|
||||
) : !isInEdit ? (
|
||||
<AppGrid apps={apps} searching />
|
||||
) : (
|
||||
<AppTable updateAppHandler={toggleUpdate} />
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
apps: state.app.apps,
|
||||
loading: state.app.loading
|
||||
}
|
||||
}
|
||||
loading: state.app.loading,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { getApps })(Apps);
|
||||
export default connect(mapStateToProps, { getApps })(Apps);
|
||||
|
|
|
@ -47,13 +47,16 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||
appsLoading,
|
||||
getCategories,
|
||||
categories,
|
||||
categoriesLoading
|
||||
categoriesLoading,
|
||||
} = props;
|
||||
|
||||
const [header, setHeader] = useState({
|
||||
dateTime: dateTime(),
|
||||
greeting: greeter()
|
||||
})
|
||||
greeting: greeter(),
|
||||
});
|
||||
|
||||
// Local search query
|
||||
const [localSearch, setLocalSearch] = useState<null | string>(null);
|
||||
|
||||
// Load applications
|
||||
useEffect(() => {
|
||||
|
@ -78,78 +81,93 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||
interval = setInterval(() => {
|
||||
setHeader({
|
||||
dateTime: dateTime(),
|
||||
greeting: greeter()
|
||||
})
|
||||
greeting: greeter(),
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [])
|
||||
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{searchConfig('hideSearch', 0) !== 1
|
||||
? <SearchBar />
|
||||
: <div></div>
|
||||
}
|
||||
{searchConfig('hideSearch', 0) !== 1 ? (
|
||||
<SearchBar setLocalSearch={setLocalSearch} />
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
|
||||
{searchConfig('hideHeader', 0) !== 1
|
||||
? (
|
||||
<header className={classes.Header}>
|
||||
<p>{header.dateTime}</p>
|
||||
<Link to='/settings' className={classes.SettingsLink}>Go to Settings</Link>
|
||||
<span className={classes.HeaderMain}>
|
||||
<h1>{header.greeting}</h1>
|
||||
<WeatherWidget />
|
||||
</span>
|
||||
</header>
|
||||
)
|
||||
: <div></div>
|
||||
}
|
||||
|
||||
{searchConfig('hideApps', 0) !== 1
|
||||
? (<Fragment>
|
||||
<SectionHeadline title='Applications' link='/applications' />
|
||||
{appsLoading
|
||||
? <Spinner />
|
||||
: <AppGrid
|
||||
apps={apps.filter((app: App) => app.isPinned)}
|
||||
totalApps={apps.length}
|
||||
/>
|
||||
}
|
||||
<div className={classes.HomeSpace}></div>
|
||||
</Fragment>)
|
||||
: <div></div>
|
||||
}
|
||||
{searchConfig('hideHeader', 0) !== 1 ? (
|
||||
<header className={classes.Header}>
|
||||
<p>{header.dateTime}</p>
|
||||
<Link to="/settings" className={classes.SettingsLink}>
|
||||
Go to Settings
|
||||
</Link>
|
||||
<span className={classes.HeaderMain}>
|
||||
<h1>{header.greeting}</h1>
|
||||
<WeatherWidget />
|
||||
</span>
|
||||
</header>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
|
||||
{searchConfig('hideCategories', 0) !== 1
|
||||
? (<Fragment>
|
||||
<SectionHeadline title='Bookmarks' link='/bookmarks' />
|
||||
{categoriesLoading
|
||||
? <Spinner />
|
||||
: <BookmarkGrid
|
||||
categories={categories.filter((category: Category) => category.isPinned)}
|
||||
totalCategories={categories.length}
|
||||
/>
|
||||
}
|
||||
</Fragment>)
|
||||
: <div></div>
|
||||
}
|
||||
{searchConfig('hideApps', 0) !== 1 ? (
|
||||
<Fragment>
|
||||
<SectionHeadline title="Applications" link="/applications" />
|
||||
{appsLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<AppGrid
|
||||
apps={
|
||||
!localSearch
|
||||
? apps.filter(({ isPinned }) => isPinned)
|
||||
: apps.filter(({ name }) =>
|
||||
new RegExp(localSearch, 'i').test(name)
|
||||
)
|
||||
}
|
||||
totalApps={apps.length}
|
||||
searching={!!localSearch}
|
||||
/>
|
||||
)}
|
||||
<div className={classes.HomeSpace}></div>
|
||||
</Fragment>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
|
||||
<Link to='/settings' className={classes.SettingsButton}>
|
||||
<Icon icon='mdiCog' color='var(--color-background)' />
|
||||
{searchConfig('hideCategories', 0) !== 1 ? (
|
||||
<Fragment>
|
||||
<SectionHeadline title="Bookmarks" link="/bookmarks" />
|
||||
{categoriesLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<BookmarkGrid
|
||||
categories={categories.filter(
|
||||
(category: Category) => category.isPinned
|
||||
)}
|
||||
totalCategories={categories.length}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
|
||||
<Link to="/settings" className={classes.SettingsButton}>
|
||||
<Icon icon="mdiCog" color="var(--color-background)" />
|
||||
</Link>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
appsLoading: state.app.loading,
|
||||
apps: state.app.apps,
|
||||
categoriesLoading: state.bookmark.loading,
|
||||
categories: state.bookmark.categories
|
||||
}
|
||||
}
|
||||
categories: state.bookmark.categories,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { getApps, getCategories })(Home);
|
||||
export default connect(mapStateToProps, { getApps, getCategories })(Home);
|
||||
|
|
|
@ -15,36 +15,41 @@ import { searchParser } from '../../utility';
|
|||
|
||||
interface ComponentProps {
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
setLocalSearch: (query: string) => void;
|
||||
}
|
||||
|
||||
const SearchBar = (props: ComponentProps): JSX.Element => {
|
||||
const { setLocalSearch, createNotification } = props;
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(document.createElement('input'));
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current.focus();
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
const searchHandler = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.code === 'Enter') {
|
||||
const prefixFound = searchParser(inputRef.current.value);
|
||||
const searchResult = searchParser(inputRef.current.value);
|
||||
|
||||
if (!prefixFound) {
|
||||
props.createNotification({
|
||||
if (!searchResult.prefix) {
|
||||
createNotification({
|
||||
title: 'Error',
|
||||
message: 'Prefix not found'
|
||||
})
|
||||
message: 'Prefix not found',
|
||||
});
|
||||
} else if (searchResult.isLocal) {
|
||||
setLocalSearch(searchResult.query);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<input
|
||||
ref={inputRef}
|
||||
type='text'
|
||||
type="text"
|
||||
className={classes.SearchBar}
|
||||
onKeyDown={(e) => searchHandler(e)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(null, { createNotification })(SearchBar);
|
||||
export default connect(null, { createNotification })(SearchBar);
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
createNotification,
|
||||
updateConfig,
|
||||
sortApps,
|
||||
sortCategories
|
||||
sortCategories,
|
||||
} from '../../../store/actions';
|
||||
|
||||
// Typescript
|
||||
|
@ -14,7 +14,7 @@ import {
|
|||
GlobalState,
|
||||
NewNotification,
|
||||
Query,
|
||||
SettingsForm
|
||||
SettingsForm,
|
||||
} from '../../../interfaces';
|
||||
|
||||
// UI
|
||||
|
@ -53,7 +53,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
searchSameTab: 0,
|
||||
dockerApps: 1,
|
||||
kubernetesApps: 1,
|
||||
unpinStoppedApps: 1
|
||||
unpinStoppedApps: 1,
|
||||
});
|
||||
|
||||
// Get config
|
||||
|
@ -73,7 +73,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
searchSameTab: searchConfig('searchSameTab', 0),
|
||||
dockerApps: searchConfig('dockerApps', 0),
|
||||
kubernetesApps: searchConfig('kubernetesApps', 0),
|
||||
unpinStoppedApps: searchConfig('unpinStoppedApps', 0)
|
||||
unpinStoppedApps: searchConfig('unpinStoppedApps', 0),
|
||||
});
|
||||
}, [props.loading]);
|
||||
|
||||
|
@ -105,115 +105,117 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: value
|
||||
[e.target.name]: value,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={e => formSubmitHandler(e)}>
|
||||
<form onSubmit={(e) => formSubmitHandler(e)}>
|
||||
{/* OTHER OPTIONS */}
|
||||
<h2 className={classes.SettingsSection}>Miscellaneous</h2>
|
||||
<InputGroup>
|
||||
<label htmlFor='customTitle'>Custom page title</label>
|
||||
<label htmlFor="customTitle">Custom page title</label>
|
||||
<input
|
||||
type='text'
|
||||
id='customTitle'
|
||||
name='customTitle'
|
||||
placeholder='Flame'
|
||||
type="text"
|
||||
id="customTitle"
|
||||
name="customTitle"
|
||||
placeholder="Flame"
|
||||
value={formData.customTitle}
|
||||
onChange={e => inputChangeHandler(e)}
|
||||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
{/* BEAHVIOR OPTIONS */}
|
||||
<h2 className={classes.SettingsSection}>App Behavior</h2>
|
||||
<InputGroup>
|
||||
<label htmlFor='pinAppsByDefault'>
|
||||
<label htmlFor="pinAppsByDefault">
|
||||
Pin new applications by default
|
||||
</label>
|
||||
<select
|
||||
id='pinAppsByDefault'
|
||||
name='pinAppsByDefault'
|
||||
id="pinAppsByDefault"
|
||||
name="pinAppsByDefault"
|
||||
value={formData.pinAppsByDefault}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor='pinCategoriesByDefault'>
|
||||
<label htmlFor="pinCategoriesByDefault">
|
||||
Pin new categories by default
|
||||
</label>
|
||||
<select
|
||||
id='pinCategoriesByDefault'
|
||||
name='pinCategoriesByDefault'
|
||||
id="pinCategoriesByDefault"
|
||||
name="pinCategoriesByDefault"
|
||||
value={formData.pinCategoriesByDefault}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor='useOrdering'>Sorting type</label>
|
||||
<label htmlFor="useOrdering">Sorting type</label>
|
||||
<select
|
||||
id='useOrdering'
|
||||
name='useOrdering'
|
||||
id="useOrdering"
|
||||
name="useOrdering"
|
||||
value={formData.useOrdering}
|
||||
onChange={e => inputChangeHandler(e)}
|
||||
onChange={(e) => inputChangeHandler(e)}
|
||||
>
|
||||
<option value='createdAt'>By creation date</option>
|
||||
<option value='name'>Alphabetical order</option>
|
||||
<option value='orderId'>Custom order</option>
|
||||
<option value="createdAt">By creation date</option>
|
||||
<option value="name">Alphabetical order</option>
|
||||
<option value="orderId">Custom order</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor='defaultSearchProvider'>Default Search Provider</label>
|
||||
<label htmlFor="defaultSearchProvider">Default Search Provider</label>
|
||||
<select
|
||||
id='defaultSearchProvider'
|
||||
name='defaultSearchProvider'
|
||||
id="defaultSearchProvider"
|
||||
name="defaultSearchProvider"
|
||||
value={formData.defaultSearchProvider}
|
||||
onChange={e => inputChangeHandler(e)}
|
||||
onChange={(e) => inputChangeHandler(e)}
|
||||
>
|
||||
{queries.map((query: Query) => (
|
||||
<option value={query.prefix}>{query.name}</option>
|
||||
{queries.map((query: Query, idx) => (
|
||||
<option key={idx} value={query.prefix}>
|
||||
{query.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor='searchSameTab'>
|
||||
<label htmlFor="searchSameTab">
|
||||
Open search results in the same tab
|
||||
</label>
|
||||
<select
|
||||
id='searchSameTab'
|
||||
name='searchSameTab'
|
||||
id="searchSameTab"
|
||||
name="searchSameTab"
|
||||
value={formData.searchSameTab}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor='appsSameTab'>Open applications in the same tab</label>
|
||||
<label htmlFor="appsSameTab">Open applications in the same tab</label>
|
||||
<select
|
||||
id='appsSameTab'
|
||||
name='appsSameTab'
|
||||
id="appsSameTab"
|
||||
name="appsSameTab"
|
||||
value={formData.appsSameTab}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor='bookmarksSameTab'>Open bookmarks in the same tab</label>
|
||||
<label htmlFor="bookmarksSameTab">Open bookmarks in the same tab</label>
|
||||
<select
|
||||
id='bookmarksSameTab'
|
||||
name='bookmarksSameTab'
|
||||
id="bookmarksSameTab"
|
||||
name="bookmarksSameTab"
|
||||
value={formData.bookmarksSameTab}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
|
@ -223,48 +225,48 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
{/* MODULES OPTIONS */}
|
||||
<h2 className={classes.SettingsSection}>Modules</h2>
|
||||
<InputGroup>
|
||||
<label htmlFor='hideSearch'>Hide search bar</label>
|
||||
<label htmlFor="hideSearch">Hide search bar</label>
|
||||
<select
|
||||
id='hideSearch'
|
||||
name='hideSearch'
|
||||
id="hideSearch"
|
||||
name="hideSearch"
|
||||
value={formData.hideSearch}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor='hideHeader'>Hide greeting and date</label>
|
||||
<label htmlFor="hideHeader">Hide greeting and date</label>
|
||||
<select
|
||||
id='hideHeader'
|
||||
name='hideHeader'
|
||||
id="hideHeader"
|
||||
name="hideHeader"
|
||||
value={formData.hideHeader}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor='hideApps'>Hide applications</label>
|
||||
<label htmlFor="hideApps">Hide applications</label>
|
||||
<select
|
||||
id='hideApps'
|
||||
name='hideApps'
|
||||
id="hideApps"
|
||||
name="hideApps"
|
||||
value={formData.hideApps}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor='hideCategories'>Hide categories</label>
|
||||
<label htmlFor="hideCategories">Hide categories</label>
|
||||
<select
|
||||
id='hideCategories'
|
||||
name='hideCategories'
|
||||
id="hideCategories"
|
||||
name="hideCategories"
|
||||
value={formData.hideCategories}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
|
@ -274,26 +276,26 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
{/* DOCKER SETTINGS */}
|
||||
<h2 className={classes.SettingsSection}>Docker</h2>
|
||||
<InputGroup>
|
||||
<label htmlFor='dockerApps'>Use Docker API</label>
|
||||
<label htmlFor="dockerApps">Use Docker API</label>
|
||||
<select
|
||||
id='dockerApps'
|
||||
name='dockerApps'
|
||||
id="dockerApps"
|
||||
name="dockerApps"
|
||||
value={formData.dockerApps}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor='unpinStoppedApps'>
|
||||
<label htmlFor="unpinStoppedApps">
|
||||
Unpin stopped containers / other apps
|
||||
</label>
|
||||
<select
|
||||
id='unpinStoppedApps'
|
||||
name='unpinStoppedApps'
|
||||
id="unpinStoppedApps"
|
||||
name="unpinStoppedApps"
|
||||
value={formData.unpinStoppedApps}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
|
@ -303,12 +305,12 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
{/* KUBERNETES SETTINGS */}
|
||||
<h2 className={classes.SettingsSection}>Kubernetes</h2>
|
||||
<InputGroup>
|
||||
<label htmlFor='kubernetesApps'>Use Kubernetes Ingress API</label>
|
||||
<label htmlFor="kubernetesApps">Use Kubernetes Ingress API</label>
|
||||
<select
|
||||
id='kubernetesApps'
|
||||
name='kubernetesApps'
|
||||
id="kubernetesApps"
|
||||
name="kubernetesApps"
|
||||
value={formData.kubernetesApps}
|
||||
onChange={e => inputChangeHandler(e, true)}
|
||||
onChange={(e) => inputChangeHandler(e, true)}
|
||||
>
|
||||
<option value={1}>True</option>
|
||||
<option value={0}>False</option>
|
||||
|
@ -321,7 +323,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
loading: state.config.loading
|
||||
loading: state.config.loading,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -329,7 +331,7 @@ const actions = {
|
|||
createNotification,
|
||||
updateConfig,
|
||||
sortApps,
|
||||
sortCategories
|
||||
sortCategories,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, actions)(OtherSettings);
|
||||
|
|
5
client/src/interfaces/SearchResult.ts
Normal file
5
client/src/interfaces/SearchResult.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export interface SearchResult {
|
||||
isLocal: boolean;
|
||||
prefix: null | string;
|
||||
query: string;
|
||||
}
|
|
@ -8,4 +8,5 @@ export * from './Category';
|
|||
export * from './Notification';
|
||||
export * from './Config';
|
||||
export * from './Forms';
|
||||
export * from './Query';
|
||||
export * from './Query';
|
||||
export * from './SearchResult';
|
||||
|
|
|
@ -1,26 +1,45 @@
|
|||
import { queries } from './searchQueries.json';
|
||||
import { Query } from '../interfaces';
|
||||
import { Query, SearchResult } from '../interfaces';
|
||||
|
||||
import { searchConfig } from '.';
|
||||
|
||||
export const searchParser = (searchQuery: string): boolean => {
|
||||
export const searchParser = (searchQuery: string): SearchResult => {
|
||||
const result: SearchResult = {
|
||||
isLocal: false,
|
||||
prefix: null,
|
||||
query: '',
|
||||
};
|
||||
|
||||
const splitQuery = searchQuery.match(/^\/([a-z]+)[ ](.+)$/i);
|
||||
const prefix = splitQuery ? splitQuery[1] : searchConfig('defaultSearchProvider', 'd');
|
||||
const search = splitQuery ? encodeURIComponent(splitQuery[2]) : encodeURIComponent(searchQuery);
|
||||
|
||||
const prefix = splitQuery
|
||||
? splitQuery[1]
|
||||
: searchConfig('defaultSearchProvider', 'l');
|
||||
|
||||
const search = splitQuery
|
||||
? encodeURIComponent(splitQuery[2])
|
||||
: encodeURIComponent(searchQuery);
|
||||
|
||||
const query = queries.find((q: Query) => q.prefix === prefix);
|
||||
|
||||
if (query) {
|
||||
const sameTab = searchConfig('searchSameTab', false);
|
||||
result.prefix = query.prefix;
|
||||
result.query = search;
|
||||
|
||||
if (sameTab) {
|
||||
document.location.replace(`${query.template}${search}`);
|
||||
if (prefix === 'l') {
|
||||
result.isLocal = true;
|
||||
} else {
|
||||
window.open(`${query.template}${search}`);
|
||||
const sameTab = searchConfig('searchSameTab', false);
|
||||
|
||||
if (sameTab) {
|
||||
document.location.replace(`${query.template}${search}`);
|
||||
} else {
|
||||
window.open(`${query.template}${search}`);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -25,6 +25,11 @@
|
|||
"prefix": "im",
|
||||
"template": "https://www.imdb.com/find?q="
|
||||
},
|
||||
{
|
||||
"name": "Local search",
|
||||
"prefix": "l",
|
||||
"template": "#"
|
||||
},
|
||||
{
|
||||
"name": "Reddit",
|
||||
"prefix": "r",
|
||||
|
|
4536
package-lock.json
generated
4536
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
@ -5,13 +5,13 @@
|
|||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"init-server": "echo Instaling server dependencies && npm install && mkdir public && touch public/flame.css",
|
||||
"init-client": "cd client && echo Instaling client dependencies && npm install",
|
||||
"dev-init": "npm run init-server && npm run init-client",
|
||||
"dev-server": "nodemon server.js",
|
||||
"dev-client": "npm start --prefix client",
|
||||
"dev": "concurrently \"npm run dev-server\" \"npm run dev-client\"",
|
||||
"skaffold": "concurrently \"npm run init-client\" \"npm run dev-server\""
|
||||
"init:server": "echo Instaling server dependencies && npm install && mkdir public && touch public/flame.css",
|
||||
"init:client": "cd client && echo Instaling client dependencies && npm install",
|
||||
"dev:init": "npm run init:server && npm run init:client",
|
||||
"dev:server": "nodemon server.js",
|
||||
"dev:client": "npm start --prefix client",
|
||||
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
|
||||
"skaffold": "concurrently \"npm run init:client\" \"npm run dev:server\""
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
|
|
|
@ -9,27 +9,27 @@ const initConfig = async () => {
|
|||
const configPairs = await Config.findAll({
|
||||
where: {
|
||||
key: {
|
||||
[Op.or]: config.map(pair => pair.key)
|
||||
}
|
||||
}
|
||||
})
|
||||
[Op.or]: config.map((pair) => pair.key),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Get key from each pair
|
||||
const configKeys = configPairs.map((pair) => pair.key);
|
||||
|
||||
// Create missing pairs
|
||||
config.forEach(async ({ key, value}) => {
|
||||
config.forEach(async ({ key, value }) => {
|
||||
if (!configKeys.includes(key)) {
|
||||
await Config.create({
|
||||
key,
|
||||
value,
|
||||
valueType: typeof value
|
||||
})
|
||||
valueType: typeof value,
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
logger.log('Initial config created');
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = initConfig;
|
||||
module.exports = initConfig;
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
},
|
||||
{
|
||||
"key": "defaultSearchProvider",
|
||||
"value": "d"
|
||||
"value": "l"
|
||||
},
|
||||
{
|
||||
"key": "dockerApps",
|
||||
|
|
Loading…
Reference in a new issue