Jelajahi Sumber

Merge pull request #128 from pawelmalak/feature

Version 1.7.2
pawelmalak 3 tahun lalu
induk
melakukan
3dd255f359

+ 1 - 1
.env

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

+ 6 - 0
CHANGELOG.md

@@ -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)
 - Fixed search action not being triggered by Numpad Enter
 - Added option to change date formatting ([#92](https://github.com/pawelmalak/flame/issues/92))

+ 10 - 0
DEV_GUIDELINES.md

@@ -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

+ 1 - 1
client/.env

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

+ 37 - 20
client/src/components/Home/Home.tsx

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

+ 52 - 6
client/src/components/SearchBar/SearchBar.tsx

@@ -5,7 +5,13 @@ import { connect } from 'react-redux';
 import { createNotification } from '../../store/actions';
 
 // Typescript
-import { NewNotification } from '../../interfaces';
+import {
+  App,
+  Category,
+  Config,
+  GlobalState,
+  NewNotification,
+} from '../../interfaces';
 
 // CSS
 import classes from './SearchBar.module.css';
@@ -16,15 +22,44 @@ import { searchParser, urlParser, redirectUrl } from '../../utility';
 interface ComponentProps {
   createNotification: (notification: NewNotification) => void;
   setLocalSearch: (query: string) => void;
+  appSearchResult: App[] | null;
+  bookmarkSearchResult: Category[] | null;
+  config: Config;
+  loading: boolean;
 }
 
 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'));
 
+  // Search bar autofocus
   useEffect(() => {
-    inputRef.current.focus();
+    if (!loading && !config.disableAutofocus) {
+      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 = () => {
@@ -53,8 +88,12 @@ const SearchBar = (props: ComponentProps): JSX.Element => {
         const url = urlParser(inputRef.current.value)[1];
         redirectUrl(url, sameTab);
       } else if (isLocal) {
-        // Local query -> filter apps and bookmarks
-        setLocalSearch(search);
+        // Local query -> redirect if at least 1 result found
+        if (appSearchResult?.length) {
+          redirectUrl(appSearchResult[0].url, sameTab);
+        } else if (bookmarkSearchResult?.length) {
+          redirectUrl(bookmarkSearchResult[0].bookmarks[0].url, sameTab);
+        }
       } else {
         // Valid query -> redirect to search results
         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);

+ 12 - 0
client/src/components/Settings/SearchSettings/SearchSettings.tsx

@@ -121,6 +121,18 @@ const SearchSettings = (props: Props): JSX.Element => {
             <option value={0}>False</option>
           </select>
         </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>
       </form>
 

+ 1 - 0
client/src/interfaces/Config.ts

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

+ 1 - 0
client/src/interfaces/Forms.ts

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

+ 6 - 2
client/src/utility/redirectUrl.ts

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

+ 1 - 0
client/src/utility/templateObjects/configTemplate.ts

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

+ 1 - 0
client/src/utility/templateObjects/settingsTemplate.ts

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

+ 0 - 8
utils/getExternalWeather.js

@@ -5,14 +5,6 @@ const loadConfig = require('./loadConfig');
 const getExternalWeather = async () => {
   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
   try {
     const res = await axios.get(

+ 3 - 1
utils/init/initialConfig.json

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

+ 24 - 11
utils/jobs.js

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