Merge pull request #546 from ajnart/gridstack-exerpiments
Gridstack exerpiments
This commit is contained in:
@@ -17,195 +17,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"apps": [
|
"apps": [
|
||||||
{
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a990",
|
|
||||||
"name": "Donate",
|
|
||||||
"url": "https://ko-fi.com/ajnart",
|
|
||||||
"behaviour": {
|
|
||||||
"onClickUrl": "https://ko-fi.com/ajnart",
|
|
||||||
"externalUrl": "https://ko-fi.com/ajnart",
|
|
||||||
"isOpeningNewTab": true
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"enabledStatusChecker": false,
|
|
||||||
"okStatus": [
|
|
||||||
200
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"appearance": {
|
|
||||||
"iconUrl": "https://uploads-ssl.webflow.com/5c14e387dab576fe667689cf/61e1116779fc0a9bd5bdbcc7_Frame%206.png"
|
|
||||||
},
|
|
||||||
"integration": {
|
|
||||||
"type": null,
|
|
||||||
"properties": []
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "wrapper",
|
|
||||||
"properties": {
|
|
||||||
"id": "default"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 3,
|
|
||||||
"height": 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a330",
|
|
||||||
"name": "Contribute",
|
|
||||||
"url": "https://github.com/ajnart/homarr",
|
|
||||||
"behaviour": {
|
|
||||||
"onClickUrl": "https://github.com/ajnart/homarr",
|
|
||||||
"externalUrl": "https://github.com/ajnart/homarr",
|
|
||||||
"isOpeningNewTab": true
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"enabledStatusChecker": false,
|
|
||||||
"okStatus": [
|
|
||||||
200
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"appearance": {
|
|
||||||
"iconUrl": "https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/github.png"
|
|
||||||
},
|
|
||||||
"integration": {
|
|
||||||
"type": null,
|
|
||||||
"properties": []
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "wrapper",
|
|
||||||
"properties": {
|
|
||||||
"id": "default"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 3
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 3,
|
|
||||||
"height": 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "5df743d9-5cb1-457c-85d2-64ff86855652",
|
|
||||||
"name": "Your app",
|
|
||||||
"url": "https://homarr.dev",
|
|
||||||
"appearance": {
|
|
||||||
"iconUrl": "/imgs/logo/logo.png"
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"enabledStatusChecker": false,
|
|
||||||
"okStatus": []
|
|
||||||
},
|
|
||||||
"behaviour": {
|
|
||||||
"isOpeningNewTab": true,
|
|
||||||
"externalUrl": "https://homarr.dev"
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "wrapper",
|
|
||||||
"properties": {
|
|
||||||
"id": "default"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"location": {
|
|
||||||
"x": 15,
|
|
||||||
"y": 5
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 5,
|
|
||||||
"height": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"integration": {
|
|
||||||
"type": null,
|
|
||||||
"properties": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33a",
|
|
||||||
"name": "Documentation",
|
|
||||||
"url": "https://homarr.dev",
|
|
||||||
"behaviour": {
|
|
||||||
"onClickUrl": "https://homarr.dev",
|
|
||||||
"externalUrl": "https://homarr.dev",
|
|
||||||
"isOpeningNewTab": true
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"enabledStatusChecker": false,
|
|
||||||
"okStatus": [
|
|
||||||
200
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"appearance": {
|
|
||||||
"iconUrl": "/imgs/logo/logo.png"
|
|
||||||
},
|
|
||||||
"integration": {
|
|
||||||
"type": null,
|
|
||||||
"properties": []
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "wrapper",
|
|
||||||
"properties": {
|
|
||||||
"id": "default"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"location": {
|
|
||||||
"x": 3,
|
|
||||||
"y": 3
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 3,
|
|
||||||
"height": 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e41a11f5-9c6e-41bc-ac0e-4c4c47582faa",
|
|
||||||
"name": "Your app",
|
|
||||||
"url": "https://homarr.dev",
|
|
||||||
"appearance": {
|
|
||||||
"iconUrl": "/imgs/logo/logo.png"
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"enabledStatusChecker": false,
|
|
||||||
"okStatus": []
|
|
||||||
},
|
|
||||||
"behaviour": {
|
|
||||||
"isOpeningNewTab": true,
|
|
||||||
"externalUrl": ""
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "wrapper",
|
|
||||||
"properties": {
|
|
||||||
"id": "default"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 6
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"integration": {
|
|
||||||
"type": null,
|
|
||||||
"properties": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "76217a87-7151-42d0-b0cf-1b72aef63f83",
|
"id": "76217a87-7151-42d0-b0cf-1b72aef63f83",
|
||||||
"name": "Small app",
|
"name": "Small app",
|
||||||
@@ -229,8 +40,8 @@
|
|||||||
},
|
},
|
||||||
"shape": {
|
"shape": {
|
||||||
"location": {
|
"location": {
|
||||||
"x": 17,
|
"x": 0,
|
||||||
"y": 0
|
"y": 2
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"width": 2,
|
"width": 2,
|
||||||
@@ -265,29 +76,43 @@
|
|||||||
"properties": []
|
"properties": []
|
||||||
},
|
},
|
||||||
"area": {
|
"area": {
|
||||||
"type": "wrapper",
|
"type": "category",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": "default"
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shape": {
|
"shape": {
|
||||||
"location": {
|
"location": {
|
||||||
"x": 3,
|
"x": 0,
|
||||||
"y": 0
|
"y": 4
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"width": 3,
|
"width": 4,
|
||||||
"height": 3
|
"height": 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
|
||||||
"widgets": [
|
|
||||||
{
|
{
|
||||||
"id": "weather",
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a990",
|
||||||
"properties": {
|
"name": "Donate",
|
||||||
"displayInFahrenheit": false,
|
"url": "https://ko-fi.com/ajnart",
|
||||||
"location": "Paris"
|
"behaviour": {
|
||||||
|
"onClickUrl": "https://ko-fi.com/ajnart",
|
||||||
|
"externalUrl": "https://ko-fi.com/ajnart",
|
||||||
|
"isOpeningNewTab": true
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"enabledStatusChecker": false,
|
||||||
|
"okStatus": [
|
||||||
|
200
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"iconUrl": "https://uploads-ssl.webflow.com/5c14e387dab576fe667689cf/61e1116779fc0a9bd5bdbcc7_Frame%206.png"
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"type": null,
|
||||||
|
"properties": []
|
||||||
},
|
},
|
||||||
"area": {
|
"area": {
|
||||||
"type": "category",
|
"type": "category",
|
||||||
@@ -297,15 +122,167 @@
|
|||||||
},
|
},
|
||||||
"shape": {
|
"shape": {
|
||||||
"location": {
|
"location": {
|
||||||
"x": 6,
|
"x": 2,
|
||||||
"y": 0
|
"y": 2
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e41a11f5-9c6e-41bc-ac0e-4c4c47582faa",
|
||||||
|
"name": "Your app",
|
||||||
|
"url": "https://homarr.dev",
|
||||||
|
"appearance": {
|
||||||
|
"iconUrl": "/imgs/logo/logo.png"
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"enabledStatusChecker": false,
|
||||||
|
"okStatus": []
|
||||||
|
},
|
||||||
|
"behaviour": {
|
||||||
|
"isOpeningNewTab": true,
|
||||||
|
"externalUrl": ""
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "wrapper",
|
||||||
|
"properties": {
|
||||||
|
"id": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"location": {
|
||||||
|
"x": 8,
|
||||||
|
"y": 12
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"width": 4,
|
"width": 4,
|
||||||
"height": 2
|
"height": 2
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"type": null,
|
||||||
|
"properties": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a330",
|
||||||
|
"name": "Contribute",
|
||||||
|
"url": "https://github.com/ajnart/homarr",
|
||||||
|
"behaviour": {
|
||||||
|
"onClickUrl": "https://github.com/ajnart/homarr",
|
||||||
|
"externalUrl": "https://github.com/ajnart/homarr",
|
||||||
|
"isOpeningNewTab": true
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"enabledStatusChecker": false,
|
||||||
|
"okStatus": [
|
||||||
|
200
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"iconUrl": "https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/github.png"
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"type": null,
|
||||||
|
"properties": []
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "category",
|
||||||
|
"properties": {
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"location": {
|
||||||
|
"x": 4,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33a",
|
||||||
|
"name": "Documentation",
|
||||||
|
"url": "https://homarr.dev",
|
||||||
|
"behaviour": {
|
||||||
|
"onClickUrl": "https://homarr.dev",
|
||||||
|
"externalUrl": "https://homarr.dev",
|
||||||
|
"isOpeningNewTab": true
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"enabledStatusChecker": false,
|
||||||
|
"okStatus": [
|
||||||
|
200
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"iconUrl": "/imgs/logo/logo.png"
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"type": null,
|
||||||
|
"properties": []
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "wrapper",
|
||||||
|
"properties": {
|
||||||
|
"id": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"location": {
|
||||||
|
"x": 8,
|
||||||
|
"y": 9
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 4,
|
||||||
|
"height": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5df743d9-5cb1-457c-85d2-64ff86855652",
|
||||||
|
"name": "Your app",
|
||||||
|
"url": "https://homarr.dev",
|
||||||
|
"appearance": {
|
||||||
|
"iconUrl": "/imgs/logo/logo.png"
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"enabledStatusChecker": false,
|
||||||
|
"okStatus": []
|
||||||
|
},
|
||||||
|
"behaviour": {
|
||||||
|
"isOpeningNewTab": true,
|
||||||
|
"externalUrl": "https://homarr.dev"
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "wrapper",
|
||||||
|
"properties": {
|
||||||
|
"id": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"location": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 7
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 6,
|
||||||
|
"height": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"type": null,
|
||||||
|
"properties": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"widgets": [
|
||||||
{
|
{
|
||||||
"id": "date",
|
"id": "date",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -317,6 +294,29 @@
|
|||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"shape": {
|
||||||
|
"location": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 6,
|
||||||
|
"height": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "weather",
|
||||||
|
"properties": {
|
||||||
|
"displayInFahrenheit": false,
|
||||||
|
"location": "Paris"
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "wrapper",
|
||||||
|
"properties": {
|
||||||
|
"id": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
"shape": {
|
"shape": {
|
||||||
"location": {
|
"location": {
|
||||||
"x": 0,
|
"x": 0,
|
||||||
@@ -341,8 +341,8 @@
|
|||||||
},
|
},
|
||||||
"shape": {
|
"shape": {
|
||||||
"location": {
|
"location": {
|
||||||
"x": 15,
|
"x": 0,
|
||||||
"y": 0
|
"y": 2
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"width": 5,
|
"width": 5,
|
||||||
|
|||||||
@@ -100,10 +100,11 @@ export const IntegrationSelector = ({ form }: IntegrationSelectorProps) => {
|
|||||||
placeholder={t('integration.type.placeholder')}
|
placeholder={t('integration.type.placeholder')}
|
||||||
itemComponent={SelectItemComponent}
|
itemComponent={SelectItemComponent}
|
||||||
data={data}
|
data={data}
|
||||||
maxDropdownHeight={400}
|
maxDropdownHeight={150}
|
||||||
|
dropdownPosition="bottom"
|
||||||
clearable
|
clearable
|
||||||
variant="default"
|
variant="default"
|
||||||
mb="md"
|
searchable
|
||||||
icon={
|
icon={
|
||||||
form.values.integration?.type && (
|
form.values.integration?.type && (
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -23,39 +23,40 @@ export const AppTile = ({ className, app }: AppTileProps) => {
|
|||||||
classes: { card: cardClass },
|
classes: { card: cardClass },
|
||||||
} = useCardStyles(false);
|
} = useCardStyles(false);
|
||||||
|
|
||||||
const inner = (
|
function Inner() {
|
||||||
<>
|
return (
|
||||||
<Text align="center" weight={500} size="md" className={classes.appName}>
|
<>
|
||||||
{app.name}
|
<Text align="center" weight={500} size="md" className={classes.appName}>
|
||||||
</Text>
|
{app.name}
|
||||||
<Center style={{ height: '75%', flex: 1 }}>
|
</Text>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
<Center style={{ height: '85%', flex: 1 }}>
|
||||||
<motion.img
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
className={classes.image}
|
<motion.img
|
||||||
src={app.appearance.iconUrl}
|
className={classes.image}
|
||||||
alt=""
|
src={app.appearance.iconUrl}
|
||||||
animate={{
|
alt={app.name}
|
||||||
scale: [0.8, 1.15, 1],
|
whileHover={{
|
||||||
rotate: [0, 15, -15, 0],
|
scale: 1.2,
|
||||||
}}
|
transition: { duration: 0.2 },
|
||||||
transition={{
|
}}
|
||||||
duration: 0.5,
|
initial={{ opacity: 0, scale: 0.5 }}
|
||||||
}}
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
/>
|
transition={{ duration: 0.3 }}
|
||||||
</Center>
|
/>
|
||||||
</>
|
</Center>
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HomarrCardWrapper className={className}>
|
<HomarrCardWrapper className={className}>
|
||||||
<AppMenu app={app} />
|
<AppMenu app={app} />
|
||||||
|
|
||||||
{!app.url || isEditMode ? (
|
{!app.url || isEditMode ? (
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
className={classes.button}
|
className={classes.button}
|
||||||
style={{ pointerEvents: isEditMode ? 'none' : 'auto' }}
|
style={{ pointerEvents: isEditMode ? 'none' : 'auto' }}
|
||||||
>
|
>
|
||||||
{inner}
|
<Inner />
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
) : (
|
) : (
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
@@ -63,9 +64,9 @@ export const AppTile = ({ className, app }: AppTileProps) => {
|
|||||||
component={NextLink}
|
component={NextLink}
|
||||||
href={app.behaviour.externalUrl.length > 0 ? app.behaviour.externalUrl : app.url}
|
href={app.behaviour.externalUrl.length > 0 ? app.behaviour.externalUrl : app.url}
|
||||||
target={app.behaviour.isOpeningNewTab ? '_blank' : '_self'}
|
target={app.behaviour.isOpeningNewTab ? '_blank' : '_self'}
|
||||||
className={cx(classes.button, classes.link)}
|
className={cx(classes.button)}
|
||||||
>
|
>
|
||||||
{inner}
|
<Inner />
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
)}
|
)}
|
||||||
<AppPing app={app} />
|
<AppPing app={app} />
|
||||||
@@ -76,14 +77,14 @@ export const AppTile = ({ className, app }: AppTileProps) => {
|
|||||||
const useStyles = createStyles((theme, _params, getRef) => ({
|
const useStyles = createStyles((theme, _params, getRef) => ({
|
||||||
image: {
|
image: {
|
||||||
ref: getRef('image'),
|
ref: getRef('image'),
|
||||||
maxHeight: '80%',
|
maxHeight: '90%',
|
||||||
maxWidth: '80%',
|
maxWidth: '90%',
|
||||||
transition: 'transform 100ms ease-in-out',
|
|
||||||
},
|
},
|
||||||
appName: {
|
appName: {
|
||||||
ref: getRef('appName'),
|
ref: getRef('appName'),
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
|
paddingBottom: 10,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -91,20 +92,12 @@ const useStyles = createStyles((theme, _params, getRef) => ({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
},
|
},
|
||||||
link: {
|
|
||||||
[`&:hover .${getRef('image')}`]: {
|
|
||||||
// TODO: add styles for image when hovering card
|
|
||||||
},
|
|
||||||
[`&:hover .${getRef('appName')}`]: {
|
|
||||||
// TODO: add styles for app name when hovering card
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const appTileDefinition = {
|
export const appTileDefinition = {
|
||||||
component: AppTile,
|
component: AppTile,
|
||||||
minWidth: 2,
|
minWidth: 1,
|
||||||
|
minHeight: 1,
|
||||||
maxWidth: 12,
|
maxWidth: 12,
|
||||||
minHeight: 2,
|
|
||||||
maxHeight: 12,
|
maxHeight: 12,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const HomarrCardWrapper = ({ ...props }: HomarrCardWrapperProps) => {
|
|||||||
withBorder
|
withBorder
|
||||||
style={{ cursor: isEditMode ? 'move' : 'default' }}
|
style={{ cursor: isEditMode ? 'move' : 'default' }}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
shadow="md"
|
shadow="sm"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,9 +35,13 @@ export const GridstackTileWrapper = ({
|
|||||||
data-type={type}
|
data-type={type}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
gs-x={x}
|
gs-x={x}
|
||||||
|
data-gridstack-x={x}
|
||||||
gs-y={y}
|
gs-y={y}
|
||||||
|
data-gridstack-y={y}
|
||||||
gs-w={width}
|
gs-w={width}
|
||||||
|
data-gridstack-w={width}
|
||||||
gs-h={height}
|
gs-h={height}
|
||||||
|
data-gridstack-h={height}
|
||||||
gs-min-w={minWidth}
|
gs-min-w={minWidth}
|
||||||
gs-min-h={minHeight}
|
gs-min-h={minHeight}
|
||||||
gs-max-w={maxWidth}
|
gs-max-w={maxWidth}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export const initializeGridstack = (
|
|||||||
items: AppType[],
|
items: AppType[],
|
||||||
widgets: IWidget<string, any>[],
|
widgets: IWidget<string, any>[],
|
||||||
isEditMode: boolean,
|
isEditMode: boolean,
|
||||||
|
isLargerThanSm: boolean,
|
||||||
events: {
|
events: {
|
||||||
onChange: (changedNode: GridStackNode) => void;
|
onChange: (changedNode: GridStackNode) => void;
|
||||||
onAdd: (addedNode: GridStackNode) => void;
|
onAdd: (addedNode: GridStackNode) => void;
|
||||||
@@ -19,7 +20,7 @@ export const initializeGridstack = (
|
|||||||
) => {
|
) => {
|
||||||
if (!wrapperRef.current) return;
|
if (!wrapperRef.current) return;
|
||||||
// calculates the currently available count of columns
|
// calculates the currently available count of columns
|
||||||
const columnCount = areaType === 'sidebar' ? 4 : Math.floor(wrapperRef.current.offsetWidth / 64);
|
const columnCount = areaType === 'sidebar' ? 4 : isLargerThanSm || typeof isLargerThanSm === 'undefined' ? 12 : 6;
|
||||||
const minRow = areaType !== 'sidebar' ? 1 : Math.floor(wrapperRef.current.offsetHeight / 64);
|
const minRow = areaType !== 'sidebar' ? 1 : Math.floor(wrapperRef.current.offsetHeight / 64);
|
||||||
// initialize gridstack
|
// initialize gridstack
|
||||||
const newGrid = gridRef;
|
const newGrid = gridRef;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { useConfigContext } from '../../../../config/provider';
|
import { useConfigContext } from '../../../../config/provider';
|
||||||
import { useConfigStore } from '../../../../config/store';
|
import { useConfigStore } from '../../../../config/store';
|
||||||
import { useResize } from '../../../../hooks/use-resize';
|
import { useResize } from '../../../../hooks/use-resize';
|
||||||
|
import { useScreenLargerThan } from '../../../../hooks/useScreenLargerThan';
|
||||||
import { AppType } from '../../../../types/app';
|
import { AppType } from '../../../../types/app';
|
||||||
import { AreaType } from '../../../../types/area';
|
import { AreaType } from '../../../../types/area';
|
||||||
import { IWidget } from '../../../../widgets/widgets';
|
import { IWidget } from '../../../../widgets/widgets';
|
||||||
@@ -31,6 +32,7 @@ export const useGridstack = (
|
|||||||
areaType: 'wrapper' | 'category' | 'sidebar',
|
areaType: 'wrapper' | 'category' | 'sidebar',
|
||||||
areaId: string
|
areaId: string
|
||||||
): UseGristackReturnType => {
|
): UseGristackReturnType => {
|
||||||
|
const isLargerThanSm = useScreenLargerThan('sm');
|
||||||
const isEditMode = useEditModeStore((x) => x.enabled);
|
const isEditMode = useEditModeStore((x) => x.enabled);
|
||||||
const { config, configVersion, name: configName } = useConfigContext();
|
const { config, configVersion, name: configName } = useConfigContext();
|
||||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||||
@@ -78,8 +80,52 @@ export const useGridstack = (
|
|||||||
// change column count depending on the width and the gridRef
|
// change column count depending on the width and the gridRef
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (areaType === 'sidebar') return;
|
if (areaType === 'sidebar') return;
|
||||||
gridRef.current?.column(Math.floor(width / 64), 'moveScale');
|
gridRef.current?.column(
|
||||||
}, [gridRef, width]);
|
isLargerThanSm || typeof isLargerThanSm === 'undefined' ? 12 : 6,
|
||||||
|
(column, prevColumn, newNodes, nodes) => {
|
||||||
|
let nextRow = 0;
|
||||||
|
let available = 6;
|
||||||
|
|
||||||
|
if (column === prevColumn) {
|
||||||
|
newNodes.concat(nodes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.reverse().forEach((node) => {
|
||||||
|
const newnode = node;
|
||||||
|
const width = parseInt(newnode.el!.getAttribute('data-gridstack-w')!, 10);
|
||||||
|
const height = parseInt(newnode.el!.getAttribute('data-gridstack-h')!, 10);
|
||||||
|
const x = parseInt(newnode.el!.getAttribute('data-gridstack-x')!, 10);
|
||||||
|
const y = parseInt(newnode.el!.getAttribute('data-gridstack-y')!, 10);
|
||||||
|
|
||||||
|
if (column === 6) {
|
||||||
|
newnode.x = available >= width ? 6 - available : 0;
|
||||||
|
newnode.y = nextRow;
|
||||||
|
|
||||||
|
if (width > 6) {
|
||||||
|
newnode.w = 6;
|
||||||
|
nextRow += 2;
|
||||||
|
available = 6;
|
||||||
|
} else if (available >= width) {
|
||||||
|
available -= width;
|
||||||
|
if (available === 0) {
|
||||||
|
nextRow += 2;
|
||||||
|
available = 6;
|
||||||
|
}
|
||||||
|
} else if (available < width) {
|
||||||
|
newnode.y = newnode.y! + 2;
|
||||||
|
available = 6 - width;
|
||||||
|
nextRow += 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newnode.x = y % 2 === 1 ? x + 6 : x;
|
||||||
|
newnode.y = Math.floor(y / 2);
|
||||||
|
}
|
||||||
|
newNodes.push(newnode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, [isLargerThanSm]);
|
||||||
|
|
||||||
const onChange = isEditMode
|
const onChange = isEditMode
|
||||||
? (changedNode: GridStackNode) => {
|
? (changedNode: GridStackNode) => {
|
||||||
@@ -236,6 +282,7 @@ export const useGridstack = (
|
|||||||
items,
|
items,
|
||||||
widgets ?? [],
|
widgets ?? [],
|
||||||
isEditMode,
|
isEditMode,
|
||||||
|
isLargerThanSm,
|
||||||
{
|
{
|
||||||
onChange,
|
onChange,
|
||||||
onAdd,
|
onAdd,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Consola from 'consola';
|
import Consola from 'consola';
|
||||||
import { ActionIcon, Button, Group, Paper, Popover, Space, Text } from '@mantine/core';
|
import { ActionIcon, Button, Group, Popover, Text } from '@mantine/core';
|
||||||
import { IconEditCircle, IconEditCircleOff, IconX } from '@tabler/icons';
|
import { IconEditCircle, IconEditCircleOff, IconX } from '@tabler/icons';
|
||||||
import { getCookie } from 'cookies-next';
|
import { getCookie } from 'cookies-next';
|
||||||
import { Trans, useTranslation } from 'next-i18next';
|
import { Trans, useTranslation } from 'next-i18next';
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { CURRENT_VERSION, REPO_URL } from '../../../../data/constants';
|
|||||||
import { useConfigContext } from '../../../config/provider';
|
import { useConfigContext } from '../../../config/provider';
|
||||||
import { Logo } from '../Logo';
|
import { Logo } from '../Logo';
|
||||||
import { useCardStyles } from '../useCardStyles';
|
import { useCardStyles } from '../useCardStyles';
|
||||||
import { AddElementAction } from './Actions/AddElementAction/AddElementAction';
|
|
||||||
import DockerMenuButton from './Actions/Docker/DockerModule';
|
import DockerMenuButton from './Actions/Docker/DockerModule';
|
||||||
import { ToggleEditModeAction } from './Actions/ToggleEditMode/ToggleEditMode';
|
import { ToggleEditModeAction } from './Actions/ToggleEditMode/ToggleEditMode';
|
||||||
import { Search } from './Search';
|
import { Search } from './Search';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ActionIcon, Badge, Menu, Tooltip } from '@mantine/core';
|
import { ActionIcon, Badge, Menu } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { IconInfoCircle, IconMenu2, IconSettings } from '@tabler/icons';
|
import { IconInfoCircle, IconMenu2, IconSettings } from '@tabler/icons';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { PasswordInput, Paper, Title, Text, Container, Group, Button } from '@mantine/core';
|
import { PasswordInput, Paper, Title, Text, Container, Button } from '@mantine/core';
|
||||||
import { setCookie } from 'cookies-next';
|
import { setCookie } from 'cookies-next';
|
||||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|||||||
@@ -13,15 +13,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@for $i from 1 to 13 {
|
@for $i from 1 to 13 {
|
||||||
.grid-stack>.grid-stack-item[gs-w="#{$i}"] { width: $i * 64px }
|
.grid-stack>.grid-stack-item[gs-w="#{$i}"] { width: ($i / 12) * 100 + "%" }
|
||||||
.grid-stack>.grid-stack-item[gs-min-w="#{$i}"] { min-width: $i * 64px }
|
.grid-stack>.grid-stack-item[gs-min-w="#{$i}"] { min-width: ($i / 12) * 100 + "%" }
|
||||||
.grid-stack>.grid-stack-item[gs-max-w="#{$i}"] { max-width: $i * 64px }
|
.grid-stack>.grid-stack-item[gs-max-w="#{$i}"] { max-width: ($i / 12) * 100 + "%" }
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 to 96 {
|
||||||
|
.grid-stack>.grid-stack-item[gs-h="#{$i}"] { height: $i * 64 + "px" }
|
||||||
|
.grid-stack>.grid-stack-item[gs-min-h="#{$i}"] { min-height: $i * 64 + "px" }
|
||||||
|
.grid-stack>.grid-stack-item[gs-max-h="#{$i}"] { max-height: $i * 64 + "px" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@for $i from 1 to 13 {
|
@for $i from 1 to 13 {
|
||||||
.grid-stack>.grid-stack-item[gs-h="#{$i}"] { height: $i * 64px; }
|
.grid-stack>.grid-stack-item[gs-x="#{$i}"] { left: ($i / 12) * 100 + "%" }
|
||||||
.grid-stack>.grid-stack-item[gs-min-h="#{$i}"] { min-height: $i * 64px; }
|
}
|
||||||
.grid-stack>.grid-stack-item[gs-max-h="#{$i}"] { max-height: $i * 64px; }
|
|
||||||
|
|
||||||
|
@for $i from 1 to 96 {
|
||||||
|
.grid-stack>.grid-stack-item[gs-y="#{$i}"] { top: $i * 64 + "px" }
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-stack>.grid-stack-item>.grid-stack-item-content,
|
.grid-stack>.grid-stack-item>.grid-stack-item-content,
|
||||||
@@ -35,16 +44,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.grid-stack>.grid-stack-item {
|
.grid-stack>.grid-stack-item {
|
||||||
min-width: 64px;
|
min-width: (1/12)+'%';
|
||||||
}
|
|
||||||
|
|
||||||
@for $i from 1 to 96 {
|
|
||||||
.grid-stack>.grid-stack-item[gs-x="#{$i}"] { left: $i * 64px }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@for $i from 1 to 96 {
|
|
||||||
.grid-stack>.grid-stack-item[gs-y="#{$i}"] { top: $i * 64px }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-stack > .grid-stack-item > .grid-stack-item-content {
|
.grid-stack > .grid-stack-item > .grid-stack-item-content {
|
||||||
@@ -54,3 +54,19 @@
|
|||||||
.grid-stack.grid-stack-animate {
|
.grid-stack.grid-stack-animate {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
@for $i from 1 to 7 {
|
||||||
|
.grid-stack>.grid-stack-item[gs-w="#{$i}"] { width: percentage(($i / 6)) !important }
|
||||||
|
.grid-stack>.grid-stack-item[gs-min-w="#{$i}"] { min-width: percentage(($i / 6)) !important }
|
||||||
|
.grid-stack>.grid-stack-item[gs-max-w="#{$i}"] { max-width: percentage(($i / 6)) !important }
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 to 7 {
|
||||||
|
.grid-stack>.grid-stack-item[gs-x="#{$i}"] { left: percentage(($i / 6)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-stack>.grid-stack-item {
|
||||||
|
min-width: percentage(1/6) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Center, createStyles, MantineThemeColors, useMantineTheme } from '@mantine/core';
|
import { createStyles, Group, MantineThemeColors, useMantineTheme } from '@mantine/core';
|
||||||
import { Calendar } from '@mantine/dates';
|
import { Calendar } from '@mantine/dates';
|
||||||
import { IconCalendarTime } from '@tabler/icons';
|
import { IconCalendarTime } from '@tabler/icons';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
@@ -21,7 +21,7 @@ const definition = defineWidget({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
gridstack: {
|
gridstack: {
|
||||||
minWidth: 4,
|
minWidth: 3,
|
||||||
minHeight: 5,
|
minHeight: 5,
|
||||||
maxWidth: 12,
|
maxWidth: 12,
|
||||||
maxHeight: 12,
|
maxHeight: 12,
|
||||||
@@ -55,35 +55,37 @@ function CalendarTile({ widget }: CalendarTileProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Calendar
|
<Group grow style={{ height: '100%' }}>
|
||||||
month={month}
|
<Calendar
|
||||||
// Should be offset 5px to the left
|
month={month}
|
||||||
style={{ position: 'relative', left: -10, top: -10 }}
|
// Should be offset 5px to the left
|
||||||
onMonthChange={setMonth}
|
style={{ position: 'relative', top: -10 }}
|
||||||
size="xs"
|
onMonthChange={setMonth}
|
||||||
fullWidth
|
size="xs"
|
||||||
onChange={() => {}}
|
fullWidth
|
||||||
firstDayOfWeek={widget.properties.sundayStart ? 'sunday' : 'monday'}
|
onChange={() => {}}
|
||||||
dayStyle={(date) => ({
|
firstDayOfWeek={widget.properties.sundayStart ? 'sunday' : 'monday'}
|
||||||
margin: 1,
|
dayStyle={(date) => ({
|
||||||
backgroundColor: isToday(date)
|
margin: 1,
|
||||||
? colorScheme === 'dark'
|
backgroundColor: isToday(date)
|
||||||
? colors.dark[5]
|
? colorScheme === 'dark'
|
||||||
: colors.gray[0]
|
? colors.dark[5]
|
||||||
: undefined,
|
: colors.gray[0]
|
||||||
})}
|
: undefined,
|
||||||
styles={{
|
})}
|
||||||
calendarHeader: {
|
styles={{
|
||||||
marginRight: 40,
|
calendarHeader: {
|
||||||
marginLeft: 40,
|
marginRight: 40,
|
||||||
},
|
marginLeft: 40,
|
||||||
}}
|
},
|
||||||
allowLevelChange={false}
|
}}
|
||||||
dayClassName={(_, modifiers) => cx({ [classes.weekend]: modifiers.weekend })}
|
allowLevelChange={false}
|
||||||
renderDay={(date) => (
|
dayClassName={(_, modifiers) => cx({ [classes.weekend]: modifiers.weekend })}
|
||||||
<CalendarDay date={date} medias={getReleasedMediasForDate(medias, date)} />
|
renderDay={(date) => (
|
||||||
)}
|
<CalendarDay date={date} medias={getReleasedMediasForDate(medias, date)} />
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const definition = defineWidget({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
gridstack: {
|
gridstack: {
|
||||||
minWidth: 4,
|
minWidth: 2,
|
||||||
minHeight: 2,
|
minHeight: 2,
|
||||||
maxWidth: 12,
|
maxWidth: 12,
|
||||||
maxHeight: 12,
|
maxHeight: 12,
|
||||||
@@ -35,12 +35,10 @@ function DateTile({ widget }: DateTileProps) {
|
|||||||
const formatString = widget.properties.display24HourFormat ? 'HH:mm' : 'h:mm A';
|
const formatString = widget.properties.display24HourFormat ? 'HH:mm' : 'h:mm A';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Center style={{ height: '100%' }}>
|
<Stack spacing="xs" justify="space-around" align="center" style={{ height: '100%' }}>
|
||||||
<Stack spacing="xs">
|
<Title>{dayjs(date).format(formatString)}</Title>
|
||||||
<Title>{dayjs(date).format(formatString)}</Title>
|
<Text size="lg">{dayjs(date).format('dddd, MMMM D')}</Text>
|
||||||
<Text size="lg">{dayjs(date).format('dddd, MMMM D')}</Text>
|
</Stack>
|
||||||
</Stack>
|
|
||||||
</Center>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const WeatherIcon = ({ code }: WeatherIconProps) => {
|
|||||||
return (
|
return (
|
||||||
<Tooltip withinPortal withArrow label={t(`card.weatherDescriptions.${name}`)}>
|
<Tooltip withinPortal withArrow label={t(`card.weatherDescriptions.${name}`)}>
|
||||||
<Box>
|
<Box>
|
||||||
<Icon size={50} />
|
<Icon style={{ float: 'left' }} size={50} />
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const definition = defineWidget({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
gridstack: {
|
gridstack: {
|
||||||
minWidth: 4,
|
minWidth: 2,
|
||||||
minHeight: 2,
|
minHeight: 2,
|
||||||
maxWidth: 12,
|
maxWidth: 12,
|
||||||
maxHeight: 12,
|
maxHeight: 12,
|
||||||
@@ -61,39 +61,34 @@ function WeatherTile({ widget }: WeatherTileProps) {
|
|||||||
|
|
||||||
// TODO: add widgetWrapper that is generic and uses the definition
|
// TODO: add widgetWrapper that is generic and uses the definition
|
||||||
return (
|
return (
|
||||||
<Center style={{ height: '100%' }}>
|
<Stack
|
||||||
<Group spacing="md" noWrap align="center">
|
spacing="xs"
|
||||||
|
justify="space-around"
|
||||||
|
align="center"
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
>
|
||||||
|
<Group grow>
|
||||||
<WeatherIcon code={weather!.current_weather.weathercode} />
|
<WeatherIcon code={weather!.current_weather.weathercode} />
|
||||||
<Stack p={0} spacing={4}>
|
<Title order={2}>
|
||||||
<Title order={2}>
|
{getPerferedUnit(
|
||||||
{getPerferedUnit(
|
weather!.current_weather.temperature,
|
||||||
weather!.current_weather.temperature,
|
widget.properties.displayInFahrenheit
|
||||||
widget.properties.displayInFahrenheit
|
)}
|
||||||
)}
|
</Title>
|
||||||
</Title>
|
|
||||||
<Group spacing="xs" noWrap>
|
|
||||||
<div>
|
|
||||||
<span>
|
|
||||||
{getPerferedUnit(
|
|
||||||
weather!.daily.temperature_2m_max[0],
|
|
||||||
widget.properties.displayInFahrenheit
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<IconArrowUpRight size={16} style={{ right: 15 }} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>
|
|
||||||
{getPerferedUnit(
|
|
||||||
weather!.daily.temperature_2m_min[0],
|
|
||||||
widget.properties.displayInFahrenheit
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<IconArrowDownRight size={16} />
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Group>
|
</Group>
|
||||||
</Center>
|
<Group noWrap spacing="xs">
|
||||||
|
<IconArrowUpRight />
|
||||||
|
{getPerferedUnit(
|
||||||
|
weather!.daily.temperature_2m_max[0],
|
||||||
|
widget.properties.displayInFahrenheit
|
||||||
|
)}
|
||||||
|
<IconArrowDownRight />
|
||||||
|
{getPerferedUnit(
|
||||||
|
weather!.daily.temperature_2m_min[0],
|
||||||
|
widget.properties.displayInFahrenheit
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user