1
0
Fork 0
mirror of https://github.com/pawelmalak/flame.git synced 2025-07-19 11:39:36 +02:00

Merge pull request #128 from pawelmalak/feature

Version 1.7.2
This commit is contained in:
pawelmalak 2021-10-28 12:43:00 +02:00 committed by GitHub
commit 3dd255f359
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 155 additions and 49 deletions

2
.env
View file

@ -1,3 +1,3 @@
PORT=5005 PORT=5005
NODE_ENV=development NODE_ENV=development
VERSION=1.7.1 VERSION=1.7.2

View file

@ -1,3 +1,9 @@
### v1.7.2 (2021-10-28)
- Pressing Enter while search bar is focused will now redirect to first result of local search ([#121](https://github.com/pawelmalak/flame/issues/121))
- Use search bar shortcuts when it's not focused ([#124](https://github.com/pawelmalak/flame/issues/124))
- Fixed bug with Weather API still logging with module being disabled ([#125](https://github.com/pawelmalak/flame/issues/125))
- Added option to disable search bar autofocus ([#127](https://github.com/pawelmalak/flame/issues/127))
### v1.7.1 (2021-10-22) ### v1.7.1 (2021-10-22)
- Fixed search action not being triggered by Numpad Enter - Fixed search action not being triggered by Numpad Enter
- Added option to change date formatting ([#92](https://github.com/pawelmalak/flame/issues/92)) - Added option to change date formatting ([#92](https://github.com/pawelmalak/flame/issues/92))

10
DEV_GUIDELINES.md Normal file
View file

@ -0,0 +1,10 @@
## Adding new config key
1. Edit utils/init/initialConfig.json
2. Edit client/src/interfaces/Config.ts
3. Edit client/src/utility/templateObjects/configTemplate.ts
If config value will be used in a form:
4. Edit client/src/interfaces/Forms.ts
5. Edit client/src/utility/templateObjects/settingsTemplate.ts

View file

@ -1 +1 @@
REACT_APP_VERSION=1.7.1 REACT_APP_VERSION=1.7.2

View file

@ -55,17 +55,21 @@ const Home = (props: ComponentProps): JSX.Element => {
// Local search query // Local search query
const [localSearch, setLocalSearch] = useState<null | string>(null); const [localSearch, setLocalSearch] = useState<null | string>(null);
const [appSearchResult, setAppSearchResult] = useState<null | App[]>(null);
const [bookmarkSearchResult, setBookmarkSearchResult] = useState<
null | Category[]
>(null);
// Load applications // Load applications
useEffect(() => { useEffect(() => {
if (apps.length === 0) { if (!apps.length) {
getApps(); getApps();
} }
}, [getApps]); }, [getApps]);
// Load bookmark categories // Load bookmark categories
useEffect(() => { useEffect(() => {
if (categories.length === 0) { if (!categories.length) {
getCategories(); getCategories();
} }
}, [getCategories]); }, [getCategories]);
@ -87,22 +91,37 @@ const Home = (props: ComponentProps): JSX.Element => {
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
// Search bookmarks useEffect(() => {
const searchBookmarks = (query: string): Category[] => { if (localSearch) {
// Search through apps
setAppSearchResult([
...apps.filter(({ name }) => new RegExp(localSearch, 'i').test(name)),
]);
// Search through bookmarks
const category = { ...categories[0] }; const category = { ...categories[0] };
category.name = 'Search Results'; category.name = 'Search Results';
category.bookmarks = categories category.bookmarks = categories
.map(({ bookmarks }) => bookmarks) .map(({ bookmarks }) => bookmarks)
.flat() .flat()
.filter(({ name }) => new RegExp(query, 'i').test(name)); .filter(({ name }) => new RegExp(localSearch, 'i').test(name));
return [category]; setBookmarkSearchResult([category]);
}; } else {
setAppSearchResult(null);
setBookmarkSearchResult(null);
}
}, [localSearch]);
return ( return (
<Container> <Container>
{!props.config.hideSearch ? ( {!props.config.hideSearch ? (
<SearchBar setLocalSearch={setLocalSearch} /> <SearchBar
setLocalSearch={setLocalSearch}
appSearchResult={appSearchResult}
bookmarkSearchResult={bookmarkSearchResult}
/>
) : ( ) : (
<div></div> <div></div>
)} )}
@ -130,11 +149,9 @@ const Home = (props: ComponentProps): JSX.Element => {
) : ( ) : (
<AppGrid <AppGrid
apps={ apps={
!localSearch !appSearchResult
? apps.filter(({ isPinned }) => isPinned) ? apps.filter(({ isPinned }) => isPinned)
: apps.filter(({ name }) => : appSearchResult
new RegExp(localSearch, 'i').test(name)
)
} }
totalApps={apps.length} totalApps={apps.length}
searching={!!localSearch} searching={!!localSearch}
@ -154,9 +171,9 @@ const Home = (props: ComponentProps): JSX.Element => {
) : ( ) : (
<BookmarkGrid <BookmarkGrid
categories={ categories={
!localSearch !bookmarkSearchResult
? categories.filter(({ isPinned }) => isPinned) ? categories.filter(({ isPinned }) => isPinned)
: searchBookmarks(localSearch) : bookmarkSearchResult
} }
totalCategories={categories.length} totalCategories={categories.length}
searching={!!localSearch} searching={!!localSearch}

View file

@ -5,7 +5,13 @@ import { connect } from 'react-redux';
import { createNotification } from '../../store/actions'; import { createNotification } from '../../store/actions';
// Typescript // Typescript
import { NewNotification } from '../../interfaces'; import {
App,
Category,
Config,
GlobalState,
NewNotification,
} from '../../interfaces';
// CSS // CSS
import classes from './SearchBar.module.css'; import classes from './SearchBar.module.css';
@ -16,15 +22,44 @@ import { searchParser, urlParser, redirectUrl } from '../../utility';
interface ComponentProps { interface ComponentProps {
createNotification: (notification: NewNotification) => void; createNotification: (notification: NewNotification) => void;
setLocalSearch: (query: string) => void; setLocalSearch: (query: string) => void;
appSearchResult: App[] | null;
bookmarkSearchResult: Category[] | null;
config: Config;
loading: boolean;
} }
const SearchBar = (props: ComponentProps): JSX.Element => { const SearchBar = (props: ComponentProps): JSX.Element => {
const { setLocalSearch, createNotification } = props; const {
setLocalSearch,
createNotification,
config,
loading,
appSearchResult,
bookmarkSearchResult,
} = props;
const inputRef = useRef<HTMLInputElement>(document.createElement('input')); const inputRef = useRef<HTMLInputElement>(document.createElement('input'));
// Search bar autofocus
useEffect(() => { useEffect(() => {
if (!loading && !config.disableAutofocus) {
inputRef.current.focus(); inputRef.current.focus();
}
}, [config]);
// Listen for keyboard events outside of search bar
useEffect(() => {
const keyOutsideFocus = (e: any) => {
const { key } = e as KeyboardEvent;
if (key === 'Escape') {
clearSearch();
}
};
window.addEventListener('keydown', keyOutsideFocus);
return () => window.removeEventListener('keydown', keyOutsideFocus);
}, []); }, []);
const clearSearch = () => { const clearSearch = () => {
@ -53,8 +88,12 @@ const SearchBar = (props: ComponentProps): JSX.Element => {
const url = urlParser(inputRef.current.value)[1]; const url = urlParser(inputRef.current.value)[1];
redirectUrl(url, sameTab); redirectUrl(url, sameTab);
} else if (isLocal) { } else if (isLocal) {
// Local query -> filter apps and bookmarks // Local query -> redirect if at least 1 result found
setLocalSearch(search); if (appSearchResult?.length) {
redirectUrl(appSearchResult[0].url, sameTab);
} else if (bookmarkSearchResult?.length) {
redirectUrl(bookmarkSearchResult[0].bookmarks[0].url, sameTab);
}
} else { } else {
// Valid query -> redirect to search results // Valid query -> redirect to search results
const url = `${query.template}${search}`; const url = `${query.template}${search}`;
@ -78,4 +117,11 @@ const SearchBar = (props: ComponentProps): JSX.Element => {
); );
}; };
export default connect(null, { createNotification })(SearchBar); const mapStateToProps = (state: GlobalState) => {
return {
config: state.config.config,
loading: state.config.loading,
};
};
export default connect(mapStateToProps, { createNotification })(SearchBar);

View file

@ -121,6 +121,18 @@ const SearchSettings = (props: Props): JSX.Element => {
<option value={0}>False</option> <option value={0}>False</option>
</select> </select>
</InputGroup> </InputGroup>
<InputGroup>
<label htmlFor="disableAutofocus">Disable search bar autofocus</label>
<select
id="disableAutofocus"
name="disableAutofocus"
value={formData.disableAutofocus ? 1 : 0}
onChange={(e) => inputChangeHandler(e, { isBool: true })}
>
<option value={1}>True</option>
<option value={0}>False</option>
</select>
</InputGroup>
<Button>Save changes</Button> <Button>Save changes</Button>
</form> </form>

View file

@ -20,4 +20,5 @@ export interface Config {
kubernetesApps: boolean; kubernetesApps: boolean;
unpinStoppedApps: boolean; unpinStoppedApps: boolean;
useAmericanDate: boolean; useAmericanDate: boolean;
disableAutofocus: boolean;
} }

View file

@ -9,6 +9,7 @@ export interface SearchForm {
hideSearch: boolean; hideSearch: boolean;
defaultSearchProvider: string; defaultSearchProvider: string;
searchSameTab: boolean; searchSameTab: boolean;
disableAutofocus: boolean;
} }
export interface OtherSettingsForm { export interface OtherSettingsForm {

View file

@ -1,7 +1,11 @@
import { urlParser } from '.';
export const redirectUrl = (url: string, sameTab: boolean) => { export const redirectUrl = (url: string, sameTab: boolean) => {
const parsedUrl = urlParser(url)[1];
if (sameTab) { if (sameTab) {
document.location.replace(url); document.location.replace(parsedUrl);
} else { } else {
window.open(url); window.open(parsedUrl);
} }
}; };

View file

@ -22,4 +22,5 @@ export const configTemplate: Config = {
kubernetesApps: false, kubernetesApps: false,
unpinStoppedApps: false, unpinStoppedApps: false,
useAmericanDate: false, useAmericanDate: false,
disableAutofocus: false,
}; };

View file

@ -28,4 +28,5 @@ export const searchSettingsTemplate: SearchForm = {
hideSearch: false, hideSearch: false,
searchSameTab: false, searchSameTab: false,
defaultSearchProvider: 'l', defaultSearchProvider: 'l',
disableAutofocus: false,
}; };

View file

@ -5,14 +5,6 @@ const loadConfig = require('./loadConfig');
const getExternalWeather = async () => { const getExternalWeather = async () => {
const { WEATHER_API_KEY: secret, lat, long } = await loadConfig(); const { WEATHER_API_KEY: secret, lat, long } = await loadConfig();
if (!secret) {
throw new Error('API key was not found. Weather updated failed');
}
if (!lat || !long) {
throw new Error('Location was not found. Weather updated failed');
}
// Fetch data from external API // Fetch data from external API
try { try {
const res = await axios.get( const res = await axios.get(

View file

@ -18,5 +18,7 @@
"dockerApps": false, "dockerApps": false,
"dockerHost": "localhost", "dockerHost": "localhost",
"kubernetesApps": false, "kubernetesApps": false,
"unpinStoppedApps": false "unpinStoppedApps": false,
"useAmericanDate": false,
"disableAutofocus": false
} }

View file

@ -3,20 +3,33 @@ const getExternalWeather = require('./getExternalWeather');
const clearWeatherData = require('./clearWeatherData'); const clearWeatherData = require('./clearWeatherData');
const Sockets = require('../Sockets'); const Sockets = require('../Sockets');
const Logger = require('./Logger'); const Logger = require('./Logger');
const loadConfig = require('./loadConfig');
const logger = new Logger(); const logger = new Logger();
// Update weather data every 15 minutes // Update weather data every 15 minutes
const weatherJob = schedule.scheduleJob('updateWeather', '0 */15 * * * *', async () => { const weatherJob = schedule.scheduleJob(
'updateWeather',
'0 */15 * * * *',
async () => {
const { WEATHER_API_KEY: secret } = await loadConfig();
try { try {
const weatherData = await getExternalWeather(); const weatherData = await getExternalWeather();
logger.log('Weather updated'); logger.log('Weather updated');
Sockets.getSocket('weather').socket.send(JSON.stringify(weatherData)); Sockets.getSocket('weather').socket.send(JSON.stringify(weatherData));
} catch (err) { } catch (err) {
if (secret) {
logger.log(err.message, 'ERROR'); logger.log(err.message, 'ERROR');
} }
}) }
}
);
// Clear old weather data every 4 hours // Clear old weather data every 4 hours
const weatherCleanerJob = schedule.scheduleJob('clearWeather', '0 5 */4 * * *', async () => { const weatherCleanerJob = schedule.scheduleJob(
'clearWeather',
'0 5 */4 * * *',
async () => {
clearWeatherData(); clearWeatherData();
}) }
);