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