diff --git a/.dockerignore b/.dockerignore index 5fcee18..da9bc10 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ node_modules github -public \ No newline at end of file +public +build.sh \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2955045..98ec862 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules data -public \ No newline at end of file +public +build.sh \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..56860ce --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,51 @@ +### v1.6.1 (2021-07-28) +- Added option to upload custom icons for bookmarks ([#52](https://github.com/pawelmalak/flame/issues/52)) +- Fixed custom icons not updating ([#58](https://github.com/pawelmalak/flame/issues/58)) +- Added changelog file + +### v1.6 (2021-07-17) +- Added support for Steam URLs ([#62](https://github.com/pawelmalak/flame/issues/62)) +- Fixed bug with custom CSS not persisting ([#64](https://github.com/pawelmalak/flame/issues/64)) +- Added option to set default prefix for search bar ([#65](https://github.com/pawelmalak/flame/issues/65)) + +### v1.5 (2021-06-24) +- Added ability to set custom CSS from settings ([#8](https://github.com/pawelmalak/flame/issues/8) and [#17](https://github.com/pawelmalak/flame/issues/17)) (experimental) +- Added option to upload custom icons ([#12](https://github.com/pawelmalak/flame/issues/12)) +- Added option to open links in a new or the same tab ([#27](https://github.com/pawelmalak/flame/issues/27)) +- Added Search bar with support for 3 search engines and 4 services ([#44](https://github.com/pawelmalak/flame/issues/44)) +- Added option to hide applications and categories ([#48](https://github.com/pawelmalak/flame/issues/48)) +- Improved Logger + +### v1.4 (2021-06-18) +- Added more sorting options. User can now choose to sort apps and categories by name, creation time or to use custom order ([#13](https://github.com/pawelmalak/flame/issues/13)) +- Added reordering functionality. User can now set custom order for apps and categories from their 'edit tables' ([#13](https://github.com/pawelmalak/flame/issues/13)) +- Changed get all controllers for applications and categories to use case-insensitive ordering ([#36](https://github.com/pawelmalak/flame/issues/36)) +- New apps will be placed correctly in the array depending on used sorting settings ([#37](https://github.com/pawelmalak/flame/issues/37)) +- Added app version to settings with option to check for updates manually ([#38](https://github.com/pawelmalak/flame/issues/38)) +- Added update check on app start ([#38](https://github.com/pawelmalak/flame/issues/38)) +- Fixed bug with decimal input values in Safari browser ([#40](https://github.com/pawelmalak/flame/issues/40)) + +### v1.3 (2021-06-14) +- Added reverse proxy support ([#23](https://github.com/pawelmalak/flame/issues/23) and [#24](https://github.com/pawelmalak/flame/issues/24)) +- Added support for more url formats ([#26](https://github.com/pawelmalak/flame/issues/26)) +- Added ability to hide main header ([#28](https://github.com/pawelmalak/flame/issues/28)) +- Fixed settings not being synchronized ([#29](https://github.com/pawelmalak/flame/issues/29)) +- Added auto-refresh for greeting and date ([#34](https://github.com/pawelmalak/flame/issues/34)) + +### v1.2 (2021-06-10) +- Added simple check to the weather module settings to inform user if the api key is missing ([#2](https://github.com/pawelmalak/flame/issues/2)) +- Added ability to set optional icons to the bookmarks ([#7](https://github.com/pawelmalak/flame/issues/7)) +- Added option to pin new applications and categories to the homescreen by default ([#11](https://github.com/pawelmalak/flame/issues/11)) +- Added background highlight while hovering over application card ([#15](https://github.com/pawelmalak/flame/issues/15)) +- Created CRON job to clear old weather data from the database ([#16](https://github.com/pawelmalak/flame/issues/16)) +- Added proxy for websocket instead of using hard coded host ([#18](https://github.com/pawelmalak/flame/issues/18)) +- Fixed bug with overwriting opened tabs ([#20](https://github.com/pawelmalak/flame/issues/20)) + +### v1.1 (2021-06-09) +- Added custom favicon and changed page title ([#3](https://github.com/pawelmalak/flame/issues/3)) +- Added functionality to set custom page title ([#3](https://github.com/pawelmalak/flame/issues/3)) +- Changed messages on the homescreen when there are apps/bookmarks created but not pinned to the homescreen ([#4](https://github.com/pawelmalak/flame/issues/4)) +- Added 'warnings' to apps and bookmarks forms about supported url formats ([#5](https://github.com/pawelmalak/flame/issues/5)) + +### v1.0 (2021-06-08) +Initial release of Flame - self-hosted startpage using Node.js on backend and React on frontend. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8016d8e..fed0789 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:14-alpine -RUN apk update && apk add --no-cache nano +RUN apk update && apk add --no-cache nano curl WORKDIR /app diff --git a/Dockerfile.multiarch b/Dockerfile.multiarch index 808b815..20ff6c2 100644 --- a/Dockerfile.multiarch +++ b/Dockerfile.multiarch @@ -1,6 +1,6 @@ FROM node:14-alpine -RUN apk update && apk add --no-cache nano +RUN apk update && apk add --no-cache nano curl WORKDIR /app diff --git a/client/.env b/client/.env index 036129f..f56a185 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.6.0 \ No newline at end of file +REACT_APP_VERSION=1.6.1 \ No newline at end of file diff --git a/client/src/components/Apps/AppCard/AppCard.tsx b/client/src/components/Apps/AppCard/AppCard.tsx index 43d7b72..79ad3d8 100644 --- a/client/src/components/Apps/AppCard/AppCard.tsx +++ b/client/src/components/Apps/AppCard/AppCard.tsx @@ -21,7 +21,7 @@ const AppCard = (props: ComponentProps): JSX.Element => { className={classes.AppCard} >
- {(/.(jpeg|jpg|png)$/).test(props.app.icon) + {(/.(jpeg|jpg|png)$/i).test(props.app.icon) ? {`${props.app.name} void; addApp: (formData: NewApp | FormData) => any; - updateApp: (id: number, formData: NewApp) => any; + updateApp: (id: number, formData: NewApp | FormData) => any; app?: App; } @@ -26,14 +25,6 @@ const AppForm = (props: ComponentProps): JSX.Element => { icon: '' }); - const inputRef = useRef(null); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, [inputRef]) - useEffect(() => { if (props.app) { setFormData({ @@ -66,21 +57,32 @@ const AppForm = (props: ComponentProps): JSX.Element => { const formSubmitHandler = (e: SyntheticEvent): void => { e.preventDefault(); + const createFormData = (): FormData => { + const data = new FormData(); + if (customIcon) { + data.append('icon', customIcon); + } + data.append('name', formData.name); + data.append('url', formData.url); + + return data; + } + if (!props.app) { if (customIcon) { - const data = new FormData(); - data.append('icon', customIcon); - - data.append('name', formData.name); - data.append('url', formData.url); - + const data = createFormData(); props.addApp(data); } else { props.addApp(formData); } } else { - props.updateApp(props.app.id, formData); - props.modalHandler(); + if (customIcon) { + const data = createFormData(); + props.updateApp(props.app.id, data); + } else { + props.updateApp(props.app.id, formData); + props.modalHandler(); + } } setFormData({ @@ -88,6 +90,8 @@ const AppForm = (props: ComponentProps): JSX.Element => { url: '', icon: '' }) + + setCustomIcon(null); } return ( @@ -105,7 +109,6 @@ const AppForm = (props: ComponentProps): JSX.Element => { required value={formData.name} onChange={(e) => inputChangeHandler(e)} - ref={inputRef} /> diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx index f2535b5..fe2198b 100644 --- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx +++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx @@ -24,7 +24,14 @@ const BookmarkCard = (props: ComponentProps): JSX.Element => { key={`bookmark-${bookmark.id}`}> {bookmark.icon && (
- + {(/.(jpeg|jpg|png)$/i).test(bookmark.icon) + ? {`${bookmark.name} + : + }
)} {bookmark.name} diff --git a/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.module.css b/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.module.css new file mode 100644 index 0000000..66b15a0 --- /dev/null +++ b/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.module.css @@ -0,0 +1,7 @@ +.Switch { + text-decoration: underline; +} + +.Switch:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx b/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx index eb83013..67059ae 100644 --- a/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx +++ b/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx @@ -7,6 +7,7 @@ import { Bookmark, Category, GlobalState, NewBookmark, NewCategory, NewNotificat import { ContentType } from '../Bookmarks'; import { getCategories, addCategory, addBookmark, updateCategory, updateBookmark, createNotification } from '../../../store/actions'; import Button from '../../UI/Buttons/Button/Button'; +import classes from './BookmarkForm.module.css'; interface ComponentProps { modalHandler: () => void; @@ -15,13 +16,22 @@ interface ComponentProps { category?: Category; bookmark?: Bookmark; addCategory: (formData: NewCategory) => void; - addBookmark: (formData: NewBookmark) => void; + addBookmark: (formData: NewBookmark | FormData) => void; updateCategory: (id: number, formData: NewCategory) => void; - updateBookmark: (id: number, formData: NewBookmark, previousCategoryId: number) => void; + updateBookmark: ( + id: number, + formData: NewBookmark | FormData, + category: { + prev: number, + curr: number + } + ) => void; createNotification: (notification: NewNotification) => void; } const BookmarkForm = (props: ComponentProps): JSX.Element => { + const [useCustomIcon, toggleUseCustomIcon] = useState(false); + const [customIcon, setCustomIcon] = useState(null); const [categoryName, setCategoryName] = useState({ name: '' }) @@ -64,6 +74,18 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => { const formSubmitHandler = (e: SyntheticEvent): void => { e.preventDefault(); + const createFormData = (): FormData => { + const data = new FormData(); + if (customIcon) { + data.append('icon', customIcon); + } + data.append('name', formData.name); + data.append('url', formData.url); + data.append('categoryId', `${formData.categoryId}`); + + return data; + } + if (!props.category && !props.bookmark) { // Add new if (props.contentType === ContentType.category) { @@ -79,14 +101,22 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => { }) return; } - - props.addBookmark(formData); + + if (customIcon) { + const data = createFormData(); + props.addBookmark(data); + } else { + props.addBookmark(formData); + } + setFormData({ name: '', url: '', categoryId: formData.categoryId, icon: '' }) + + setCustomIcon(null) } } else { // Update @@ -96,13 +126,35 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => { setCategoryName({ name: '' }); } else if (props.contentType === ContentType.bookmark && props.bookmark) { // Update bookmark - props.updateBookmark(props.bookmark.id, formData, props.bookmark.categoryId); + if (customIcon) { + const data = createFormData(); + props.updateBookmark( + props.bookmark.id, + data, + { + prev: props.bookmark.categoryId, + curr: formData.categoryId + } + ) + } else { + props.updateBookmark( + props.bookmark.id, + formData, + { + prev: props.bookmark.categoryId, + curr: formData.categoryId + } + ); + } + setFormData({ name: '', url: '', categoryId: -1, icon: '' }) + + setCustomIcon(null) } props.modalHandler(); @@ -123,6 +175,12 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => { }) } + const fileChangeHandler = (e: ChangeEvent): void => { + if (e.target.files) { + setCustomIcon(e.target.files[0]); + } + } + let button = if (!props.category && !props.bookmark) { @@ -216,25 +274,49 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => { })}
- - - inputChangeHandler(e)} - /> - - Use icon name from MDI. - - {' '}Click here for reference - - - + {!useCustomIcon + // mdi + ? ( + + inputChangeHandler(e)} + /> + + Use icon name from MDI. + + {' '}Click here for reference + + + toggleUseCustomIcon(!useCustomIcon)} + className={classes.Switch}> + Switch to custom icon upload + + ) + // custom + : ( + + fileChangeHandler(e)} + accept='.jpg,.jpeg,.png' + /> + toggleUseCustomIcon(!useCustomIcon)} + className={classes.Switch}> + Switch to MDI + + ) + } ) } diff --git a/client/src/components/Settings/AppDetails/AppDetails.tsx b/client/src/components/Settings/AppDetails/AppDetails.tsx index 90fe2fb..50fd37f 100644 --- a/client/src/components/Settings/AppDetails/AppDetails.tsx +++ b/client/src/components/Settings/AppDetails/AppDetails.tsx @@ -17,6 +17,15 @@ const AppDetails = (): JSX.Element => { {' '} version {process.env.REACT_APP_VERSION}

+

+ See changelog {' '} + + here + +

) diff --git a/client/src/store/actions/bookmark.ts b/client/src/store/actions/bookmark.ts index 0398bbb..b4b5831 100644 --- a/client/src/store/actions/bookmark.ts +++ b/client/src/store/actions/bookmark.ts @@ -69,7 +69,7 @@ export interface AddBookmarkAction { payload: Bookmark } -export const addBookmark = (formData: NewBookmark) => async (dispatch: Dispatch) => { +export const addBookmark = (formData: NewBookmark | FormData) => async (dispatch: Dispatch) => { try { const res = await axios.post>('/api/bookmarks', formData); @@ -77,7 +77,7 @@ export const addBookmark = (formData: NewBookmark) => async (dispatch: Dispatch) type: ActionTypes.createNotification, payload: { title: 'Success', - message: `Bookmark ${formData.name} created` + message: `Bookmark created` } }) @@ -225,7 +225,14 @@ export interface UpdateBookmarkAction { payload: Bookmark } -export const updateBookmark = (bookmarkId: number, formData: NewBookmark, previousCategoryId: number) => async (dispatch: Dispatch) => { +export const updateBookmark = ( + bookmarkId: number, + formData: NewBookmark | FormData, + category: { + prev: number, + curr: number + } +) => async (dispatch: Dispatch) => { try { const res = await axios.put>(`/api/bookmarks/${bookmarkId}`, formData); @@ -233,12 +240,12 @@ export const updateBookmark = (bookmarkId: number, formData: NewBookmark, previo type: ActionTypes.createNotification, payload: { title: 'Success', - message: `Bookmark ${formData.name} updated` + message: `Bookmark updated` } }) // Check if category was changed - const categoryWasChanged = formData.categoryId !== previousCategoryId; + const categoryWasChanged = category.curr !== category.prev; if (categoryWasChanged) { // Delete bookmark from old category @@ -246,7 +253,7 @@ export const updateBookmark = (bookmarkId: number, formData: NewBookmark, previo type: ActionTypes.deleteBookmark, payload: { bookmarkId, - categoryId: previousCategoryId + categoryId: category.prev } }) @@ -256,7 +263,7 @@ export const updateBookmark = (bookmarkId: number, formData: NewBookmark, previo payload: res.data.data }) } else { - // Else update only name/url + // Else update only name/url/icon dispatch({ type: ActionTypes.updateBookmark, payload: res.data.data diff --git a/controllers/apps.js b/controllers/apps.js index 238c66b..92c1736 100644 --- a/controllers/apps.js +++ b/controllers/apps.js @@ -20,7 +20,6 @@ exports.createApp = asyncWrapper(async (req, res, next) => { _body.icon = req.file.filename; } - if (pinApps) { if (parseInt(pinApps.value)) { app = await App.create({ @@ -96,7 +95,13 @@ exports.updateApp = asyncWrapper(async (req, res, next) => { return next(new ErrorResponse(`App with id of ${req.params.id} was not found`, 404)); } - app = await app.update({ ...req.body }); + let _body = { ...req.body }; + + if (req.file) { + _body.icon = req.file.filename; + } + + app = await app.update(_body); res.status(200).json({ success: true, diff --git a/controllers/bookmark.js b/controllers/bookmark.js index 08b2fca..8077a8c 100644 --- a/controllers/bookmark.js +++ b/controllers/bookmark.js @@ -7,7 +7,18 @@ const { Sequelize } = require('sequelize'); // @route POST /api/bookmarks // @access Public exports.createBookmark = asyncWrapper(async (req, res, next) => { - const bookmark = await Bookmark.create(req.body); + let bookmark; + + let _body = { + ...req.body, + categoryId: parseInt(req.body.categoryId) + }; + + if (req.file) { + _body.icon = req.file.filename; + } + + bookmark = await Bookmark.create(_body); res.status(201).json({ success: true, @@ -59,7 +70,16 @@ exports.updateBookmark = asyncWrapper(async (req, res, next) => { return next(new ErrorResponse(`Bookmark with id of ${req.params.id} was not found`, 404)); } - bookmark = await bookmark.update({ ...req.body }); + let _body = { + ...req.body, + categoryId: parseInt(req.body.categoryId) + }; + + if (req.file) { + _body.icon = req.file.filename; + } + + bookmark = await bookmark.update(_body); res.status(200).json({ success: true, diff --git a/package.json b/package.json index 3150454..2716484 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start": "node server.js", - "init-server": "echo Instaling server dependencies && npm install", + "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", diff --git a/routes/apps.js b/routes/apps.js index 091550c..37c0286 100644 --- a/routes/apps.js +++ b/routes/apps.js @@ -19,7 +19,7 @@ router router .route('/:id') .get(getApp) - .put(updateApp) + .put(upload, updateApp) .delete(deleteApp); router diff --git a/routes/bookmark.js b/routes/bookmark.js index f0d62f4..c594738 100644 --- a/routes/bookmark.js +++ b/routes/bookmark.js @@ -1,5 +1,6 @@ const express = require('express'); const router = express.Router(); +const upload = require('../middleware/multer'); const { createBookmark, @@ -11,13 +12,13 @@ const { router .route('/') - .post(createBookmark) + .post(upload, createBookmark) .get(getBookmarks); router .route('/:id') .get(getBookmark) - .put(updateBookmark) + .put(upload, updateBookmark) .delete(deleteBookmark); module.exports = router; \ No newline at end of file