feat(app-widget): show description in widget (#3876)
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
import SuperJSON from "superjson";
|
||||||
|
|
||||||
|
import { eq } from "../..";
|
||||||
|
import type { Database } from "../..";
|
||||||
|
import { items } from "../../schema";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To support showing the description in the widget itself we replaced
|
||||||
|
* the tooltip show option with display mode.
|
||||||
|
*/
|
||||||
|
export async function migrateAppWidgetShowDescriptionTooltipToDisplayModeAsync(db: Database) {
|
||||||
|
const existingAppItems = await db.query.items.findMany({
|
||||||
|
where: (table, { eq }) => eq(table.kind, "app"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemsToUpdate = existingAppItems
|
||||||
|
.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
options: SuperJSON.parse<{ showDescriptionTooltip?: boolean }>(item.options),
|
||||||
|
}))
|
||||||
|
.filter((item) => item.options.showDescriptionTooltip !== undefined);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Migrating app items with showDescriptionTooltip to descriptionDisplayMode count=${itemsToUpdate.length}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
itemsToUpdate.map(async (item) => {
|
||||||
|
const { showDescriptionTooltip, ...options } = item.options;
|
||||||
|
await db
|
||||||
|
.update(items)
|
||||||
|
.set({
|
||||||
|
options: SuperJSON.stringify({
|
||||||
|
...options,
|
||||||
|
descriptionDisplayMode: showDescriptionTooltip ? "tooltip" : "hidden",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.where(eq(items.id, item.id));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Migrated app items with showDescriptionTooltip to descriptionDisplayMode count=${itemsToUpdate.length}`);
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import type { Database } from "../..";
|
import type { Database } from "../..";
|
||||||
import { migrateReleaseWidgetProviderToOptionsAsync } from "./0000_release_widget_provider_to_options";
|
import { migrateReleaseWidgetProviderToOptionsAsync } from "./0000_release_widget_provider_to_options";
|
||||||
import { migrateOpnsenseCredentialsAsync } from "./0001_opnsense_credentials";
|
import { migrateOpnsenseCredentialsAsync } from "./0001_opnsense_credentials";
|
||||||
|
import { migrateAppWidgetShowDescriptionTooltipToDisplayModeAsync } from "./0002_app_widget_show_description_tooltip_to_display_mode";
|
||||||
|
|
||||||
export const applyCustomMigrationsAsync = async (db: Database) => {
|
export const applyCustomMigrationsAsync = async (db: Database) => {
|
||||||
await migrateReleaseWidgetProviderToOptionsAsync(db);
|
await migrateReleaseWidgetProviderToOptionsAsync(db);
|
||||||
await migrateOpnsenseCredentialsAsync(db);
|
await migrateOpnsenseCredentialsAsync(db);
|
||||||
|
await migrateAppWidgetShowDescriptionTooltipToDisplayModeAsync(db);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ export const mapApp = (
|
|||||||
appId: appsMap.get(app.id)?.id!,
|
appId: appsMap.get(app.id)?.id!,
|
||||||
openInNewTab: app.behaviour.isOpeningNewTab,
|
openInNewTab: app.behaviour.isOpeningNewTab,
|
||||||
pingEnabled: app.network.enabledStatusChecker,
|
pingEnabled: app.network.enabledStatusChecker,
|
||||||
showDescriptionTooltip: app.behaviour.tooltipDescription !== "",
|
|
||||||
showTitle: app.appearance.appNameStatus === "normal",
|
showTitle: app.appearance.appNameStatus === "normal",
|
||||||
layout: app.appearance.positionAppName,
|
layout: app.appearance.positionAppName,
|
||||||
|
descriptionDisplayMode: app.behaviour.tooltipDescription !== "" ? "tooltip" : "hidden",
|
||||||
} satisfies WidgetComponentProps<"app">["options"]),
|
} satisfies WidgetComponentProps<"app">["options"]),
|
||||||
layouts: boardSizes.map((size) => {
|
layouts: boardSizes.map((size) => {
|
||||||
const shapeForSize = app.shape[size];
|
const shapeForSize = app.shape[size];
|
||||||
|
|||||||
@@ -1266,9 +1266,6 @@
|
|||||||
"showTitle": {
|
"showTitle": {
|
||||||
"label": "Show app name"
|
"label": "Show app name"
|
||||||
},
|
},
|
||||||
"showDescriptionTooltip": {
|
|
||||||
"label": "Show description tooltip"
|
|
||||||
},
|
|
||||||
"pingEnabled": {
|
"pingEnabled": {
|
||||||
"label": "Enable status check"
|
"label": "Enable status check"
|
||||||
},
|
},
|
||||||
@@ -1280,6 +1277,15 @@
|
|||||||
"column": "Vertical",
|
"column": "Vertical",
|
||||||
"column-reverse": "Vertical (reversed)"
|
"column-reverse": "Vertical (reversed)"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"descriptionDisplayMode": {
|
||||||
|
"label": "Description display mode",
|
||||||
|
"description": "Choose how to display the app description",
|
||||||
|
"option": {
|
||||||
|
"normal": "Within widget",
|
||||||
|
"tooltip": "As tooltip",
|
||||||
|
"hidden": "Hidden"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import { Fragment, Suspense } from "react";
|
import { Fragment, Suspense } from "react";
|
||||||
import { Flex, Text, Tooltip, UnstyledButton } from "@mantine/core";
|
import { Flex, rem, Stack, Text, Tooltip, UnstyledButton } from "@mantine/core";
|
||||||
import { IconLoader } from "@tabler/icons-react";
|
import { IconLoader } from "@tabler/icons-react";
|
||||||
import combineClasses from "clsx";
|
import combineClasses from "clsx";
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
|
|||||||
))}
|
))}
|
||||||
position="right-start"
|
position="right-start"
|
||||||
multiline
|
multiline
|
||||||
disabled={!options.showDescriptionTooltip || !app.description}
|
disabled={options.descriptionDisplayMode !== "tooltip" || !app.description || isEditMode}
|
||||||
styles={{ tooltip: { maxWidth: 300 } }}
|
styles={{ tooltip: { maxWidth: 300 } }}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
@@ -87,16 +87,34 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
|
|||||||
align="center"
|
align="center"
|
||||||
gap={isColumnLayout ? 0 : "sm"}
|
gap={isColumnLayout ? 0 : "sm"}
|
||||||
>
|
>
|
||||||
{options.showTitle && (
|
<Stack gap={0}>
|
||||||
<Text
|
{options.showTitle && (
|
||||||
className="app-title"
|
<Text
|
||||||
fw={700}
|
className="app-title"
|
||||||
size={isTiny ? "8px" : "sm"}
|
fw={700}
|
||||||
ta={isColumnLayout ? "center" : undefined}
|
size={isTiny ? rem(8) : "sm"}
|
||||||
>
|
ta={isColumnLayout ? "center" : undefined}
|
||||||
{app.name}
|
>
|
||||||
</Text>
|
{app.name}
|
||||||
)}
|
</Text>
|
||||||
|
)}
|
||||||
|
{options.descriptionDisplayMode === "normal" && (
|
||||||
|
<Text
|
||||||
|
className="app-description"
|
||||||
|
size={isTiny ? rem(8) : "sm"}
|
||||||
|
ta={isColumnLayout ? "center" : undefined}
|
||||||
|
c="dimmed"
|
||||||
|
lineClamp={4}
|
||||||
|
>
|
||||||
|
{app.description?.split("\n").map((line, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
{line}
|
||||||
|
<br />
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
<MaskedOrNormalImage
|
<MaskedOrNormalImage
|
||||||
imageUrl={app.iconUrl}
|
imageUrl={app.iconUrl}
|
||||||
hasColor={board.iconColor !== null}
|
hasColor={board.iconColor !== null}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
IconApps,
|
IconApps,
|
||||||
IconDeviceDesktopX,
|
IconDeviceDesktopX,
|
||||||
|
IconEyeOff,
|
||||||
IconLayoutBottombarExpand,
|
IconLayoutBottombarExpand,
|
||||||
IconLayoutNavbarExpand,
|
IconLayoutNavbarExpand,
|
||||||
IconLayoutSidebarLeftExpand,
|
IconLayoutSidebarLeftExpand,
|
||||||
IconLayoutSidebarRightExpand,
|
IconLayoutSidebarRightExpand,
|
||||||
|
IconTextScan2,
|
||||||
|
IconTooltip,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
|
|
||||||
import { createWidgetDefinition } from "../definition";
|
import { createWidgetDefinition } from "../definition";
|
||||||
@@ -18,7 +21,34 @@ export const { definition, componentLoader } = createWidgetDefinition("app", {
|
|||||||
appId: factory.app(),
|
appId: factory.app(),
|
||||||
openInNewTab: factory.switch({ defaultValue: true }),
|
openInNewTab: factory.switch({ defaultValue: true }),
|
||||||
showTitle: factory.switch({ defaultValue: true }),
|
showTitle: factory.switch({ defaultValue: true }),
|
||||||
showDescriptionTooltip: factory.switch({ defaultValue: false }),
|
descriptionDisplayMode: factory.select({
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label(t) {
|
||||||
|
return t("widget.app.option.descriptionDisplayMode.option.normal");
|
||||||
|
},
|
||||||
|
value: "normal",
|
||||||
|
icon: IconTextScan2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label(t) {
|
||||||
|
return t("widget.app.option.descriptionDisplayMode.option.tooltip");
|
||||||
|
},
|
||||||
|
value: "tooltip",
|
||||||
|
icon: IconTooltip,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label(t) {
|
||||||
|
return t("widget.app.option.descriptionDisplayMode.option.hidden");
|
||||||
|
},
|
||||||
|
value: "hidden",
|
||||||
|
icon: IconEyeOff,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: "hidden",
|
||||||
|
searchable: true,
|
||||||
|
withDescription: true,
|
||||||
|
}),
|
||||||
layout: factory.select({
|
layout: factory.select({
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user