mirror of
https://github.com/ajnart/homarr.git
synced 2025-10-30 01:56:05 +01:00
🚀 Patch v0.3.1
Patch v0.3.1
This commit is contained in:
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
@@ -4,8 +4,12 @@ name: Master docker CI
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/**'
|
||||||
|
- '**.md'
|
||||||
tags:
|
tags:
|
||||||
- v*
|
- v*
|
||||||
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -110,6 +114,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
22
.github/workflows/docker_dev.yml
vendored
22
.github/workflows/docker_dev.yml
vendored
@@ -6,7 +6,13 @@ name: Development CI
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [dev]
|
branches: [dev]
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/**'
|
||||||
|
- '**.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/**'
|
||||||
|
- '**.md'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tags:
|
tags:
|
||||||
@@ -25,13 +31,17 @@ jobs:
|
|||||||
yarn_install_and_build:
|
yarn_install_and_build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
|
||||||
- name: Yarn cache
|
- name: Yarn cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
@@ -39,6 +49,7 @@ jobs:
|
|||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
restore-keys: ${{ runner.os }}-yarn-
|
restore-keys: ${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Nextjs cache
|
- name: Nextjs cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
@@ -50,8 +61,10 @@ jobs:
|
|||||||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
|
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
|
||||||
# If source files changed but packages didn't, rebuild from a prior cache.
|
# If source files changed but packages didn't, rebuild from a prior cache.
|
||||||
restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
|
restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
|
||||||
|
|
||||||
- run: yarn install --frozen-lockfile
|
- run: yarn install --frozen-lockfile
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
|
|
||||||
- name: Cache build output
|
- name: Cache build output
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
id: restore-build
|
id: restore-build
|
||||||
@@ -72,8 +85,10 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v2
|
||||||
id: restore-build
|
id: restore-build
|
||||||
with:
|
with:
|
||||||
@@ -85,6 +100,7 @@ jobs:
|
|||||||
./.next/standalone/
|
./.next/standalone/
|
||||||
./packages.json
|
./packages.json
|
||||||
key: ${{ github.sha }}
|
key: ${{ github.sha }}
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v4
|
||||||
@@ -95,11 +111,15 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
tpye=raw,value=dev,priority=1
|
tpye=raw,value=dev,priority=1
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
@@ -111,6 +131,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -35,4 +35,5 @@ yarn-error.log*
|
|||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
# storybook
|
# storybook
|
||||||
storybook-static
|
storybook-static
|
||||||
|
data/configs
|
||||||
10
Dockerfile
10
Dockerfile
@@ -1,19 +1,13 @@
|
|||||||
FROM node:16-alpine
|
FROM node:16-alpine
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
|
||||||
RUN adduser --system --uid 1001 nextjs
|
|
||||||
|
|
||||||
COPY /next.config.js ./
|
COPY /next.config.js ./
|
||||||
COPY /public ./public
|
COPY /public ./public
|
||||||
COPY /package.json ./package.json
|
COPY /package.json ./package.json
|
||||||
|
# Automatically leverage output traces to reduce image size. https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
# Automatically leverage output traces to reduce image size
|
|
||||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
|
||||||
COPY /.next/standalone ./
|
COPY /.next/standalone ./
|
||||||
COPY /.next/static ./.next/static
|
COPY /.next/static ./.next/static
|
||||||
|
|
||||||
EXPOSE 7575
|
EXPOSE 7575
|
||||||
ENV PORT 7575
|
ENV PORT 7575
|
||||||
VOLUME /app/data/configs
|
VOLUME /app/data/configs
|
||||||
CMD ["node", "server.js"]
|
CMD ["node", "server.js"]
|
||||||
|
|||||||
@@ -9,9 +9,7 @@
|
|||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/ajnart/homarr?label=Downloads%20"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/ajnart/homarr?label=Downloads%20"></a>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="">
|
|
||||||
<img align="end" width=600 src="https://user-images.githubusercontent.com/49837342/168315259-b778c816-10fe-44db-bd25-3eea6f31b233.png" />
|
<img align="end" width=600 src="https://user-images.githubusercontent.com/49837342/168315259-b778c816-10fe-44db-bd25-3eea6f31b233.png" />
|
||||||
<a/>
|
|
||||||
</p>
|
</p>
|
||||||
<p align = "center">
|
<p align = "center">
|
||||||
A homepage for <i>your</i> server.
|
A homepage for <i>your</i> server.
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
{
|
{
|
||||||
"name": "config",
|
"name": "config",
|
||||||
"services": [],
|
"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": {
|
"settings": {
|
||||||
"searchBar": true,
|
"searchBar": true,
|
||||||
"searchUrl": "https://duckduckgo.com/?q=",
|
"searchUrl": "Custom",
|
||||||
"enabledModules": [
|
"enabledModules": [
|
||||||
"Date",
|
"Date",
|
||||||
"Calendar"
|
"Calendar",
|
||||||
|
"Weather"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export const REPO_URL = 'ajnart/homarr';
|
export const REPO_URL = 'ajnart/homarr';
|
||||||
export const CURRENT_VERSION = 'v0.3.0';
|
export const CURRENT_VERSION = 'v0.3.1';
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
@@ -28,9 +29,9 @@ export function AddItemShelfButton(props: any) {
|
|||||||
<Modal
|
<Modal
|
||||||
size="xl"
|
size="xl"
|
||||||
radius="md"
|
radius="md"
|
||||||
|
title={<Title order={3}>Add service</Title>}
|
||||||
opened={props.opened || opened}
|
opened={props.opened || opened}
|
||||||
onClose={() => setOpened(false)}
|
onClose={() => setOpened(false)}
|
||||||
title="Add a service"
|
|
||||||
>
|
>
|
||||||
<AddAppShelfItemForm setOpened={setOpened} />
|
<AddAppShelfItemForm setOpened={setOpened} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -1,30 +1,21 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Text, AspectRatio, SimpleGrid, Card, Image, useMantineTheme } from '@mantine/core';
|
import { Text, AspectRatio, Card, Image, useMantineTheme, Center, Grid } from '@mantine/core';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { serviceItem } from '../../tools/types';
|
import { serviceItem } from '../../tools/types';
|
||||||
import AppShelfMenu from './AppShelfMenu';
|
import AppShelfMenu from './AppShelfMenu';
|
||||||
|
|
||||||
const AppShelf = () => {
|
const AppShelf = (props: any) => {
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleGrid
|
<Grid gutter="xl" align="center">
|
||||||
cols={7}
|
|
||||||
spacing="xl"
|
|
||||||
breakpoints={[
|
|
||||||
{ maxWidth: 2400, cols: 6, spacing: 'xl' },
|
|
||||||
{ maxWidth: 1800, cols: 5, spacing: 'xl' },
|
|
||||||
{ maxWidth: 1500, cols: 4, spacing: 'lg' },
|
|
||||||
{ maxWidth: 800, cols: 3, spacing: 'md' },
|
|
||||||
{ maxWidth: 400, cols: 3, spacing: 'sm' },
|
|
||||||
{ maxWidth: 400, cols: 2, spacing: 'sm' },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{config.services.map((service) => (
|
{config.services.map((service) => (
|
||||||
<AppShelfItem key={service.name} service={service} />
|
<Grid.Col span={6} xl={2} xs={4} sm={3} md={3}>
|
||||||
|
<AppShelfItem key={service.name} service={service} />
|
||||||
|
</Grid.Col>
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,16 +33,9 @@ export function AppShelfItem(props: any) {
|
|||||||
setHovering(false);
|
setHovering(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card
|
<Card withBorder radius="lg" shadow="md">
|
||||||
style={{
|
|
||||||
boxShadow: hovering ? '0px 0px 3px rgba(0, 0, 0, 0.5)' : '0px 0px 1px rgba(0, 0, 0, 0.5)',
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[1],
|
|
||||||
}}
|
|
||||||
radius="md"
|
|
||||||
>
|
|
||||||
<Card.Section>
|
<Card.Section>
|
||||||
<Text mt="sm" align="center" lineClamp={1} weight={500}>
|
<Text mt="sm" align="center" lineClamp={1} weight={550}>
|
||||||
{service.name}
|
{service.name}
|
||||||
</Text>
|
</Text>
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -68,34 +52,35 @@ export function AppShelfItem(props: any) {
|
|||||||
<AppShelfMenu service={service} />
|
<AppShelfMenu service={service} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
<Card.Section>
|
<Center>
|
||||||
<AspectRatio
|
<Card.Section>
|
||||||
ratio={3 / 5}
|
<AspectRatio
|
||||||
m="xl"
|
ratio={3 / 5}
|
||||||
style={{
|
m="xl"
|
||||||
width: 150,
|
style={{
|
||||||
height: 90,
|
width: 150,
|
||||||
}}
|
height: 90,
|
||||||
>
|
|
||||||
<motion.i
|
|
||||||
whileHover={{
|
|
||||||
cursor: 'pointer',
|
|
||||||
scale: 1.1,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<motion.i
|
||||||
style={{
|
whileHover={{
|
||||||
maxWidth: 80,
|
cursor: 'pointer',
|
||||||
|
scale: 1.1,
|
||||||
}}
|
}}
|
||||||
fit="contain"
|
>
|
||||||
onClick={() => {
|
<Image
|
||||||
window.open(service.url);
|
width={80}
|
||||||
}}
|
height={80}
|
||||||
src={service.icon}
|
src={service.icon}
|
||||||
/>
|
fit="contain"
|
||||||
</motion.i>
|
onClick={() => {
|
||||||
</AspectRatio>
|
window.open(service.url);
|
||||||
</Card.Section>
|
}}
|
||||||
|
/>
|
||||||
|
</motion.i>
|
||||||
|
</AspectRatio>
|
||||||
|
</Card.Section>
|
||||||
|
</Center>
|
||||||
</Card>
|
</Card>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createStyles, Switch, Group, useMantineColorScheme } from '@mantine/core';
|
import { createStyles, Switch, Group, useMantineColorScheme, Kbd } from '@mantine/core';
|
||||||
import { Sun, MoonStars } from 'tabler-icons-react';
|
import { Sun, MoonStars } from 'tabler-icons-react';
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
@@ -40,6 +40,9 @@ export function ColorSchemeSwitch() {
|
|||||||
<Switch checked={colorScheme === 'dark'} onChange={() => toggleColorScheme()} size="md" />
|
<Switch checked={colorScheme === 'dark'} onChange={() => toggleColorScheme()} size="md" />
|
||||||
</div>
|
</div>
|
||||||
Switch to {colorScheme === 'dark' ? 'light' : 'dark'} mode
|
Switch to {colorScheme === 'dark' ? 'light' : 'dark'} mode
|
||||||
|
<Group spacing={2}>
|
||||||
|
<Kbd>Ctrl</Kbd>+<Kbd>J</Kbd>
|
||||||
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,40 @@
|
|||||||
import { TextInput, Text, Popover, Box } from '@mantine/core';
|
import { TextInput, Kbd, createStyles, useMantineTheme, Text, Popover } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/hooks';
|
import { useForm, useHotkeys } from '@mantine/hooks';
|
||||||
import { useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { Search, BrandYoutube, Download } from 'tabler-icons-react';
|
import { Search, BrandYoutube, Download } from 'tabler-icons-react';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
|
|
||||||
|
const useStyles = createStyles((theme) => ({
|
||||||
|
hide: {
|
||||||
|
[theme.fn.smallerThan('sm')]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export default function SearchBar(props: any) {
|
export default function SearchBar(props: any) {
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const [icon, setIcon] = useState(<Search />);
|
const [icon, setIcon] = useState(<Search />);
|
||||||
const querryUrl = config.settings.searchUrl || 'https://www.google.com/search?q=';
|
const queryUrl = config.settings.searchUrl || 'https://www.google.com/search?q=';
|
||||||
|
const textInput: any = useRef(null);
|
||||||
|
useHotkeys([['ctrl+K', () => textInput.current.focus()]]);
|
||||||
|
|
||||||
|
const { classes, cx } = useStyles();
|
||||||
|
const theme = useMantineTheme();
|
||||||
|
const rightSection = (
|
||||||
|
<div className={classes.hide}>
|
||||||
|
<Kbd>Ctrl</Kbd>
|
||||||
|
<span style={{ margin: '0 5px' }}>+</span>
|
||||||
|
<Kbd>K</Kbd>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
querry: '',
|
query: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -21,70 +43,66 @@ export default function SearchBar(props: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<form
|
||||||
mb={"xl"}
|
onChange={() => {
|
||||||
style={{
|
// If query contains !yt or !t add "Searching on YouTube" or "Searching torrent"
|
||||||
width: '100%',
|
const query = form.values.query.trim();
|
||||||
|
const isYoutube = query.startsWith('!yt');
|
||||||
|
const isTorrent = query.startsWith('!t');
|
||||||
|
if (isYoutube) {
|
||||||
|
setIcon(<BrandYoutube size={22} />);
|
||||||
|
} else if (isTorrent) {
|
||||||
|
setIcon(<Download size={22} />);
|
||||||
|
} else {
|
||||||
|
setIcon(<Search size={22} />);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
|
onSubmit={form.onSubmit((values) => {
|
||||||
|
// Find if query is prefixed by !yt or !t
|
||||||
|
const query = values.query.trim();
|
||||||
|
const isYoutube = query.startsWith('!yt');
|
||||||
|
const isTorrent = query.startsWith('!t');
|
||||||
|
if (isYoutube) {
|
||||||
|
window.open(`https://www.youtube.com/results?search_query=${query.substring(3)}`);
|
||||||
|
} else if (isTorrent) {
|
||||||
|
window.open(`https://bitsearch.to/search?q=${query.substring(3)}`);
|
||||||
|
} else {
|
||||||
|
window.open(`${queryUrl}${values.query}`);
|
||||||
|
}
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<form
|
<Popover
|
||||||
onChange={() => {
|
opened={opened}
|
||||||
// If querry contains !yt or !t add "Searching on YouTube" or "Searching torrent"
|
position="bottom"
|
||||||
const querry = form.values.querry.trim();
|
placement="start"
|
||||||
const isYoutube = querry.startsWith('!yt');
|
width={260}
|
||||||
const isTorrent = querry.startsWith('!t');
|
withArrow
|
||||||
if (isYoutube) {
|
radius="md"
|
||||||
setIcon(<BrandYoutube size={22} />);
|
trapFocus={false}
|
||||||
} else if (isTorrent) {
|
transition="pop-bottom-right"
|
||||||
setIcon(<Download size={22} />);
|
onFocusCapture={() => setOpened(true)}
|
||||||
} else {
|
onBlurCapture={() => setOpened(false)}
|
||||||
setIcon(<Search size={22} />);
|
target={
|
||||||
}
|
<TextInput
|
||||||
}}
|
variant="filled"
|
||||||
onSubmit={form.onSubmit((values) => {
|
icon={icon}
|
||||||
// Find if querry is prefixed by !yt or !t
|
ref={textInput}
|
||||||
const querry = values.querry.trim();
|
rightSectionWidth={90}
|
||||||
const isYoutube = querry.startsWith('!yt');
|
rightSection={rightSection}
|
||||||
const isTorrent = querry.startsWith('!t');
|
radius="md"
|
||||||
if (isYoutube) {
|
size="md"
|
||||||
window.open(`https://www.youtube.com/results?search_query=${querry.substring(3)}`);
|
styles={{ rightSection: { pointerEvents: 'none' } }}
|
||||||
} else if (isTorrent) {
|
placeholder="Search the web..."
|
||||||
window.open(`https://bitsearch.to/search?q=${querry.substring(3)}`);
|
{...props}
|
||||||
} else {
|
{...form.getInputProps('query')}
|
||||||
window.open(`${querryUrl}${values.querry}`);
|
/>
|
||||||
}
|
}
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<Popover
|
<Text>
|
||||||
opened={opened}
|
tip: Use the prefixes <b>!yt</b> and <b>!t</b> in front of your query to search on YouTube
|
||||||
style={{
|
or for a Torrent respectively.
|
||||||
width: '100%',
|
</Text>
|
||||||
}}
|
</Popover>
|
||||||
position="bottom"
|
</form>
|
||||||
placement="start"
|
|
||||||
withArrow
|
|
||||||
trapFocus={false}
|
|
||||||
transition="pop-top-left"
|
|
||||||
onFocusCapture={() => setOpened(true)}
|
|
||||||
onBlurCapture={() => setOpened(false)}
|
|
||||||
target={
|
|
||||||
<TextInput
|
|
||||||
variant="filled"
|
|
||||||
color="blue"
|
|
||||||
icon={icon}
|
|
||||||
radius="md"
|
|
||||||
size="md"
|
|
||||||
placeholder="Search the web"
|
|
||||||
{...props}
|
|
||||||
{...form.getInputProps('querry')}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Text>
|
|
||||||
tip: Use the prefixes <b>!yt</b> and <b>!t</b> in front of your query to search on YouTube or for a Torrent respectively.
|
|
||||||
</Text>
|
|
||||||
</Popover>
|
|
||||||
</form>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ function SettingsMenu(props: any) {
|
|||||||
/>
|
/>
|
||||||
{searchUrl === 'Custom' && (
|
{searchUrl === 'Custom' && (
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Querry URL"
|
label="Query URL"
|
||||||
placeholder="Custom querry url"
|
placeholder="Custom query url"
|
||||||
value={customSearchUrl}
|
value={customSearchUrl}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setCustomSearchUrl(event.currentTarget.value);
|
setCustomSearchUrl(event.currentTarget.value);
|
||||||
@@ -142,7 +142,8 @@ export function SettingsMenuButton(props: any) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
size="md"
|
size="xl"
|
||||||
|
radius="md"
|
||||||
title={<Title order={3}>Settings</Title>}
|
title={<Title order={3}>Settings</Title>}
|
||||||
opened={props.opened || opened}
|
opened={props.opened || opened}
|
||||||
onClose={() => setOpened(false)}
|
onClose={() => setOpened(false)}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Aside as MantineAside, Group } from '@mantine/core';
|
import { Aside as MantineAside, Group } from '@mantine/core';
|
||||||
|
import { DateModule } from '../modules';
|
||||||
import { CalendarModule } from '../modules/calendar/CalendarModule';
|
import { CalendarModule } from '../modules/calendar/CalendarModule';
|
||||||
import ModuleWrapper from '../modules/moduleWrapper';
|
import ModuleWrapper from '../modules/moduleWrapper';
|
||||||
|
|
||||||
export default function Aside() {
|
export default function Aside(props: any) {
|
||||||
return (
|
return (
|
||||||
<MantineAside
|
<MantineAside
|
||||||
hiddenBreakpoint="md"
|
hiddenBreakpoint="md"
|
||||||
@@ -14,8 +15,9 @@ export default function Aside() {
|
|||||||
base: 'auto',
|
base: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Group mt="sm" direction="column">
|
<Group mt="sm" grow direction="column">
|
||||||
<ModuleWrapper module={CalendarModule} />
|
<ModuleWrapper module={CalendarModule} />
|
||||||
|
<ModuleWrapper module={DateModule} />
|
||||||
</Group>
|
</Group>
|
||||||
</MantineAside>
|
</MantineAside>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -54,24 +54,12 @@ export function Footer({ links }: FooterCenteredProps) {
|
|||||||
</Anchor>
|
</Anchor>
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FooterComponent height="auto" style={{ border: 'none' }}>
|
<FooterComponent p={5} height="auto" style={{ border: 'none', position: 'fixed', bottom: 0, right: 0 }}>
|
||||||
<Group
|
<Group position="right" mr="xs" mb="xs">
|
||||||
sx={{
|
<ActionIcon<'a'> component="a" href="https://github.com/ajnart/homarr" size="lg">
|
||||||
position: 'fixed',
|
<BrandGithub size={18} />
|
||||||
bottom: 0,
|
</ActionIcon>
|
||||||
right: 15,
|
|
||||||
}}
|
|
||||||
direction="row"
|
|
||||||
align="center"
|
|
||||||
mb={15}
|
|
||||||
>
|
|
||||||
<Group className={classes.links}>{items}</Group>
|
|
||||||
<Group spacing="xs" position="right" noWrap>
|
|
||||||
<ActionIcon<'a'> component="a" href="https://github.com/ajnart/homarr" size="lg">
|
|
||||||
<BrandGithub size={18} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Group>
|
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: '0.90rem',
|
fontSize: '0.90rem',
|
||||||
|
|||||||
@@ -1,117 +1,35 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createStyles, Header as Head, Group, Drawer, Center } from '@mantine/core';
|
import { createStyles, Header as Head, Group, Box } from '@mantine/core';
|
||||||
import { useBooleanToggle } from '@mantine/hooks';
|
|
||||||
import { NextLink } from '@mantine/next';
|
|
||||||
import { Logo } from './Logo';
|
import { Logo } from './Logo';
|
||||||
import CalendarComponent from '../modules/calendar/CalendarModule';
|
import SearchBar from '../SearchBar/SearchBar';
|
||||||
import { SettingsMenuButton } from '../Settings/SettingsMenu';
|
|
||||||
import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem';
|
import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem';
|
||||||
|
import { SettingsMenuButton } from '../Settings/SettingsMenu';
|
||||||
|
|
||||||
const HEADER_HEIGHT = 60;
|
const HEADER_HEIGHT = 60;
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
root: {
|
hide: {
|
||||||
position: 'relative',
|
[theme.fn.smallerThan('xs')]: {
|
||||||
zIndex: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
dropdown: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: HEADER_HEIGHT,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
zIndex: 0,
|
|
||||||
borderTopRightRadius: 0,
|
|
||||||
borderTopLeftRadius: 0,
|
|
||||||
borderTopWidth: 0,
|
|
||||||
overflow: 'hidden',
|
|
||||||
|
|
||||||
[theme.fn.largerThan('md')]: {
|
|
||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
header: {
|
|
||||||
display: 'flex',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
|
|
||||||
links: {
|
|
||||||
[theme.fn.smallerThan('md')]: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
burger: {
|
|
||||||
[theme.fn.largerThan('md')]: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
link: {
|
|
||||||
display: 'block',
|
|
||||||
lineHeight: 1,
|
|
||||||
padding: '8px 12px',
|
|
||||||
borderRadius: theme.radius.sm,
|
|
||||||
textDecoration: 'none',
|
|
||||||
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.colors.gray[7],
|
|
||||||
fontSize: theme.fontSizes.sm,
|
|
||||||
fontWeight: 500,
|
|
||||||
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
|
|
||||||
},
|
|
||||||
|
|
||||||
[theme.fn.smallerThan('sm')]: {
|
|
||||||
borderRadius: 0,
|
|
||||||
padding: theme.spacing.md,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
linkActive: {
|
|
||||||
'&, &:hover': {
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark'
|
|
||||||
? theme.fn.rgba(theme.colors[theme.primaryColor][9], 0.25)
|
|
||||||
: theme.colors[theme.primaryColor][0],
|
|
||||||
color: theme.colors[theme.primaryColor][theme.colorScheme === 'dark' ? 3 : 7],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface HeaderResponsiveProps {
|
export function Header(props: any) {
|
||||||
links: { link: string; label: string }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Header({ links }: HeaderResponsiveProps) {
|
|
||||||
const [opened, toggleOpened] = useBooleanToggle(false);
|
|
||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Head height={HEADER_HEIGHT}>
|
<Head height="auto">
|
||||||
<Group direction="row" align="center" position="apart" className={classes.header} mx="xl">
|
<Group m="xs" position="apart">
|
||||||
<NextLink style={{ textDecoration: 'none' }} href="/">
|
<Box className={classes.hide}>
|
||||||
<Logo style={{ fontSize: 22 }} />
|
<Logo style={{ fontSize: 22 }} />
|
||||||
</NextLink>
|
</Box>
|
||||||
<Group>
|
<Group noWrap>
|
||||||
|
<SearchBar />
|
||||||
<SettingsMenuButton />
|
<SettingsMenuButton />
|
||||||
<AddItemShelfButton />
|
<AddItemShelfButton />
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Drawer
|
|
||||||
opened={opened}
|
|
||||||
overlayOpacity={0.55}
|
|
||||||
overlayBlur={3}
|
|
||||||
onClose={() => toggleOpened()}
|
|
||||||
position="right"
|
|
||||||
>
|
|
||||||
{opened ?? (
|
|
||||||
<Center>
|
|
||||||
<CalendarComponent />
|
|
||||||
</Center>
|
|
||||||
)}
|
|
||||||
</Drawer>
|
|
||||||
</Head>
|
</Head>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,28 @@
|
|||||||
import { AppShell, Center, createStyles } from '@mantine/core';
|
import { AppShell, createStyles } from '@mantine/core';
|
||||||
import { Header } from './Header';
|
import { Header } from './Header';
|
||||||
import { Footer } from './Footer';
|
import { Footer } from './Footer';
|
||||||
import Aside from './Aside';
|
import Aside from './Aside';
|
||||||
import Navbar from './Navbar';
|
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
main: {
|
main: {},
|
||||||
[theme.fn.largerThan('md')]: {
|
|
||||||
maxWidth: 1500,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function Layout({ children, style }: any) {
|
export default function Layout({ children, style }: any) {
|
||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
navbar={<Navbar />}
|
|
||||||
aside={<Aside />}
|
aside={<Aside />}
|
||||||
header={<Header links={[]} />}
|
header={<Header />}
|
||||||
footer={<Footer links={[]} />}
|
footer={<Footer links={[]} />}
|
||||||
>
|
>
|
||||||
<Center>
|
<main
|
||||||
<main
|
className={cx(classes.main)}
|
||||||
className={cx(classes.main)}
|
style={{
|
||||||
style={{
|
...style,
|
||||||
...style,
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{children}
|
||||||
{children}
|
</main>
|
||||||
</main>
|
|
||||||
</Center>
|
|
||||||
</AppShell>
|
</AppShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function Navbar() {
|
|||||||
base: 'auto',
|
base: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Group mt="sm" direction="column">
|
<Group mt="sm" direction="column" align="center">
|
||||||
<ModuleWrapper module={DateModule} />
|
<ModuleWrapper module={DateModule} />
|
||||||
</Group>
|
</Group>
|
||||||
</MantineNavbar>
|
</MantineNavbar>
|
||||||
|
|||||||
@@ -13,16 +13,7 @@ export default function ModuleWrapper(props: any) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card hidden={!isShown} mx="sm" withBorder radius="lg" shadow="sm">
|
||||||
hidden={!isShown}
|
|
||||||
mx="sm"
|
|
||||||
radius="lg"
|
|
||||||
shadow="sm"
|
|
||||||
style={{
|
|
||||||
// Make background color of the card depend on the theme
|
|
||||||
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : 'white',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<module.component />
|
<module.component />
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Group } from '@mantine/core';
|
|
||||||
import { getCookie, setCookies } from 'cookies-next';
|
import { getCookie, setCookies } from 'cookies-next';
|
||||||
import { GetServerSidePropsContext } from 'next';
|
import { GetServerSidePropsContext } from 'next';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@@ -6,7 +5,6 @@ import fs from 'fs';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import AppShelf from '../components/AppShelf/AppShelf';
|
import AppShelf from '../components/AppShelf/AppShelf';
|
||||||
import LoadConfigComponent from '../components/Config/LoadConfig';
|
import LoadConfigComponent from '../components/Config/LoadConfig';
|
||||||
import SearchBar from '../components/SearchBar/SearchBar';
|
|
||||||
import { Config } from '../tools/types';
|
import { Config } from '../tools/types';
|
||||||
import { useConfig } from '../tools/state';
|
import { useConfig } from '../tools/state';
|
||||||
|
|
||||||
@@ -54,10 +52,7 @@ export default function HomePage(props: any) {
|
|||||||
}, [initialConfig]);
|
}, [initialConfig]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchBar />
|
<AppShelf />
|
||||||
<Group align="start" position="apart" noWrap>
|
|
||||||
<AppShelf />
|
|
||||||
</Group>
|
|
||||||
<LoadConfigComponent />
|
<LoadConfigComponent />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user