diff --git a/.storybook/main.js b/.storybook/main.js
index 80bc56834..e0e85fcfa 100644
--- a/.storybook/main.js
+++ b/.storybook/main.js
@@ -1,14 +1,9 @@
module.exports = {
stories: ['../src/components/**/*.story.mdx', '../src/components/**/*.story.*'],
addons: [
- 'storybook-dark-mode',
'@storybook/addon-links',
'storybook-addon-mock/register',
'@storybook/addon-essentials',
- {
- name: 'storybook-addon-turbo-build',
- options: { optimizationLevel: 2 },
- },
],
typescript: {
check: false,
diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
index d982cf344..5023bb98f 100644
--- a/.storybook/preview.tsx
+++ b/.storybook/preview.tsx
@@ -1,4 +1,3 @@
-import { useDarkMode } from 'storybook-dark-mode';
import { MantineProvider, ColorSchemeProvider } from '@mantine/core';
import { NotificationsProvider } from '@mantine/notifications';
@@ -7,11 +6,7 @@ export const parameters = { layout: 'fullscreen' };
function ThemeWrapper(props: { children: React.ReactNode }) {
return (
{}}>
-
+
{props.children}
diff --git a/README.md b/README.md
index 221858ee8..3c1ad52c0 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,9 @@
Homarr
+ Don't forget to star the repo if you enjoy the Homarr project!
+
+
@@ -9,7 +12,8 @@
-
+
+
A homepage for your server.
@@ -21,7 +25,7 @@
-
+
# ๐ Table of Contents
@@ -32,6 +36,8 @@
- [โก Installation](#-installation)
- [๐ณ Deploying from Docker Image](#-deploying-from-docker-image)
- [๐ ๏ธ Building from Source](#%EF%B8%8F-building-from-source)
+ - [๐ Guides](#-guides)
+ - [๐ Drag and Drop (Rearrange)](#-drag-and-drop-rearrange)
- [๐ง Configuration](#-configuration)
- [๐งฉ Integrations](#--integrations)
- [๐งโ๐คโ๐ง Multiple Configs](#-multiple-configs)
@@ -53,8 +59,6 @@ Homarr is a simple and lightweight homepage for your server, that helps you easi
## ๐ฅ Known Issues
- Posters on the Calendar get blocked by adblockers. (IMDb posters)
-- Editing a service creates a duplicate (#97)
-- Used search engine not properly selected (#35)
**[โคด๏ธ Back to Top](#-table-of-contents)**
@@ -106,6 +110,11 @@ _Requirements_:
- Start the NextJS web server: ``yarn start``
- *Note: If you want to update the code in real time, launch with ``yarn dev``*
+## ๐ Guides
+
+### ๐ Drag and Drop (Rearrange)
+You can rearrange items by Drag and Dropping them to any position. To Drag an Drop, click and hold an icon for 250ms and then drag it to the desired position.
+
## ๐ง Configuration
### ๐งฉ Integrations
diff --git a/data/configs/config.json b/data/configs/config.json
deleted file mode 100644
index b9c4ae388..000000000
--- a/data/configs/config.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "name": "config",
- "services": [
- {
- "type": "Other",
- "name": "YouTube",
- "icon": "https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/youtube.png",
- "url": "https://youtube.com/"
- },
- {
- "type": "Other",
- "name": "YouTube ",
- "icon": "https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/youtube.png",
- "url": "https://youtube.com/"
- }
- ],
- "settings": {
- "searchBar": true,
- "searchUrl": "Custom",
- "enabledModules": [
- "Date",
- "Calendar",
- "Weather"
- ]
- }
-}
\ No newline at end of file
diff --git a/data/configs/config_new.json b/data/configs/config_new.json
deleted file mode 100644
index 283a8b971..000000000
--- a/data/configs/config_new.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "config_new",
- "services": [
- {
- "type": "Other",
- "name": "example",
- "icon": "https://c.tenor.com/o656qFKDzeUAAAAC/rick-astley-never-gonna-give-you-up.gif",
- "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
- }
- ],
- "settings": {
- "searchBar": true,
- "searchUrl": "https://duckduckgo.com/?q=",
- "enabledModules": []
- }
-}
\ No newline at end of file
diff --git a/data/configs/default.json b/data/configs/default.json
index f976f31e8..8f606710e 100644
--- a/data/configs/default.json
+++ b/data/configs/default.json
@@ -2,15 +2,14 @@
"name": "default",
"services": [
{
- "type": "Other",
"name": "example",
+ "id": "09c45847-8afc-4c1a-9697-f03192de948a",
+ "type": "Other",
"icon": "https://c.tenor.com/o656qFKDzeUAAAAC/rick-astley-never-gonna-give-you-up.gif",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
],
"settings": {
- "searchBar": true,
- "searchUrl": "https://bing.com/search?q=",
- "enabledModules": []
+ "searchUrl": "https://bing.com/search?q="
}
}
\ No newline at end of file
diff --git a/data/constants.ts b/data/constants.ts
index 377bac1ff..9c9793518 100644
--- a/data/constants.ts
+++ b/data/constants.ts
@@ -1,2 +1,2 @@
export const REPO_URL = 'ajnart/homarr';
-export const CURRENT_VERSION = 'v0.4.0';
+export const CURRENT_VERSION = 'v0.5.0';
diff --git a/next.config.js b/next.config.js
index 9cc409a07..31fc7b641 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,13 +1,16 @@
+const { env } = require('process');
+
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
- reactStrictMode: true,
+ reactStrictMode: false,
eslint: {
ignoreDuringBuilds: true,
},
experimental: {
outputStandalone: true,
},
+ basePath: env.BASE_URL,
});
diff --git a/package.json b/package.json
index 8b110f68e..59d5c5b6d 100644
--- a/package.json
+++ b/package.json
@@ -1,91 +1,80 @@
{
"name": "homarr",
- "version": "0.4.0",
+ "version": "0.5.0",
"private": "false",
"description": "Homarr - A homepage for your server.",
"repository": {
"type": "git",
"url": "https://github.com/ajnart/homarr"
},
- "scripts": {
- "dev": "next dev",
- "build": "next build",
- "analyze": "ANALYZE=true next build",
- "start": "next start --port 7575",
- "typecheck": "tsc --noEmit",
- "export": "next build && next export",
- "lint": "next lint",
- "jest": "jest",
- "jest:watch": "jest --watch",
- "prettier:check": "prettier --check \"**/*.{ts,tsx}\"",
- "prettier:write": "prettier --write \"**/*.{ts,tsx}\"",
- "test": "npm run prettier:check && npm run lint && npm run typecheck && npm run jest",
- "storybook": "start-storybook -p 7001",
- "storybook:build": "build-storybook",
- "ci": "yarn test && yarn lint --fix && yarn typecheck && yarn prettier:write"
- },
- "dependencies": {
- "@mantine/core": "^4.2.4",
- "@mantine/dates": "^4.2.4",
- "@mantine/dropzone": "^4.2.4",
- "@mantine/form": "^4.2.4",
- "@mantine/hooks": "^4.2.4",
- "@mantine/modals": "^4.2.4",
- "@mantine/next": "^4.2.4",
- "@mantine/notifications": "^4.2.4",
- "@mantine/prism": "^4.2.4",
- "@mantine/rte": "^4.2.4",
- "@mantine/spotlight": "^4.2.4",
- "@modulz/radix-icons": "^4.0.0",
- "axios": "^0.27.2",
- "cookies-next": "^2.0.4",
- "dayjs": "^1.11.2",
- "framer-motion": "^6.3.1",
- "js-file-download": "^0.4.12",
- "next": "12.1.5-canary.4",
- "prism-react-renderer": "^1.3.1",
- "react": "18.0.0",
- "react-dom": "18.0.0",
- "tabler-icons-react": "^1.46.0"
- },
- "devDependencies": {
- "@babel/core": "^7.17.8",
- "@next/bundle-analyzer": "^12.1.4",
- "@next/eslint-plugin-next": "^12.1.4",
- "@storybook/addon-essentials": "^6.4.22",
- "@storybook/addon-links": "^6.4.22",
- "@storybook/react": "^6.4.22",
- "@testing-library/dom": "^8.12.0",
- "@testing-library/jest-dom": "^5.16.3",
- "@testing-library/react": "^13.0.0",
- "@testing-library/user-event": "^14.0.4",
- "@types/jest": "^27.4.1",
- "@types/node": "^17.0.23",
- "@types/react": "17.0.43",
- "@typescript-eslint/eslint-plugin": "^5.16.0",
- "@typescript-eslint/parser": "^5.16.0",
- "babel-loader": "^8.2.4",
- "eslint": "^8.11.0",
- "eslint-config-airbnb": "19.0.4",
- "eslint-config-airbnb-typescript": "^16.1.4",
- "eslint-config-mantine": "1.1.0",
- "eslint-plugin-import": "^2.25.4",
- "eslint-plugin-jest": "^26.1.3",
- "eslint-plugin-jsx-a11y": "^6.5.1",
- "eslint-plugin-react": "^7.29.4",
- "eslint-plugin-react-hooks": "^4.3.0",
- "eslint-plugin-storybook": "^0.5.11",
- "eslint-plugin-testing-library": "^5.2.0",
- "eslint-plugin-unused-imports": "^2.0.0",
- "jest": "^27.5.1",
- "prettier": "^2.6.2",
- "storybook-addon-mock": "^2.3.2",
- "storybook-addon-turbo-build": "^1.1.0",
- "storybook-dark-mode": "^1.0.9",
- "ts-jest": "^27.1.4",
- "typescript": "4.6.3"
- },
- "resolutions": {
- "@types/react": "17.0.30"
- }
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "analyze": "ANALYZE=true next build",
+ "start": "next start",
+ "typecheck": "tsc --noEmit",
+ "export": "next build && next export",
+ "lint": "next lint",
+ "jest": "jest",
+ "jest:watch": "jest --watch",
+ "prettier:check": "prettier --check \"**/*.{ts,tsx}\"",
+ "prettier:write": "prettier --write \"**/*.{ts,tsx}\"",
+ "test": "npm run prettier:check && npm run lint && npm run typecheck && npm run jest",
+ "storybook": "start-storybook -p 7001",
+ "storybook:build": "build-storybook",
+ "ci": "yarn test && yarn lint --fix && yarn typecheck && yarn prettier:write"
+ },
+ "dependencies": {
+ "@dnd-kit/core": "^6.0.1",
+ "@dnd-kit/sortable": "^7.0.0",
+ "@mantine/core": "^4.2.6",
+ "@mantine/dates": "^4.2.6",
+ "@mantine/dropzone": "^4.2.6",
+ "@mantine/form": "^4.2.6",
+ "@mantine/hooks": "^4.2.6",
+ "@mantine/next": "^4.2.6",
+ "@mantine/notifications": "^4.2.6",
+ "@mantine/prism": "^4.2.6",
+ "axios": "^0.27.2",
+ "cookies-next": "^2.0.4",
+ "dayjs": "^1.11.2",
+ "framer-motion": "^6.3.1",
+ "js-file-download": "^0.4.12",
+ "next": "12.1.6",
+ "prism-react-renderer": "^1.3.1",
+ "react": "^17.0.1",
+ "react-dom": "^17.0.1",
+ "tabler-icons-react": "^1.46.0",
+ "uuid": "^8.3.2"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.17.8",
+ "@next/bundle-analyzer": "^12.1.4",
+ "@next/eslint-plugin-next": "^12.1.4",
+ "@storybook/react": "^6.5.4",
+ "@types/node": "^17.0.23",
+ "@types/react": "17.0.43",
+ "@types/uuid": "^8.3.4",
+ "@typescript-eslint/eslint-plugin": "^5.16.0",
+ "@typescript-eslint/parser": "^5.16.0",
+ "eslint": "^8.11.0",
+ "eslint-config-airbnb": "^19.0.4",
+ "eslint-config-airbnb-typescript": "^16.1.0",
+ "eslint-config-mantine": "1.1.0",
+ "eslint-plugin-import": "^2.25.4",
+ "eslint-plugin-jest": "^26.1.3",
+ "eslint-plugin-jsx-a11y": "^6.5.1",
+ "eslint-plugin-react": "^7.29.4",
+ "eslint-plugin-react-hooks": "^4.3.0",
+ "eslint-plugin-storybook": "^0.5.11",
+ "eslint-plugin-testing-library": "^5.2.0",
+ "eslint-plugin-unused-imports": "^2.0.0",
+ "jest": "^28.1.0",
+ "prettier": "^2.6.2",
+ "require-from-string": "^2.0.2",
+ "typescript": "4.6.4"
+ },
+ "resolutions": {
+ "@types/react": "17.0.30"
+ }
}
diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx
index ccc6569f2..c061930e9 100644
--- a/src/components/AppShelf/AddAppShelfItem.tsx
+++ b/src/components/AppShelf/AddAppShelfItem.tsx
@@ -6,21 +6,17 @@ import {
Image,
Button,
Select,
- AspectRatio,
- Text,
- Card,
LoadingOverlay,
ActionIcon,
Tooltip,
Title,
} from '@mantine/core';
import { useForm } from '@mantine/form';
-import { motion } from 'framer-motion';
import { useState } from 'react';
import { Apps } from 'tabler-icons-react';
+import { v4 as uuidv4 } from 'uuid';
import { useConfig } from '../../tools/state';
import { ServiceTypeList } from '../../tools/types';
-import { AppShelfItemWrapper } from './AppShelfItemWrapper';
export function AddItemShelfButton(props: any) {
const [opened, setOpened] = useState(false);
@@ -51,70 +47,16 @@ export function AddItemShelfButton(props: any) {
);
}
-export default function AddItemShelfItem(props: any) {
- const [opened, setOpened] = useState(false);
- return (
- <>
- setOpened(false)}
- title="Add a service"
- >
-
-
-
-
-
-
- Add a service
-
-
-
-
-
-
- setOpened(true)} size={60} />
-
-
-
-
- >
- );
-}
-
function MatchIcon(name: string, form: any) {
fetch(
`https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/${name
.replace(/\s+/g, '-')
.toLowerCase()}.png`
- )
- .then((res) => {
- if (res.status === 200) {
- form.setFieldValue('icon', res.url);
- }
- })
- .catch(() => {
- // Do nothing
- });
+ ).then((res) => {
+ if (res.ok) {
+ form.setFieldValue('icon', res.url);
+ }
+ });
return false;
}
@@ -126,9 +68,10 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
const form = useForm({
initialValues: {
+ id: props.id ?? uuidv4(),
type: props.type ?? 'Other',
name: props.name ?? '',
- icon: props.icon ?? '',
+ icon: props.icon ?? '/favicon.svg',
url: props.url ?? '',
apiKey: props.apiKey ?? (undefined as unknown as string),
},
@@ -136,15 +79,18 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
apiKey: () => null,
// Validate icon with a regex
icon: (value: string) => {
- if (!value.match(/^https?:\/\/.+\.(png|jpg|jpeg|gif)$/)) {
+ // Regex to match everything that ends with and icon extension
+ if (!value.match(/\.(png|jpg|jpeg|gif|svg)$/)) {
return 'Please enter a valid icon URL';
}
return null;
},
// Validate url with a regex http/https
url: (value: string) => {
- if (!value.match(/^https?:\/\/.+\/$/)) {
- return 'Please enter a valid URL (that ends with a /)';
+ try {
+ const _isValid = new URL(value);
+ } catch (e) {
+ return 'Please enter a valid URL';
}
return null;
},
@@ -166,11 +112,12 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &