From bdc586d854661960c06ff949be1896ccf3213c95 Mon Sep 17 00:00:00 2001 From: ewsgit Date: Thu, 19 Mar 2026 23:22:35 +0000 Subject: [PATCH] minor fixes and adjustments --- .prettierrc | 4 - .zed/settings.json | 29 ++ .../web/widgets/weather/Widget.module.scss | 64 ++-- .../web/widgets/weather/Widget.tsx | 98 +++--- .../uk.tcsw.settings/backend/index.ts | 21 +- applications/uk.tcsw.settings/web/App.tsx | 43 ++- .../uk.tcsw.settings/web/appContext.ts | 13 + .../MethodPassword/MethodPassword.tsx | 14 +- .../authentication/reset-password/Index.tsx | 16 - .../pages/instance/components/Users/Users.tsx | 32 +- .../CreateUser/CreateUser.module.scss | 17 +- .../components/CreateUser/CreateUser.tsx | 78 +++-- .../components/Users/components/User/User.tsx | 130 +++++++- .../web/pages/profile/Index.module.scss | 1 - .../web/pages/profile/Index.tsx | 152 +++++---- .../web/pages/root/index.module.scss | 19 +- .../uk.tcsw.settings/web/pages/root/index.tsx | 177 ++++++---- .../web/pages/manage-installed/Index.tsx | 311 +++++++++--------- biome.json | 26 ++ bun.lock | 21 ++ instance/src/systems/configuration.ts | 18 +- instance/src/systems/users.ts | 2 +- package.json | 3 + .../components/switch/UKSwitch.module.scss | 26 ++ .../src/components/switch/UKSwitch.tsx | 32 +- .../src/components/textField/UKTextField.tsx | 227 +++++++------ 26 files changed, 966 insertions(+), 608 deletions(-) delete mode 100644 .prettierrc create mode 100644 .zed/settings.json create mode 100644 applications/uk.tcsw.settings/web/appContext.ts delete mode 100644 applications/uk.tcsw.settings/web/pages/authentication/reset-password/Index.tsx create mode 100644 biome.json diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 8d7b4cd..0000000 --- a/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "printWidth": 80, - "tabWidth": 2 -} diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..c9d9982 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,29 @@ +// Folder-specific settings +// +// For a full list of overridable settings, and general information on folder-specific settings, +// see the documentation: https://zed.dev/docs/configuring-zed#settings-files +{ + "languages": { + "JavaScript": { + "formatter": { "language_server": { "name": "biome" } }, + "code_actions_on_format": { + "source.fixAll.biome": true, + "source.organizeImports.biome": true + } + }, + "TypeScript": { + "formatter": { "language_server": { "name": "biome" } }, + "code_actions_on_format": { + "source.fixAll.biome": true, + "source.organizeImports.biome": true + } + }, + "TSX": { + "formatter": { "language_server": { "name": "biome" } }, + "code_actions_on_format": { + "source.fixAll.biome": true, + "source.organizeImports.biome": true + } + } + } +} diff --git a/applications/uk.tcsw.dashboard/web/widgets/weather/Widget.module.scss b/applications/uk.tcsw.dashboard/web/widgets/weather/Widget.module.scss index c1c154c..8087a4b 100644 --- a/applications/uk.tcsw.dashboard/web/widgets/weather/Widget.module.scss +++ b/applications/uk.tcsw.dashboard/web/widgets/weather/Widget.module.scss @@ -1,56 +1,60 @@ .root { - display: flex; - flex-direction: column; - grid-row: span 2; - gap: 0.5rem; + display: flex; + flex-direction: column; + grid-row: span 2; + gap: 0.5rem; } .header { - display: flex; - flex-direction: row; - justify-content: space-between; + display: flex; + flex-direction: row; + justify-content: space-between; } .overviewContainer { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .overviewCondition { - display: flex; - flex-direction: row; - gap: 0.5rem; + display: flex; + flex-direction: row; + gap: 0.5rem; } .overviewIcon { - --icon-size: 2rem; + --icon-size: 2rem; } .overviewHighLow { - display: flex; - flex-direction: row; - gap: 0.25rem; - align-items: center; + display: flex; + flex-direction: row; + gap: 0.25rem; + align-items: center; } .overviewHighLowSeparator { - font-size: 2rem !important; + font-size: 2rem !important; } .hours { - height: 100%; - display: flex; - flex-direction: row; - padding: 0.25rem; - overflow-x: auto; + height: 100%; + display: flex; + flex-direction: row; + padding: 0.25rem; + overflow-x: auto; + + &::-webkit-scrollbar { + display: none; + } } .dataSource { - margin-top: -0.25rem; - margin-bottom: -0.75rem; - text-align: center; - margin-left: auto; - margin-right: auto; - font-size: 0.5rem !important; - opacity: 0.5; + margin-top: -0.25rem; + margin-bottom: -0.75rem; + text-align: center; + margin-left: auto; + margin-right: auto; + font-size: 0.5rem !important; + opacity: 0.5; } diff --git a/applications/uk.tcsw.dashboard/web/widgets/weather/Widget.tsx b/applications/uk.tcsw.dashboard/web/widgets/weather/Widget.tsx index 68819af..17cbc1e 100644 --- a/applications/uk.tcsw.dashboard/web/widgets/weather/Widget.tsx +++ b/applications/uk.tcsw.dashboard/web/widgets/weather/Widget.tsx @@ -1,59 +1,59 @@ import UKCard from "@tcsw/uikit-solid/src/components/card/UKCard.tsx"; -import styles from "./Widget.module.scss"; -import type { Component } from "solid-js"; -import UKText from "@tcsw/uikit-solid/src/components/text/UKText.tsx"; import UKIcon from "@tcsw/uikit-solid/src/components/icon/UKIcon.jsx"; +import UKText from "@tcsw/uikit-solid/src/components/text/UKText.tsx"; +import type { Component } from "solid-js"; import Hour from "./components/hour/Hour"; +import styles from "./Widget.module.scss"; const Widget: Component = () => { - return ( - - - Weather For [LocationName] + return ( + + + Weather For [Loc] (No weather app installed) + +
+
+
+ cloud + + Cloudy + +
+ + Feels like 5° + +
+ + 8° + + + · -
-
-
- cloud - - Cloudy - -
- - Feels like 5° - -
- - 8° - - - · - - - 6° - -
-
- - 7° - -
- - - - - - - - - - - - - Source Open-Meteo + + 6° - - ); +
+
+ + 7° + +
+ + + + + + + + + + + + + Source Open-Meteo + +
+ ); }; export default Widget; diff --git a/applications/uk.tcsw.settings/backend/index.ts b/applications/uk.tcsw.settings/backend/index.ts index b8dde74..97b56f4 100644 --- a/applications/uk.tcsw.settings/backend/index.ts +++ b/applications/uk.tcsw.settings/backend/index.ts @@ -288,6 +288,12 @@ const router = t.router({ }), }, instance: { + hasFeature: procedure + .input(z.string()) + .output(z.boolean()) + .query(async (opt) => { + return opt.ctx.instance.sys.configuration.hasFeature(opt.input); + }), getUsers: adminProcedure.output(z.number().array()).query(async (_opt) => { const users = await instance.sys.users.getAllUsers(); @@ -391,7 +397,12 @@ const router = t.router({ return email || "unknown"; }), setEmail: adminProcedure - .input(z.object({ userId: z.number(), email: z.email() })) + .input( + z.object({ + userId: z.number(), + email: z.email().or(z.literal("unknown")), + }), + ) .mutation(async (opt) => { await ( await opt.ctx.instance.sys.users.getUserById(opt.input.userId) @@ -418,6 +429,12 @@ const router = t.router({ return true; }), + getIsMe: adminProcedure + .input(z.number()) + .output(z.boolean()) + .query(async (opt) => { + return opt.input === opt.ctx.userId; + }), delete: adminProcedure .input(z.object({ userId: z.number() })) .mutation(async (opt) => { @@ -465,7 +482,7 @@ const router = t.router({ return await user.isAdministrator(); } - return this; + return false; }), createUser: procedure .input(z.object({ username: z.string() })) diff --git a/applications/uk.tcsw.settings/web/App.tsx b/applications/uk.tcsw.settings/web/App.tsx index e0fcc86..5469783 100644 --- a/applications/uk.tcsw.settings/web/App.tsx +++ b/applications/uk.tcsw.settings/web/App.tsx @@ -1,28 +1,45 @@ -import { type Component } from "solid-js"; -import RootPage from "./pages/root"; import { Route } from "@solidjs/router"; +import { type Component, createEffect, createSignal } from "solid-js"; +import { AppContext } from "./appContext.ts"; import Layout from "./Layout"; -import ProfilePage from "./pages/profile/Index"; +import trpc from "./lib/trpc.ts"; +import ApplicationPage from "./pages/applications/applicationId/Index.tsx"; +import ApplicationsPage from "./pages/applications/Index.tsx"; import AuthenticationPage from "./pages/authentication/Index"; -import StoragePage from "./pages/storage/Index"; -import CustomizationPage from "./pages/customization/Index"; -import InstancePage from "./pages/instance/Index"; -import ResetPasswordPage from "./pages/authentication/reset-password/Index"; -import WallpaperPage from "./pages/customization/wallpaper/Index.tsx"; import ColorThemePage from "./pages/customization/colorTheme/Index.tsx"; -import ApplicationsPage from "./pages/applications/Index.tsx"; -import ApplicationPage from "./pages/applications/applicationId/Index.tsx"; +import CustomizationPage from "./pages/customization/Index"; import QuickShortcutsPage from "./pages/customization/quickShortcuts/Index.tsx"; +import WallpaperPage from "./pages/customization/wallpaper/Index.tsx"; +import InstancePage from "./pages/instance/Index"; +import ProfilePage from "./pages/profile/Index"; +import RootPage from "./pages/root"; +import StoragePage from "./pages/storage/Index"; const App: Component = () => { + const [isAdministrator, setIsAdministrator] = createSignal(false); + const [shootYourselfInTheFoot, setShootYourselfInTheFoot] = + createSignal(false); + + createEffect(async () => { + setIsAdministrator(await trpc.instance.isUserAdministrator.query()); + setShootYourselfInTheFoot( + await trpc.instance.hasFeature.query("shoot_yourself_in_the_foot"), + ); + }); + return ( - <> + - @@ -37,7 +54,7 @@ const App: Component = () => { - + ); }; diff --git a/applications/uk.tcsw.settings/web/appContext.ts b/applications/uk.tcsw.settings/web/appContext.ts new file mode 100644 index 0000000..7ef51cd --- /dev/null +++ b/applications/uk.tcsw.settings/web/appContext.ts @@ -0,0 +1,13 @@ +import { createContext, type Accessor } from "solid-js"; + +const AppContext = createContext<{ + isAdministrator: Accessor; + shootYourselfInTheFoot: Accessor; + setShootYourselfInTheFoot: (value: boolean) => void; +}>({ + isAdministrator: () => false, + shootYourselfInTheFoot: () => false, + setShootYourselfInTheFoot: () => {}, +}); + +export { AppContext }; diff --git a/applications/uk.tcsw.settings/web/pages/authentication/components/MethodPassword/MethodPassword.tsx b/applications/uk.tcsw.settings/web/pages/authentication/components/MethodPassword/MethodPassword.tsx index c7f5adb..bbb8d37 100644 --- a/applications/uk.tcsw.settings/web/pages/authentication/components/MethodPassword/MethodPassword.tsx +++ b/applications/uk.tcsw.settings/web/pages/authentication/components/MethodPassword/MethodPassword.tsx @@ -1,5 +1,9 @@ +import { useSearchParams } from "@solidjs/router"; import UKButton from "@tcsw/uikit-solid/src/components/button/UKButton.jsx"; +import UKDialog from "@tcsw/uikit-solid/src/components/dialog/UKDialog.tsx"; +import UKIcon from "@tcsw/uikit-solid/src/components/icon/UKIcon.jsx"; import UKStackItem from "@tcsw/uikit-solid/src/components/stack/UKStackItem.jsx"; +import UKText from "@tcsw/uikit-solid/src/components/text/UKText.jsx"; import { type Component, createResource, @@ -7,17 +11,17 @@ import { Suspense, } from "solid-js"; import trpc from "../../../../lib/trpc"; -import styles from "./MethodPassword.module.scss"; -import UKText from "@tcsw/uikit-solid/src/components/text/UKText.jsx"; -import UKIcon from "@tcsw/uikit-solid/src/components/icon/UKIcon.jsx"; -import UKDialog from "@tcsw/uikit-solid/src/components/dialog/UKDialog.tsx"; import ResetPasswordDialogue from "./components/ResetPasswordDialogue/ResetPasswordDialogue.tsx"; +import styles from "./MethodPassword.module.scss"; const MethodPassword: Component = () => { const [hasPassword, { refetch: refetchHasPassword }] = createResource(() => trpc.authentication.hasPassword.query(), ); - const [showDialog, setShowDialog] = createSignal(false); + const [urlSearchParams] = useSearchParams(); + const [showDialog, setShowDialog] = createSignal( + urlSearchParams["change-passsword"] === "true", + ); return ( <> diff --git a/applications/uk.tcsw.settings/web/pages/authentication/reset-password/Index.tsx b/applications/uk.tcsw.settings/web/pages/authentication/reset-password/Index.tsx deleted file mode 100644 index 64aaaf8..0000000 --- a/applications/uk.tcsw.settings/web/pages/authentication/reset-password/Index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { createEffect, type Component } from "solid-js"; -import { useNavigate } from "@solidjs/router"; - -const ResetPasswordPage: Component = () => { - const navigate = useNavigate(); - - createEffect(() => { - navigate("/app/uk.tcsw.settings/"); - alert("Implement new UIKit dialogue"); - // dialogue.show(); - }); - - return <>; -}; - -export default ResetPasswordPage; diff --git a/applications/uk.tcsw.settings/web/pages/instance/components/Users/Users.tsx b/applications/uk.tcsw.settings/web/pages/instance/components/Users/Users.tsx index 62206d5..44ef33a 100644 --- a/applications/uk.tcsw.settings/web/pages/instance/components/Users/Users.tsx +++ b/applications/uk.tcsw.settings/web/pages/instance/components/Users/Users.tsx @@ -6,23 +6,23 @@ import CreateUser from "./components/CreateUser/CreateUser"; import UKStackLabel from "@tcsw/uikit-solid/src/components/stack/UKStackLabel.tsx"; const Users: Component = () => { - const [users, { refetch: refetchUsers }] = createResource(() => trpc.instance.getUsers.query()); + const [users, { refetch: refetchUsers }] = createResource(() => + trpc.instance.getUsers.query(), + ); - return ( - <> - - Instance Users - - - - {(userId) => { - return refetchUsers()} userId={userId} />; - }} - - - refetchUsers()} /> - - ); + return ( + <> + Instance Users + + + {(userId) => { + return refetchUsers()} userId={userId} />; + }} + + + refetchUsers()} /> + + ); }; export default Users; diff --git a/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/CreateUser/CreateUser.module.scss b/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/CreateUser/CreateUser.module.scss index 042a654..c8e08cc 100644 --- a/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/CreateUser/CreateUser.module.scss +++ b/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/CreateUser/CreateUser.module.scss @@ -1,6 +1,15 @@ .expanded { - padding: 1rem; - display: flex; - flex-direction: column; - gap: 1rem; + padding: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.component { + max-width: 64rem; + display: flex; + width: 100%; + flex-direction: row; + align-items: center; + justify-content: end; } diff --git a/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/CreateUser/CreateUser.tsx b/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/CreateUser/CreateUser.tsx index 4dffbcc..f573830 100644 --- a/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/CreateUser/CreateUser.tsx +++ b/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/CreateUser/CreateUser.tsx @@ -1,35 +1,61 @@ import UKButton from "@tcsw/uikit-solid/src/components/button/UKButton.jsx"; -import UKStack from "@tcsw/uikit-solid/src/components/stack/UKStack.jsx"; -import UKStackItem from "@tcsw/uikit-solid/src/components/stack/UKStackItem.jsx"; +import UKDialog from "@tcsw/uikit-solid/src/components/dialog/UKDialog.jsx"; +import UKDivider from "@tcsw/uikit-solid/src/components/divider/UKDivider.jsx"; +import UKText from "@tcsw/uikit-solid/src/components/text/UKText.jsx"; import UKTextField from "@tcsw/uikit-solid/src/components/textField/UKTextField.jsx"; -import { createSignal, type Component } from "solid-js"; -import styles from "./CreateUser.module.scss"; +import { type Component, createSignal } from "solid-js"; import trpc from "../../../../../../lib/trpc"; +import styles from "./CreateUser.module.scss"; const CreateUser: Component<{ updateUsers: () => void }> = (props) => { - const [username, setUsername] = createSignal(""); + const [username, setUsername] = createSignal(""); + const [showCreateUserDialog, setShowCreateUserDialog] = + createSignal(false); - return ( - - - - { - await trpc.instance.createUser.mutate({ username: username() }); - props.updateUsers(); - setUsername(""); - }} - > - Create User - - - } - /> - - ); + return ( + <> +
+ { + setShowCreateUserDialog(true); + }} + size={"s"} + color="filled" + > + Create new user + +
+ setShowCreateUserDialog(false)} + > + + Create user + + + + { + await trpc.instance.createUser.mutate({ + username: username(), + }); + props.updateUsers(); + setUsername(""); + }} + > + Create User + + + + ); }; export default CreateUser; diff --git a/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/User/User.tsx b/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/User/User.tsx index 096f88c..0b4091f 100644 --- a/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/User/User.tsx +++ b/applications/uk.tcsw.settings/web/pages/instance/components/Users/components/User/User.tsx @@ -1,20 +1,30 @@ -import UKStackItem from "@tcsw/uikit-solid/src/components/stack/UKStackItem.jsx"; -import { createResource, createSignal, type Component } from "solid-js"; -import styles from "./User.module.scss"; -import UKTextField from "@tcsw/uikit-solid/src/components/textField/UKTextField.jsx"; -import UKSwitch from "@tcsw/uikit-solid/src/components/switch/UKSwitch.jsx"; -import UKText from "@tcsw/uikit-solid/src/components/text/UKText.jsx"; import UKButton from "@tcsw/uikit-solid/src/components/button/UKButton.jsx"; -import trpc from "../../../../../../lib/trpc"; import UKButtonGroup from "@tcsw/uikit-solid/src/components/buttonGroup/UKButtonGroup.jsx"; import UKDialog from "@tcsw/uikit-solid/src/components/dialog/UKDialog.jsx"; import UKDivider from "@tcsw/uikit-solid/src/components/divider/UKDivider.jsx"; +import UKStackItem from "@tcsw/uikit-solid/src/components/stack/UKStackItem.jsx"; +import UKSwitch from "@tcsw/uikit-solid/src/components/switch/UKSwitch.jsx"; +import UKText from "@tcsw/uikit-solid/src/components/text/UKText.jsx"; +import UKTextField from "@tcsw/uikit-solid/src/components/textField/UKTextField.jsx"; +import { + type Component, + createResource, + createSignal, + Show, + useContext, +} from "solid-js"; +import { AppContext } from "../../../../../../appContext.ts"; +import trpc from "../../../../../../lib/trpc"; +import styles from "./User.module.scss"; const User: Component<{ userId: number; updateUsers: () => void; }> = (props) => { - const [showDialog, setShowDialog] = createSignal(false); + const appContext = useContext(AppContext)!; + const [showDialog, setShowDialog] = createSignal< + "user" | "confirmDelete" | "removeOwnAdmin" | undefined + >(undefined); const [username, { mutate: setUsername }] = createResource( () => trpc.instance.user.getUsername.query(props.userId), { @@ -43,6 +53,12 @@ const User: Component<{ () => trpc.instance.user.getIsAdministrator.query(props.userId), { initialValue: false }, ); + const [isMe] = createResource( + () => trpc.instance.user.getIsMe.query(props.userId), + { + initialValue: false, + }, + ); return ( <> @@ -51,11 +67,14 @@ const User: Component<{ type: "icon", value: isAdministrator() ? "shield_person" : "person", }} - labelText={`${forename()} ${surname()}`} + labelText={`${isMe() ? "(YOU) - " : ""} ${forename()} ${surname() !== "undefined" ? surname() : ""}`} supportingText={`(${props.userId}) ${username()}`} - onClick={() => setShowDialog(true)} + onClick={() => setShowDialog("user")} /> - setShowDialog(false)}> + showDialog() === "user"} + onClose={() => setShowDialog(undefined)} + >
Modify User @@ -72,6 +91,7 @@ const User: Component<{ }} defaultValue={username()} label="Username" + setValue={username()} />
Is Administrator { + if (isMe() && !val) { + setShowDialog("removeOwnAdmin"); + return; + } setIsAdministrator(val); trpc.instance.user.setIsAdministrator.mutate({ administrator: val, @@ -146,10 +174,7 @@ const User: Component<{ { - await trpc.instance.user.delete.mutate({ - userId: props.userId, - }); - props.updateUsers(); + setShowDialog("confirmDelete"); }} > Delete @@ -159,13 +184,86 @@ const User: Component<{ class={styles.closeButton} color={"filled"} onClick={() => { - setShowDialog(false); + setShowDialog(undefined); }} > Close
+ showDialog() === "confirmDelete"} + onClose={() => setShowDialog(undefined)} + > + + Confirm Deletion + + + + + Are you sure you want to delete this user? This action cannot be + undone. + + + { + await trpc.instance.user.delete.mutate({ + userId: props.userId, + }); + props.updateUsers(); + }} + > + Yes, delete + + setShowDialog(undefined)}> + No, cancel + + + + + + Sorry, you cannot delete your own user account. Please ask another + administrator to delete your account if you wish to do so. + + setShowDialog(undefined)}> + Close + + + + showDialog() === "removeOwnAdmin"} + onClose={() => setShowDialog(undefined)} + > + + Remove Administrator Privileges + + + + Are you sure you want to remove your own administrator privileges? You + will not be able to modify any users or settings if you do this. + Please ask another administrator or use the console if you need to + restore your privileges. + + + { + setIsAdministrator(false); + await trpc.instance.user.setIsAdministrator.mutate({ + administrator: false, + userId: props.userId, + }); + setShowDialog(undefined); + }} + > + Yes, remove + + setShowDialog("user")}> + Cancel + + + ); }; diff --git a/applications/uk.tcsw.settings/web/pages/profile/Index.module.scss b/applications/uk.tcsw.settings/web/pages/profile/Index.module.scss index 4a5d2dd..14fb170 100644 --- a/applications/uk.tcsw.settings/web/pages/profile/Index.module.scss +++ b/applications/uk.tcsw.settings/web/pages/profile/Index.module.scss @@ -21,7 +21,6 @@ display: flex; flex-direction: row; align-items: center; - padding-left: 1rem; gap: 1rem; .fullName { diff --git a/applications/uk.tcsw.settings/web/pages/profile/Index.tsx b/applications/uk.tcsw.settings/web/pages/profile/Index.tsx index 7122e88..a4b05ba 100644 --- a/applications/uk.tcsw.settings/web/pages/profile/Index.tsx +++ b/applications/uk.tcsw.settings/web/pages/profile/Index.tsx @@ -15,79 +15,93 @@ import { useNavigate } from "@solidjs/router"; import UKStackItem from "@tcsw/uikit-solid/src/components/stack/UKStackItem.tsx"; import webInstanceTRPC from "@tcsw/workspaces-instance-web/src/lib/trpc.ts"; import useIsMobile from "@tcsw/uikit-solid/src/core/useIsMobile.js"; +import UKDivider from "@tcsw/uikit-solid/src/components/divider/UKDivider.jsx"; const ProfilePage: Component = () => { - const navigate = useNavigate(); - const isMobile = useIsMobile(); - const [name, { refetch: refetchName }] = createResource(() => trpc.profile.getName.query()); - const [role] = createResource(() => trpc.profile.getRole.query()); - const [avatar, { refetch: refetchAvatar }] = createResource(() => trpc.profile.getProfilePicture.query()); + const navigate = useNavigate(); + const isMobile = useIsMobile(); + const [name, { refetch: refetchName }] = createResource(() => + trpc.profile.getName.query(), + ); + const [role] = createResource(() => trpc.profile.getRole.query()); + const [avatar, { refetch: refetchAvatar }] = createResource(() => + trpc.profile.getProfilePicture.query(), + ); - return ( - <> - + +
+
+ +
+ + {name() || "Unknown"} + + + {role() || "Unknown"} + +
+
+ + Basic info + + + + + + + + + + Contact info + + + + + {isMobile() ? ( + <> + + Session + + + { + webInstanceTRPC.authorization.logout.mutate(); + navigate("/"); }} - /> -
-
- -
- - {name() || "Unknown"} - - - {role() || "Unknown"} - -
-
- - Basic info - - - - - - - - - - Contact info - - - - - {isMobile() ? ( - <> - - Session - - - { - webInstanceTRPC.authorization.logout.mutate(); - navigate("/"); - }} - /> - - - ) : null} -
- - ); + /> +
+ + ) : null} +
+ + ); }; export default ProfilePage; diff --git a/applications/uk.tcsw.settings/web/pages/root/index.module.scss b/applications/uk.tcsw.settings/web/pages/root/index.module.scss index fb9c8c1..3c63a19 100644 --- a/applications/uk.tcsw.settings/web/pages/root/index.module.scss +++ b/applications/uk.tcsw.settings/web/pages/root/index.module.scss @@ -3,6 +3,8 @@ flex-direction: column; padding-top: 2rem; padding-bottom: 2rem; + padding-left: 1rem; + padding-right: 1rem; overflow-y: auto; align-items: center; } @@ -14,8 +16,6 @@ width: 100%; height: max-content; gap: 1rem; - padding-left: 1rem; - padding-right: 1rem; } .divider { @@ -27,9 +27,12 @@ display: flex; flex-direction: row; align-items: center; - padding-left: 1rem; + justify-content: center; gap: 1rem; + padding: 0; cursor: pointer; + background: transparent; + border: none; .fullName { color: rgb(var(--uk-sys-color-on-surface)); @@ -40,3 +43,13 @@ color: rgb(var(--uk-sys-color-on-surface-variant)); } } + +.quickActions { + display: flex; + flex-direction: row; + gap: 0.5rem; + align-items: center; + justify-content: center; + margin-top: 1rem; + margin-bottom: -1rem; +} diff --git a/applications/uk.tcsw.settings/web/pages/root/index.tsx b/applications/uk.tcsw.settings/web/pages/root/index.tsx index 61a7407..99994dd 100644 --- a/applications/uk.tcsw.settings/web/pages/root/index.tsx +++ b/applications/uk.tcsw.settings/web/pages/root/index.tsx @@ -9,79 +9,118 @@ import trpc from "../../lib/trpc"; import { useNavigate } from "@solidjs/router"; import UKStack from "@tcsw/uikit-solid/src/components/stack/UKStack.tsx"; import UKTopAppBar from "@tcsw/uikit-solid/src/components/topAppBar/UKTopAppBar.jsx"; +import UKButton from "@tcsw/uikit-solid/src/components/button/UKButton.jsx"; +import webInstanceTRPC from "@tcsw/workspaces-instance-web/src/lib/trpc.ts"; const RootPage: Component = () => { - const navigate = useNavigate(); - const [fullName] = createResource(() => trpc.overview.user.fullName.query()); - const [role] = createResource(() => trpc.overview.user.role.query()); - const [avatar] = createResource(() => trpc.overview.user.getAvatar.query()); + const navigate = useNavigate(); + const [fullName] = createResource(() => trpc.overview.user.fullName.query()); + const [role] = createResource(() => trpc.overview.user.role.query()); + const [avatar] = createResource(() => trpc.overview.user.getAvatar.query()); - return ( - <> - -
-
-
{ - navigate("/app/uk.tcsw.settings/profile"); - }} - > - -
- - {fullName() || "Unknown"} - - - {role() || "Unknown"} - -
-
- - - - - - - - {role() === "Administrator" && ( - - )} - -
+ return ( + <> + +
+
+ +
+ { + webInstanceTRPC.authorization.logout.mutate(); + navigate("/"); + }} + > + Logout + + + navigate( + "/app/uk.tcsw.settings/authentication/?change-passsword=true", + ) + } + > + Change Password + +
+ + + + + + + + {role() === "Administrator" && ( + + )} + +
+
+ + ); }; export default RootPage; diff --git a/applications/uk.tcsw.store/web/pages/manage-installed/Index.tsx b/applications/uk.tcsw.store/web/pages/manage-installed/Index.tsx index 4c20ad0..a2eb259 100644 --- a/applications/uk.tcsw.store/web/pages/manage-installed/Index.tsx +++ b/applications/uk.tcsw.store/web/pages/manage-installed/Index.tsx @@ -1,76 +1,84 @@ -import UKText from "@tcsw/uikit-solid/src/components/text/UKText.jsx"; -import { createEffect, createResource, createSignal, For, Suspense, type Component } from "solid-js"; -import styles from "./Index.module.scss"; -import UKIconButton from "@tcsw/uikit-solid/src/components/iconButton/UKIconButton.jsx"; -import trpc from "../../lib/trpc"; +import { useNavigate } from "@solidjs/router"; import UKButton from "@tcsw/uikit-solid/src/components/button/UKButton.jsx"; +import UKIcon from "@tcsw/uikit-solid/src/components/icon/UKIcon.jsx"; +import UKIconButton from "@tcsw/uikit-solid/src/components/iconButton/UKIconButton.jsx"; import UKStack from "@tcsw/uikit-solid/src/components/stack/UKStack.jsx"; -import UKSwitch from "@tcsw/uikit-solid/src/components/switch/UKSwitch.jsx"; import UKStackItem from "@tcsw/uikit-solid/src/components/stack/UKStackItem.jsx"; -import UKIcon from "@tcsw/uikit-solid/src/components/icon/UKIcon.jsx"; +import UKSwitch from "@tcsw/uikit-solid/src/components/switch/UKSwitch.jsx"; +import UKText from "@tcsw/uikit-solid/src/components/text/UKText.jsx"; import UKTopAppBar from "@tcsw/uikit-solid/src/components/topAppBar/UKTopAppBar.jsx"; -import { useNavigate } from "@solidjs/router"; +import { + type Component, + createEffect, + createResource, + createSignal, + For, + Suspense, +} from "solid-js"; +import trpc from "../../lib/trpc"; +import styles from "./Index.module.scss"; const ManageInstalledPage: Component = () => { - const navigate = useNavigate(); - const [selectionMode, setSelectionMode] = createSignal(false); - const [selectedApplicationIds, setSelectedApplicationIds] = createSignal([]); - const [installedApplications, { refetch: refetchInstalledApplications }] = createResource(() => - trpc.manageInstalled.getApplications.query(), - ); - const [enabledApplications, setEnabledApplications] = createSignal([]); + const navigate = useNavigate(); + const [selectionMode, setSelectionMode] = createSignal(false); + const [selectedApplicationIds, setSelectedApplicationIds] = createSignal([]); + const [installedApplications, { refetch: refetchInstalledApplications }] = createResource(() => + trpc.manageInstalled.getApplications.query(), + ); + const [enabledApplications, setEnabledApplications] = createSignal([]); - createEffect(() => { - setEnabledApplications(installedApplications()?.enabledApplications || []); - }); + createEffect(() => { + setEnabledApplications(installedApplications()?.enabledApplications || []); + }); - return ( -
- - {selectionMode() && ( - <> - { - await trpc.manageInstalled.uninstallApplications.mutate({ - applications: selectedApplicationIds(), - }); - await refetchInstalledApplications(); - setSelectedApplicationIds([]); - setSelectionMode(false); - return; - }} - color={"filled"} - size={"s"} - > - Uninstall - - - )} - {selectionMode() && ( - {`Selected: ${selectedApplicationIds().length}`} - )} - { - if (selectionMode()) setSelectedApplicationIds([]); + return ( +
+ + {selectionMode() && ( + { + await trpc.manageInstalled.uninstallApplications.mutate({ + applications: selectedApplicationIds(), + }); + await refetchInstalledApplications(); + setSelectedApplicationIds([]); + setSelectionMode(false); + return; + }} + color={"filled"} + size={"s"} + > + Uninstall + + )} + {selectionMode() && ( + {`Selected: ${selectedApplicationIds().length}`} + )} + { + if (selectionMode()) setSelectedApplicationIds([]); - setSelectionMode(!selectionMode()); - }} - /> - - } + setSelectionMode(!selectionMode()); + }} /> + + } + /> - {/* Perhaps add this at some point, create a dialogue with a text field for a custom install uri */} - {/* {!selectionMode() && ( + {/* Perhaps add this at some point, create a dialogue with a text field for a custom install uri */} + {/* {!selectionMode() && ( { }} /> )} */} - - - - {(app) => { - return ( - - - {!selectionMode() ? ( - <> - - navigate( - `/app/uk.tcsw.store/app/${app.repository}/${app.id}`, - ) - } - /> - {installedApplications()?.cannotDisable.includes(app.id) ? ( - <> - ) : ( - { - if (val) { - setEnabledApplications((prev) => [ - ...prev, - app.id, - ]); - } else { - setEnabledApplications((prev) => - prev.filter((i) => i !== app.id), - ); - } + + + + {(app) => { + return ( + + + + navigate( + `/app/uk.tcsw.store/app/${app.repository}/${app.id}?origin=/app/uk.tcsw.store/manage-installed`, + ) + } + /> + {installedApplications()?.cannotDisable.includes(app.id) ? null : ( + { + if (val) { + setEnabledApplications((prev) => [...prev, app.id]); + } else { + setEnabledApplications((prev) => + prev.filter((i) => i !== app.id), + ); + } - await trpc.manageInstalled.setEnabledApplications.mutate( - { - enabledApplications: enabledApplications(), - }, - ); - }} - value={enabledApplications().includes(app.id)} - /> - )} - - ) : installedApplications()?.cannotDisable.includes(app.id) ? ( - <> - ) : ( - - {selectedApplicationIds().includes(app.id) - ? "check" - : "check_indeterminate_small"} - - )} - - } - onClick={ - selectionMode() - ? installedApplications()?.cannotDisable.includes(app.id) - ? undefined - : async () => { - if (!selectedApplicationIds().includes(app.id)) { - setSelectedApplicationIds((prev) => [...prev, app.id]); - } else { - setSelectedApplicationIds((prev) => - prev.filter((i) => i !== app.id), - ); - } - } - : undefined - } - /> - - ); - }} - - - -
- ); + await trpc.manageInstalled.setEnabledApplications.mutate({ + enabledApplications: enabledApplications(), + }); + }} + value={enabledApplications().includes(app.id)} + /> + )} + + ) : installedApplications()?.cannotDisable.includes(app.id) ? null : ( + + {selectedApplicationIds().includes(app.id) + ? "check" + : "check_indeterminate_small"} + + ) + } + onClick={ + selectionMode() + ? installedApplications()?.cannotDisable.includes(app.id) + ? undefined + : async () => { + if (!selectedApplicationIds().includes(app.id)) { + setSelectedApplicationIds((prev) => [...prev, app.id]); + } else { + setSelectedApplicationIds((prev) => + prev.filter((i) => i !== app.id), + ); + } + } + : undefined + } + /> + + ); + }} + + + +
+ ); }; export default ManageInstalledPage; diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..b155c7a --- /dev/null +++ b/biome.json @@ -0,0 +1,26 @@ +{ + "json": { + "parser": { + "allowComments": true + } + }, + "linter": { + "rules": { + "suspicious": { + "noTsIgnore": "off" + }, + "a11y": { + "useValidAriaRole": "off" + }, + "style": { + "noNonNullAssertion": "off" + } + } + }, + "formatter": { + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 100 + } +} \ No newline at end of file diff --git a/bun.lock b/bun.lock index b0bfa6d..aa20836 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,9 @@ "@typescript/native-preview": "^7.0.0-dev.20260304.1", "prettier": "^3.8.1", }, + "devDependencies": { + "@biomejs/biome": "^2.4.8", + }, }, "applications/uk.tcsw.console/backend": { "name": "uk-tcsw-console-backend", @@ -458,6 +461,24 @@ "@balena/dockerignore": ["@balena/dockerignore@1.0.2", "", {}, "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q=="], + "@biomejs/biome": ["@biomejs/biome@2.4.8", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.8", "@biomejs/cli-darwin-x64": "2.4.8", "@biomejs/cli-linux-arm64": "2.4.8", "@biomejs/cli-linux-arm64-musl": "2.4.8", "@biomejs/cli-linux-x64": "2.4.8", "@biomejs/cli-linux-x64-musl": "2.4.8", "@biomejs/cli-win32-arm64": "2.4.8", "@biomejs/cli-win32-x64": "2.4.8" }, "bin": { "biome": "bin/biome" } }, "sha512-ponn0oKOky1oRXBV+rlSaUlixUxf1aZvWC19Z41zBfUOUesthrQqL3OtiAlSB1EjFjyWpn98Q64DHelhA6jNlA=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ARx0tECE8I7S2C2yjnWYLNbBdDoPdq3oyNLhMglmuctThwUsuzFWRKrHmIGwIRWKz0Mat9DuzLEDp52hGnrxGQ=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Jg9/PsB9vDCJlANE8uhG7qDhb5w0Ix69D7XIIc8IfZPUoiPrbLm33k2Ig3NOJ/7nb3UbesFz3D1aDKm9DvzjhQ=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-5CdrsJct76XG2hpKFwXnEtlT1p+4g4yV+XvvwBpzKsTNLO9c6iLlAxwcae2BJ7ekPGWjNGw9j09T5KGPKKxQig=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-Zo9OhBQDJ3IBGPlqHiTISloo5H0+FBIpemqIJdW/0edJ+gEcLR+MZeZozcUyz3o1nXkVA7++DdRKQT0599j9jA=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.8", "", { "os": "linux", "cpu": "x64" }, "sha512-PdKXspVEaMCQLjtZCn6vfSck/li4KX9KGwSDbZdgIqlrizJ2MnMcE3TvHa2tVfXNmbjMikzcfJpuPWH695yJrw=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.8", "", { "os": "linux", "cpu": "x64" }, "sha512-Gi8quv8MEuDdKaPFtS2XjEnMqODPsRg6POT6KhoP+VrkNb+T2ywunVB+TvOU0LX1jAZzfBr+3V1mIbBhzAMKvw=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-LoFatS0tnHv6KkCVpIy3qZCih+MxUMvdYiPWLHRri7mhi2vyOOs8OrbZBcLTUEWCS+ktO72nZMy4F96oMhkOHQ=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.8", "", { "os": "win32", "cpu": "x64" }, "sha512-vAn7iXDoUbqFXqVocuq1sMYAd33p8+mmurqJkWl6CtIhobd/O6moe4rY5AJvzbunn/qZCdiDVcveqtkFh1e7Hg=="], + "@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="], "@bufbuild/protobuf": ["@bufbuild/protobuf@2.11.0", "", {}, "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ=="], diff --git a/instance/src/systems/configuration.ts b/instance/src/systems/configuration.ts index 5960084..a5cb2b8 100644 --- a/instance/src/systems/configuration.ts +++ b/instance/src/systems/configuration.ts @@ -1,7 +1,11 @@ import type { Instance } from "../index.js"; import System from "../system.js"; import path from "path"; -import { promises as fs, readFileSync as fsReadFileSync } from "fs"; +import { + promises as fs, + readFileSync as fsReadFileSync, + existsSync as fsExistsSync, +} from "fs"; export enum WorkspacesFeatureFlags { SlashCommands = "slash_commands", @@ -124,11 +128,17 @@ export default class ConfigurationSystem extends System { ]; if ( - path.join( - this.instance.sys.filesystem.AUTOINSTALL_PATH, - "configuration.json", + fsExistsSync( + path.join( + this.instance.sys.filesystem.AUTOINSTALL_PATH, + "configuration.json", + ), ) ) { + this.log.info( + "Auto-install configuration detected. Loading configuration from auto-install.", + ); + let autoInstallConfig = JSON.parse( fsReadFileSync( path.join( diff --git a/instance/src/systems/users.ts b/instance/src/systems/users.ts index 0131032..94641ac 100644 --- a/instance/src/systems/users.ts +++ b/instance/src/systems/users.ts @@ -367,7 +367,7 @@ export class WorkspacesUser { Get if the user is an administrator @returns `boolean` - if the user an administrator */ - async isAdministrator(): Promise { + async isAdministrator(): Promise { const db = this.instance.sys.database.postgres(); return ( diff --git a/package.json b/package.json index 17c1c13..388de14 100644 --- a/package.json +++ b/package.json @@ -22,5 +22,8 @@ "dependencies": { "@typescript/native-preview": "^7.0.0-dev.20260304.1", "prettier": "^3.8.1" + }, + "devDependencies": { + "@biomejs/biome": "^2.4.8" } } diff --git a/uikit-solid/src/components/switch/UKSwitch.module.scss b/uikit-solid/src/components/switch/UKSwitch.module.scss index 29dae5a..b783d68 100644 --- a/uikit-solid/src/components/switch/UKSwitch.module.scss +++ b/uikit-solid/src/components/switch/UKSwitch.module.scss @@ -103,4 +103,30 @@ background-color: rgb(var(--uk-sys-color-on-primary)); } } + + &:disabled { + background-color: rgb(var(--uk-sys-color-outline)); + + &::before { + background-color: rgb(var(--uk-sys-color-outline)); + } + + .handle { + color: rgb(var(--uk-sys-color-surface-container-highest), 0.38); + background-color: rgb(var(--uk-sys-color-outline), 0.38); + } + } + + &[data-value="true"]:disabled { + background-color: rgb(var(--uk-sys-color-outline)); + + &::before { + background-color: rgb(var(--uk-sys-color-outline)); + } + + .handle { + color: rgb(var(--uk-sys-color-on-surface), 0.38); + background-color: rgb(var(--uk-sys-color-surface), 0.38); + } + } } diff --git a/uikit-solid/src/components/switch/UKSwitch.tsx b/uikit-solid/src/components/switch/UKSwitch.tsx index 62de49a..34a008f 100644 --- a/uikit-solid/src/components/switch/UKSwitch.tsx +++ b/uikit-solid/src/components/switch/UKSwitch.tsx @@ -4,22 +4,24 @@ import styles from "./UKSwitch.module.scss"; import UKIcon from "../icon/UKIcon"; const UKSwitch: Component<{ - value: boolean; - getValue: (value: boolean) => void; - class?: string; - icon?: boolean; + value: boolean; + getValue: (value: boolean) => void; + class?: string; + icon?: boolean; + disabled?: boolean; }> = (props) => { - return ( - - ); + return ( + + ); }; export default UKSwitch; diff --git a/uikit-solid/src/components/textField/UKTextField.tsx b/uikit-solid/src/components/textField/UKTextField.tsx index 41fd27e..09fd726 100644 --- a/uikit-solid/src/components/textField/UKTextField.tsx +++ b/uikit-solid/src/components/textField/UKTextField.tsx @@ -6,127 +6,138 @@ import UKIcon from "../icon/UKIcon"; // TODO: add reveal password 'eye' icon const UKTextField: Component<{ - color: "filled" | "outlined"; - leadingIcon?: { icon: string; onClick?: () => void }; - labelEmpty?: string; - label: string; - trailingIcon?: { icon: string; onClick?: () => void }; - supportingText?: string; - getValue: (value: string) => void; - onEscape?: () => void; - onSubmit?: () => void; - onFocus?: () => void; - onBlur?: () => void; - defaultValue?: string; - setValue: string; - maximumCharacterCount?: number; - shouldMask?: boolean; - forceFocussed?: boolean; - as?: "textarea" | "input"; - error?: boolean; - class?: string; - containerClass?: string; - autocomplete?: string; + color: "filled" | "outlined"; + leadingIcon?: { icon: string; onClick?: () => void }; + labelEmpty?: string; + label: string; + trailingIcon?: { icon: string; onClick?: () => void }; + supportingText?: string; + getValue: (value: string) => void; + onEscape?: () => void; + onSubmit?: () => void; + onFocus?: () => void; + onBlur?: () => void; + defaultValue?: string; + setValue: string; + maximumCharacterCount?: number; + shouldMask?: boolean; + forceFocussed?: boolean; + as?: "textarea" | "input"; + error?: boolean; + class?: string; + containerClass?: string; + autocomplete?: string; }> = (props) => { - const [characterLength, setCharacterLength] = createSignal(0); - let textAreaRef!: HTMLTextAreaElement; - let inputRef!: HTMLInputElement; + const [characterLength, setCharacterLength] = createSignal(0); + let textAreaRef!: HTMLTextAreaElement; + let inputRef!: HTMLInputElement; - if (props.defaultValue && props.defaultValue !== "") { - setCharacterLength(props.defaultValue.length); - } + if (props.defaultValue && props.defaultValue !== "") { + setCharacterLength(props.defaultValue.length); + } - const elementProperties = { - class: clsx(props.class, styles.input), - onKeyUp: ( - e: KeyboardEvent & { - currentTarget: HTMLInputElement | HTMLTextAreaElement; - target: DOMElement; - }, - ) => { - if (e.key === "Escape") { - e.currentTarget.blur(); - props.onEscape?.(); - } + const elementProperties = { + class: clsx(props.class, styles.input), + onKeyUp: ( + e: KeyboardEvent & { + currentTarget: HTMLInputElement | HTMLTextAreaElement; + target: DOMElement; + }, + ) => { + if (e.key === "Escape") { + e.currentTarget.blur(); + props.onEscape?.(); + } - setCharacterLength(e.currentTarget.value.length); + setCharacterLength(e.currentTarget.value.length); - props.getValue(e.currentTarget.value); - }, - onSubmit: props.onSubmit, - value: props.defaultValue, - maxLength: props.maximumCharacterCount, - type: props.shouldMask ? "password" : "text", - onFocus: props.onFocus, - onBlur: props.onBlur, - autocomplete: props.autocomplete, - }; + props.getValue(e.currentTarget.value); + }, + onSubmit: props.onSubmit, + value: props.defaultValue, + maxLength: props.maximumCharacterCount, + type: props.shouldMask ? "password" : "text", + onFocus: props.onFocus, + onBlur: props.onBlur, + autocomplete: props.autocomplete, + }; - createEffect(() => { - if (props.setValue === undefined) return; + let didMount = false; + createEffect(() => { + if (!didMount) { + didMount = true; + return; + } + if (props.setValue === undefined) return; - if (textAreaRef) { - textAreaRef.value = props.setValue; + if (textAreaRef) { + textAreaRef.value = props.setValue; - setCharacterLength(textAreaRef.value.length); + setCharacterLength(textAreaRef.value.length); - props.getValue(textAreaRef.value); - } - if (inputRef) { - inputRef.value = props.setValue; + props.getValue(textAreaRef.value); + } + if (inputRef) { + inputRef.value = props.setValue; - setCharacterLength(inputRef.value.length); + setCharacterLength(inputRef.value.length); - props.getValue(inputRef.value); - } - }, [props.setValue]); + props.getValue(inputRef.value); + } + }, [props.setValue]); - return ( -
-
0} - data-force-focus={props.forceFocussed} - > - {props.leadingIcon && ( - - {props.leadingIcon.icon} - - )} -
- {props.as === "textarea" ? ( -